Nuxt 4 的架构减法
一个内容型站点不需要复杂前端状态。让路由、服务端渲染和内容查询各自回到最擅长的位置。

很多 Nuxt 项目变复杂,并不是因为业务真的复杂,而是因为我们太早把所有可能性都放进了架构。
个人博客是一个很好的校准器:页面数量有限、内容结构稳定、交互集中在少数区域。它提醒我们,框架最有价值的部分往往不是增加能力,而是删除不必要的选择。
从默认 SSR 开始
技术文章需要被搜索引擎理解,也需要在弱网下尽早出现正文。Nuxt 默认的服务端渲染刚好符合这个目标。
export default defineNuxtConfig({
modules: ['@nuxt/content'],
nitro: {
preset: 'cloudflare',
},
})
这里没有把整个站改成 SPA,也没有为每个页面单独发明数据加载方式。页面在服务端完成第一次查询和渲染,客户端接管后继续使用同一套路由。
如果某一小块必须依赖浏览器 API,再局部使用 ClientOnly。不要为了一个客户端组件牺牲整页的语义和首屏。
内容查询留在页面边界
首页、文章列表和文章详情需要的数据粒度不同。查询应该贴近消费它的页面:
const { data: posts } = await useAsyncData('latest-posts', () => {
return queryCollection('posts')
.where('draft', '=', false)
.order('publishedAt', 'DESC')
.limit(6)
.all()
})
这样做有几个直接好处:
- 数据参与 SSR,不需要
onMounted后再闪烁加载; - 查询键明确,Nuxt 可以在 hydration 时复用 payload;
- 页面知道自己需要什么,不必经过一层通用 store;
- 草稿过滤与排序规则出现在真正相关的位置。
只有当多个页面共享了同一段复杂业务状态时,才值得抽成 composable 或 store。
组件只抽取稳定重复
“看起来可能复用”不是组件边界。真正稳定的重复通常同时包含结构、语义和行为。
这个博客里,文章卡片是合适的组件:它在首页和索引页重复出现,拥有固定的元数据、标题、摘要、标签和跳转行为。
相反,首页 Hero 即使很大,也未必需要拆成五个小组件。它只在一个页面出现,内部元素共同完成一段视觉叙事。过早拆分只会让样式关系跨文件跳转。
组件不是文件整理工具,而是可独立理解和复用的 UI 契约。
服务端 API 保持窄接口
登录与留言属于运行时数据,放在 server/api。每个 endpoint 只做一件事:
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/session
GET /api/comments/:post
POST /api/comments/:post
窄接口使授权规则更容易检查。读取留言是公开的,创建留言需要会话;退出只销毁当前令牌,不需要触碰用户资料。
如果一个 endpoint 同时处理读取、写入、登录和格式转换,它就已经失去了清晰边界。
把状态放回正确的位置
Vue 的响应式状态很方便,但不是所有状态都应该进入 JavaScript:
| 状态 | 最合适的位置 |
|---|---|
| 当前文章 | 路由 |
| 搜索词 | 页面 ref |
| 标签筛选 | URL query + 页面状态 |
| 登录用户 | SSR 安全的 useState |
| 文章正文 | Nuxt Content |
| 会话真实性 | 服务端 D1 |
当状态位置正确时,大量“同步逻辑”会自然消失。我们不再需要监听路由去修复 store,也不需要用 localStorage 假装登录状态可靠。
减法不是功能更少
好的架构减法不会让用户少得到什么。相反,它把复杂度预算留给真正有感知的部分:
- 更清晰的排版;
- 更快的首屏;
- 更可靠的登录;
- 更舒服的移动端阅读;
- 更容易坚持的 Markdown 写作流程。
Nuxt 4 提供了足够多的能力。项目成熟的标志,不是把它们全部用上,而是知道哪些能力可以暂时不用。
留言
登录后加入讨论
你的邮箱不会公开,留言只显示昵称。