Next.js App Router 全栈实战:从零构建一个 Markdown 笔记系统

前言

如果你正在学习 React 全栈开发,Next.js 几乎是你绕不开的框架。它不仅提供了服务端渲染(SSR)搜索引擎优化(SEO) 能力,还引入了 React Server Component(RSC) 这个革命性的概念,让你在同一个项目中既能写服务端逻辑,也能写客户端交互。

本文将带你从零开始,用 Next.js App Router 构建一个完整的 Markdown 笔记系统。这个项目麻雀虽小五脏俱全:涵盖了路由设计、组件拆分、数据库操作、动态路由、表单编辑等核心知识点。读完本文,你将掌握 Next.js 全栈开发的核心方法论。


一、技术背景

1.1 为什么选 Next.js?

Next.js 是 Vercel 推出的 React 全栈框架,它解决了传统 React 单页应用(SPA)的几个痛点:

痛点 传统 SPA Next.js
SEO 页面是 JS 渲染的,搜索引擎抓不到内容 服务端渲染,HTML 直接返回
首屏加载 需要下载整个 JS bundle 才能渲染 服务端预渲染,首屏秒开
前后端协作 需要单独搭建 BFF 层或 API 服务 一个项目搞定前后端
路由 需要额外安装 react-router 文件系统即路由,零配置

1.2 核心概念速览

在开始写代码之前,先理解几个关键概念:

  • SSR(Server-Side Rendering):页面在服务端渲染成 HTML,浏览器直接展示,首屏快、SEO 友好
  • RSC(React Server Component):默认情况下,Next.js App Router 中的组件都是服务端组件,可以直接访问数据库、文件系统等后端资源
  • "use client":当组件需要交互(useState、useEffect、onClick 等)时,在文件顶部加上这个指令,将其标记为客户端组件
  • Hydration(水合):服务端渲染的 HTML 到达浏览器后,React 在客户端"激活"它,使其具备交互能力,这个过程叫水合

二、项目需求分析

2.1 我们要做什么

一个支持 Markdown 格式的笔记系统,具备完整的 CRUD 功能:

  1. 界面布局:两列布局,左侧笔记列表,右侧笔记内容
  2. 创建笔记:点击 New 按钮新增笔记,左侧列表同步更新
  3. 查看笔记:点击左侧笔记项,右侧显示 Markdown 渲染后的内容
  4. 编辑笔记:支持 Markdown 格式的编辑器
  5. 删除笔记:删除后左侧列表同步更新
  6. 搜索功能:在笔记列表中进行搜索

2.2 技术方案

  • 框架:Next.js 16 + React 19
  • 路由:App Router(文件系统路由)
  • 数据库 :Redis(通过 lib/redis.js 封装)
  • Markdown 渲染marked 库将 Markdown 转为 HTML
  • 样式:CSS 自定义属性 + BEM 命名规范

核心设计 :数据库中存储的是 Markdown 原文,页面展示时通过 marked 渲染为 HTML。这样既保证了数据的可编辑性,又保证了展示的美观性。


三、路由设计:App Router 文件即路由

Next.js App Router 的核心思想是 "文件即路由",目录结构直接映射 URL 路径。这意味着你不需要写任何路由配置代码,创建文件夹和文件就自动生成了路由。

3.1 路由规划

对于这个笔记系统,我们需要以下路由:

bash 复制代码
app/
├── page.js                  → /                      首页(空状态提示)
├── layout.js                → 全局布局(侧边栏 + 内容区)
├── note/
│   ├── [id]/
│   │   └── page.js          → /note/123              笔记详情
│   └── edit/
│       ├── page.js           → /note/edit             新增笔记
│       └── [id]/
│           └── page.js       → /note/edit/123         编辑笔记

3.2 路由解读

URL 对应文件 功能
/ app/page.js 首页,显示"点击左侧笔记查看"
/note/123 app/note/[id]/page.js 查看 id 为 123 的笔记详情
/note/edit app/note/edit/page.js 新增笔记
/note/edit/123 app/note/edit/[id]/page.js 编辑 id 为 123 的笔记

[id] 是动态路由参数 ,在组件中通过 params 参数获取:

javascript 复制代码
// app/note/[id]/page.js
export default async function Page({ params }) {
  const { id } = await params;
  // 用 id 查询数据库...
}

3.3 RESTful 风格对照

这个路由设计天然契合 RESTful API 风格:

HTTP 方法 路由 操作
GET /note/123 查看笔记
GET /note/edit 新增笔记页面
POST /note/edit 提交新增
GET /note/edit/123 编辑笔记页面
PUT /note/edit/123 提交修改
DELETE /note/123 删除笔记

四、组件规划:规范驱动编程

开发之前不要急着写代码。先分析需求,规划技术方案,拆解路由和组件。

这是项目中最重要的一条原则。写代码之前先把组件树画清楚,你会发现自己少走很多弯路。

4.1 组件树

markdown 复制代码
RootLayout
├── Sidebar(侧边栏)
│   ├── SidebarSearchField(搜索框)
│   ├── EditButton(新增/编辑按钮,可复用)
│   └── SidebarNoteList(笔记列表)
│       └── NoteItem(单条笔记)
└── Note(笔记内容区)
    ├── NoteEditor(编辑器)
    └── NotePreview(Markdown 预览)

全局布局(app/layout.js

javascript 复制代码
import './style.css'
import Sidebar from '@/components/Sidebar'

export default async function RootLayout({ children }) {
  return (
    <html>
      <head>
        <title>JieE的大模型工程师博客</title>
        <meta name="description" content="..." />
        <meta name="keywords" content="llm,claude,deepseek,rag,langchain" />
      </head>
      <body>
        <div className="container">
          <div className="main">
            <Sidebar />
            <section className="col note-viewer">
              {children}  {/* 页面内容动态渲染在这里 */}
            </section>
          </div>
        </div>
      </body>
    </html>
  );
}

这里有几个关键点:

  • layout.jsRSC 组件async function),可以直接做服务端操作
  • <head> 中的 SEO 标签(title、meta)直接在服务端渲染,搜索引擎能抓取到
  • {children} 是子页面的占位符,Next.js 会自动把对应路由的页面内容插入这里

侧边栏(components/Sidebar.js

javascript 复制代码
import Link from 'next/link'

export default async function Sidebar() {
  return (
    <section className="col sidebar">
      <Link href="/" className="sidebar-header">
        <img className="logo" src="/logo.svg" role="presentation" />
        <strong>LLM Notes</strong>
      </Link>
      <section className="sidebar-menu" role="menubar">
        {/* 搜索框和新增按钮,后续实现 */}
      </section>
    </section>
  )
}

五、目录架构

python 复制代码
my-app/
├── app/                    # 页面主目录(文件即路由)
│   ├── page.js             # 首页
│   ├── layout.js           # 全局布局
│   ├── style.css           # 全局样式
│   └── note/
│       ├── [id]/
│       │   └── page.js     # 笔记详情
│       └── edit/
│           ├── page.js     # 新增笔记
│           └── [id]/
│               └── page.js # 编辑笔记
├── components/             # 可复用组件
│   └── Sidebar.js
├── lib/                    # 工具函数和数据库操作
│   └── redis.js
├── public/                 # 静态资源
│   └── logo.svg
├── jsconfig.json           # 路径别名配置
├── package.json
└── next.config.mjs

5.1 各目录职责

目录 职责
app/ 页面路由,每个 page.js 对应一个 URL
components/ 可复用的 UI 组件,与路由无关
lib/ 数据库操作、通用工具函数
public/ 静态资源(图片、字体等),可直接通过 /logo.svg 访问

六、路径别名(Alias)配置

当项目结构深了之后,你可能会写出这样的 import:

javascript 复制代码
// ❌ 地狱级相对路径
import { getAllNotes } from '../../../lib/redis.js';

通过 jsconfig.json(或 tsconfig.json)配置路径别名,可以大幅简化导入:

json 复制代码
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["components/*"],
      "@/lib/*": ["lib/*"]
    }
  }
}

配置后,所有 import 都变得干净利落:

javascript 复制代码
// ✅ 无论文件在哪,引入路径都一样
import { getAllNotes } from "@/lib/redis.js";
import Sidebar from "@/components/Sidebar";

@ 符号直接指向项目根目录,不管你的文件嵌套多深,引入路径始终如一。


七、BEM 命名规范

在 CSS 中,我们采用 BEM(Block Element Modifier) 命名规范,这是国际通用的 CSS 命名方法论:

符号 含义 示例 说明
块名 Block(块) .note 一个独立的组件
__ Element(元素) .note__title 块内部的子元素
-- Modifier(修改器) .note--empty-state 块的不同状态或变体

7.1 项目中的实际应用

css 复制代码
/* Block:笔记组件 */
.note { ... }

/* Element:笔记标题 */
.note-header { ... }

/* Modifier:空状态变体 */
.note--empty-state {
  margin-inline: 20px 20px;
}

/* 更复杂的组合 */
.edit-button--solid { ... }   /* 实心按钮 */
.edit-button--outline { ... } /* 描边按钮 */

对比 Tailwind CSS:Tailwind 是原子类方案(每个 class 只做一件事,通过组合实现复杂样式),BEM 是语义化方案(通过命名表达组件结构和状态)。两者不冲突,可以根据团队偏好选择或结合使用。


八、数据层:lib/redis.js

数据层是连接页面和数据库的桥梁。我们将所有数据库操作封装在 lib/ 目录下:

javascript 复制代码
// lib/redis.js
export async function getAllNotes() {
  // 从 Redis 获取所有笔记
}

这种封装的好处:

  1. 单一职责 :页面只关心展示,数据操作逻辑集中在 lib/
  2. 易于测试:可以单独对数据层进行单元测试
  3. 易于切换 :将来换数据库(如 MySQL、PostgreSQL),只需改 lib/ 文件,页面代码不用动

九、RSC(React Server Component)实战

Next.js App Router 中,默认所有组件都是服务端组件。这意味着你可以直接在组件中:

javascript 复制代码
// app/note/[id]/page.js
import { getAllNotes } from "@/lib/redis.js";

export default async function NotePage({ params }) {
  const { id } = await params;
  // 直接在服务端组件中调用数据库,无需 fetch/axios
  const notes = await getAllNotes();
  const note = notes.find(n => n.id === id);

  return (
    <div className="note">
      <h1>{note.title}</h1>
      <div>{note.content}</div>
    </div>
  );
}

关键点

  • 组件用 async function,可以 await 数据库查询
  • 数据在服务端获取,减少客户端请求
  • 不需要 useEffect + useState 来管理异步数据

当组件需要交互(表单、按钮点击等)时,加上 "use client" 指令即可:

javascript 复制代码
"use client";
// 现在可以使用 useState、useEffect、onClick 等客户端特性

十、总结

通过这个项目,我们完整走通了 Next.js App Router 全栈开发的核心流程:

  1. 需求分析 → 明确功能边界和技术选型
  2. 路由设计 → 利用 App Router 的文件系统路由,天然支持 RESTful 风格
  3. 组件规划 → 先画组件树,再写代码(规范驱动编程)
  4. 目录架构app/ 路由 + components/ 组件 + lib/ 数据层 + public/ 静态资源
  5. 路径别名@/ 一劳永逸解决相对路径地狱
  6. BEM 命名 → 让 CSS 具备语义化和可维护性
  7. RSC → 服务端组件直接操作数据库,减少前后端接口联调

核心思想 :Next.js 的 App Router 让前端开发者可以像写后端一样写代码------直接在组件中访问数据库,不再需要傻等后端接口。而 "use client" 指令让交互组件也能无缝嵌入这个体系中。服务端负责数据和渲染,客户端负责交互和体验,这就是 Next.js 全栈开发的核心哲学。


附录:项目文件清单

文件 说明
app/layout.js 全局布局,包含 Sidebar + 内容区
app/page.js 首页,空状态提示
app/note/[id]/page.js 笔记详情页(动态路由)
app/note/edit/page.js 新增笔记页
app/note/edit/[id]/page.js 编辑笔记页(动态路由)
components/Sidebar.js 侧边栏组件
lib/redis.js 数据库操作封装
jsconfig.json 路径别名配置
app/style.css 全局样式(CSS Reset + BEM)
package.json 项目依赖:Next.js 16 + React 19
相关推荐
DsirNg8 小时前
React Server Components 在真实项目中的边界:哪些组件该放在服务端
性能优化·react·next.js·app router·前端架构·rsc·react server components
濮水大叔1 天前
CabloyJS 强大之处不仅仅是 IoC,而是全栈资源的寻址体系
typescript·node.js·全栈
东方小月1 天前
从零开发一个 Coding Agent(十一):实现 CLI 的 print 模式
node.js·全栈
Asize1 天前
为什么写代码前要先规划组件树?Next.js + Redis 笔记系统实战
前端·javascript·next.js
小林ixn1 天前
用 Next.js 和 Redis 撸一个 Markdown 笔记系统:RSC 实战与组件化拆解
前端·redis·next.js
浮生望2 天前
Next.js App Router 实战入门:从 SPA 到 SSR 的全栈思维转变
全栈
用户938515635073 天前
Next.js 笔记系统(二):Redis 数据服务与侧边栏组件拆分实战
javascript·全栈
用户938515635073 天前
从 0 拆解一个 Next.js 笔记系统:npx、App Router、RSC 与组件规划全记录
前端·后端·全栈
GitLqr3 天前
玩转 Flutter 中的 Stack 与 Positioned:解决 UI 重叠问题的实战指南
flutter·面试·全栈