前言
如果你正在学习 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 功能:
- 界面布局:两列布局,左侧笔记列表,右侧笔记内容
- 创建笔记:点击 New 按钮新增笔记,左侧列表同步更新
- 查看笔记:点击左侧笔记项,右侧显示 Markdown 渲染后的内容
- 编辑笔记:支持 Markdown 格式的编辑器
- 删除笔记:删除后左侧列表同步更新
- 搜索功能:在笔记列表中进行搜索
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 预览)
4.2 实际代码:Layout 和 Sidebar
全局布局(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.js是 RSC 组件 (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 获取所有笔记
}
这种封装的好处:
- 单一职责 :页面只关心展示,数据操作逻辑集中在
lib/ - 易于测试:可以单独对数据层进行单元测试
- 易于切换 :将来换数据库(如 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 全栈开发的核心流程:
- 需求分析 → 明确功能边界和技术选型
- 路由设计 → 利用 App Router 的文件系统路由,天然支持 RESTful 风格
- 组件规划 → 先画组件树,再写代码(规范驱动编程)
- 目录架构 →
app/路由 +components/组件 +lib/数据层 +public/静态资源 - 路径别名 →
@/一劳永逸解决相对路径地狱 - BEM 命名 → 让 CSS 具备语义化和可维护性
- 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 |