Next.js 16 全栈实战:从零搭一个 Markdown 笔记系统
用最新版 Next.js 16 + React 19 + Redis,手把手写一个「左列表右内容」的笔记应用,带你吃透 App Router、RSC 与工程化规范。
一、这个项目要做什么
一个极简笔记系统,核心需求就四条:
- 界面分两列:左侧笔记列表,右侧笔记内容
- 支持笔记的增删改查(CRUD),存储为 Markdown
- 数据库里存 Markdown,页面展示渲染后的 HTML
- 支持搜索
技术选型如下:
| 能力 | 方案 |
|---|---|
| 框架 | Next.js 16.3.1(App Router) |
| UI | React 19.2.8 |
| 数据存储 | Redis(ioredis 客户端) |
| 日期处理 | dayjs |
一个很有意思的点:我们没有用 MySQL,而是用 Redis 直接当数据库(这个放到第二篇详细讲)。
二、先规划,再写代码
很多同学拿到需求就埋头写代码,结果越写越乱。这里的做法是「规范驱动编程」------先分析需求,再拆任务细节(路由 + 组件),最后才动手。
组件本质上就是「工作单元」,把需求拆成清晰的组件树:
markdown
Sidebar 侧边栏
├── SidebarSearchField 搜索框(规划中)
└── SidebarNoteList 笔记列表
└── SidebarNoteItem 单条笔记
Note 右侧内容
├── NoteEditor 编辑态
└── NotePreview 预览态(渲染 Markdown)
有了这张组件图,团队分工、AI 辅助生成、代码维护都清晰很多。规划好了,再动手写,速度反而更快。
三、App Router:文件即路由
Next.js 的 App Router 把「文件路径」直接映射成「URL 路由」,天然就是 RESTful 风格。本项目目录结构:
bash
app/
layout.js 全局布局(头部 SEO + 侧边栏 + 内容区)
page.js 首页(右侧空状态)
note/
[id]/page.js 笔记详情(动态路由)
edit/
page.js 新增一条笔记
[id]/page.js 编辑某条笔记
components/ 组件
lib/ 数据服务层
public/ 静态资源
几个核心点:
1. 动态路由 [id]
文件夹用方括号 [id] 表示动态段,访问 /note/1702459181837 时,[id]/page.js 就能拿到这个 id,用来去数据库查对应笔记。
2. 新增 vs 编辑的拆分
/note/edit/page.js 是「新增」,/note/edit/[id]/page.js 是「编辑」。区别就在于有没有 [id]------带 id 说明是改已有笔记,不带就是新建。一套逻辑,两种路由,非常清晰。
3. 根布局 layout.js
layout.js 是全站骨架,看真实代码:
JSX
javascript
import './style.css';
import Sidebar from '@/components/Sidebar';
export default async function RootLayout({children}) {
return (
<html>
<head>
<title>戴总的大模型工程师博客</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>
)
}
左侧 Sidebar 固定,右侧 {children} 会随着路由切换渲染不同页面------这就是布局复用的精髓。
四、RSC:在服务端直接读数据
Next.js 16 最大的心智转变是 React Server Component(RSC) 。组件默认跑在服务端,可以直接 await 数据库、文件系统,而不需要额外的 API 层。
看 Sidebar.js:
JSX
javascript
import { getAllNotes } from '@/lib/redis';
import SidebarNoteList from './SidebarNoteList';
export default async function Sidebar() {
const notes = await getAllNotes(); // 服务端直接读 Redis
return (
<section className="col sidebar">
{/* ... */}
<nav>
<SidebarNoteList notes={notes} />
</nav>
</section>
)
}
注意两点:
- 组件是
async function,因为它要await后端数据 - 没有
use client,默认就是服务端组件,SEO 友好(内容直接渲染进 HTML,爬虫能抓到)
SSR + RSC 让首屏既有数据、又对搜索引擎友好,这是 Next.js 相比纯 SPA 的核心优势。
五、路径别名:告别 ../../../
深目录里引入 lib/redis.js,写相对路径会变成 ../../../lib/redis.js,又丑又易错。项目里配了 alias:
JSON
json
// jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"]
}
}
}
于是可以这样优雅地引入:
js
javascript
import { getAllNotes } from '@/lib/redis';
import Sidebar from '@/components/Sidebar';
@ 直接定位到项目根目录,路径一目了然。
六、BEM 命名规范
CSS 类名不维护,项目大了就是灾难。本项目采用 BEM 国际命名规范:
- Block :独立的块,如
.sidebar - Element :块的子元素,用
_连接,如.sidebar-header - Modifier :状态的修饰,用
__连接
JSX
css
<span className="note-text--empty-state">
Click a note on the left to view something.
</span>
语义化标签(nav / section)+ BEM 命名,让样式代码也能自解释,配合「注释大法」把未来规划写清楚(比如 {/* SideSearchField 未来干 */}),团队协作体验拉满。
总结
这篇文章我们搭好了 Next.js 16 笔记应用的「骨架」:用组件规划拆需求、用 App Router 组织路由、用 RSC 直连数据、用 alias 和 BEM 规范工程。下一篇,我们把目光转向数据层------为什么这个项目敢用 Redis 当数据库。