用 Next.js 从零搭一个博客:目录结构与渲染策略
搭博客最容易走偏的地方,不是样式,而是一开始就没想清楚"什么该静态、什么该动态"。这篇先把目录和渲染策略定下来,后面的功能都只是往里加东西。
目录结构#
这是本项目实际使用的分层,核心原则是:内容与代码分离、读内容的能力收敛到一个文件里。
moyu-blog/
├─ app/ # 页面与路由(App Router)
│ ├─ page.tsx # 首页
│ ├─ posts/ # 全部文章列表
│ ├─ section/[section]/ # 栏目页
│ ├─ tag/[tag]/ # 标签页
│ ├─ post/[slug]/ # 文章详情
│ ├─ admin/ # 后台(写/改/删)
│ └─ api/ # 评论、搜索、后台接口
├─ components/ # 组件(服务端为主,交互单独成客户端组件)
├─ content/posts/ # ★ 文章 Markdown(带 frontmatter)
├─ data/comments.json # ★ 评论数据
├─ lib/ # 读内容 / 渲染 / 鉴权
└─ site.config.ts # 站点与栏目配置好处很直接:备份 = 复制 content/ 和 data/;换主题 = 只动 app/ 与 components/;加栏目 = 改 site.config.ts。
渲染策略:默认静态,例外才动态#
| 页面 | 策略 | 原因 |
|---|---|---|
| 首页 / 列表 / 栏目 / 标签 | ISR(revalidate = 300) |
内容变化不频繁,静态 HTML 抓取最快 |
| 文章详情 | ISR | 正文要出现在首屏 HTML 里 |
| 后台管理 | 强制动态 | 依赖登录 Cookie |
| 评论接口 | 强制动态 | 每次读写文件 |
判断标准就一句话:搜索引擎和访客看到的内容,必须能在不执行 JS 的情况下拿到。
读取内容只有一层#
不要在页面里到处 fs.readFileSync,把读取集中到 lib/posts.ts:
export function getAllPosts(options: ListOptions = {}): PostMeta[] {
const { includeDrafts = false, section, tag, query, limit, offset } = options;
let posts: PostMeta[] = [];
for (const file of markdownFiles()) {
const post = readPostFile(file);
if (!includeDrafts && post.draft) continue;
posts.push(post);
}
// 过滤 + 排序 + 分页
return posts;
}页面只负责"拿数据 → 拼 HTML",这样搜索、RSS、sitemap 都能复用同一份数据源,不会出现"列表有、RSS 没有"的不一致。
经验:每多一个直接读文件的地方,就多一个数据不一致的隐患。
什么时候需要用客户端组件#
只有三类必须 'use client':
- 需要浏览器状态:深色模式开关、阅读进度条、目录高亮;
- 需要事件委托:图片灯箱、代码复制按钮;
- 需要请求接口:评论区、搜索框。
其余全部保持服务端组件。把 useState 塞进列表卡片这类组件,只会白白增大客户端包体积。
上线前自查#
-
curl首页能看到文章标题与摘要(说明是服务端渲染) - 每个文章页
<title>与canonical正确 -
/sitemap.xml与/rss.xml可访问 -
/search/设置了noindex - 后台
/admin/未登录时看不到内容
下一篇会把"内容更新后立刻生效"这一块讲透。
评论
正在加载评论…