金键盘
发布于 2025-08-14 / 5 阅读
0
0

nextjs 心智负担好重,给我整理一些需要关注的细节。

小白问:
nextjs 心智负担好重,给我整理一些需要关注的细节。

大牛:
别急,按层拆就不那么累了。下面按“必懂/易坑/进阶优化”三层给你一张速查清单,照着走就好。

一、必懂基线(先稳住基本盘)

  1. 路由与文件约定

  • app 目录:基于文件的路由,layout.tsx 可嵌套,page.tsx 是页面,loading/error/not-found 为内置边界。

  • server component 默认:组件默认在服务端渲染(RSC)。需要浏览器交互的组件用 "use client"。

  • 路径与动态路由:[slug]、[...catchAll],生成静态/动态路径要区分清楚。

  1. 数据获取模型

  • Server Component 直接 await fetch/DB(推荐,少包袱)。

  • Route Handlers(app/api/**):写后端接口,便于前后统一。

  • Actions(server actions):表单/事件直调服务端函数,减少 API 层样板。

  • 静态 vs 动态:fetch 默认缓存(静态化)。动态数据用 { cache: 'no-store' } 或 { next: { revalidate: 0 } }。

  1. 渲染与缓存大图

  • SSG:构建时生成,最快。适合不常变的数据。

  • ISR:revalidate 秒级回生,兼顾新鲜与速度。

  • SSR:每次请求渲染,最灵活,最贵。

  • 边缘/节点:选择 runtime: 'edge' 适合低延迟、无 Node API 场景。

  1. 客户端与服务端边界

  • "use client" 组件内不能直接 await DB/秘密变量。

  • 服务器组件不能使用浏览器 API(window、document)。

  • Props 下传:RSC → Client 可以,但 Client → RSC 不行(RSC 在更外层)。

二、易坑提示(减少踩坑时间)

  1. 缓存行为易混

  • fetch 在 RSC 内默认缓存,常见“数据不更新”的问题多因忘记设置 no-store 或 revalidate。

  • 同一 URL + 相同 options 会被合并缓存;变 query 或 headers 才算新键。

  1. 环境变量

  • 只有以 NEXT_PUBLIC_ 开头的变量会暴露到客户端。

  • 服务器端可用 .env.*,Vercel 中注意环境区分与加密。

  1. 路由冲突与优先级

  • 约定路由优先于通配,嵌套路由匹配由树结构决定。避免同级多个动态段造成歧义。

  • 记得用 not-found.tsx 处理兜底。

  1. CSS 与样式隔离

  • 全局样式仅可在 app/layout.tsx 引入。

  • Module CSS、Tailwind、CSS-in-JS 混用时注意样式优先级与 hydration 一致性。

  1. Hydration 不一致

  • 客户端组件初始渲染必须与服务端输出一致;涉及时间、随机数、窗口尺寸时做条件防护或 useEffect 里处理。

  1. 图片和字体

  • next/image 默认优化但需正确设置域名或 loader;静态资源体积大要小心首次加载。

  • next/font 避免 FOUT/FOIT,记得 subset/weight 合理配置。

三、性能与可观测(进阶必修)

  1. 分层渲染策略

  • RSC 尽可能承载数据与重组件,客户端组件仅做交互。

  • 使用 Suspense 分块并行加载,loading.tsx 提升感知速度。

  1. 缓存与再验证策略清单

  • 静态页面:export const revalidate = 3600。

  • 动态数据:fetch(url, { cache: 'no-store' })。

  • 部分块静态、部分块动态:父级静态 + 子级用 no-store 或独立 route handler。

  • 使用标签化 revalidateTag/unstable_cache 做细粒度失效。

  1. 边缘/函数平台选择

  • 低延迟读多写少:Edge Runtime + KV/缓存层。

  • 需要 Node 原生库或长时间计算:Node serverless/专用 server。

  • 数据库直连:在 Server Components 或 Server Actions 内进行,减少 API hop。

  1. 资源分割与体积控制

  • 避免给客户端传大对象与 DB 结果;在 RSC 里先裁剪。

  • 动态 import 客户端组件,配合 suspense。

  • 分析包体:next build 后用 next/script、Webpack 分析工具定位大包。

  1. 图片与流式传输

  • 图片用 fill/priority 慎用;首屏关键图可 priority。

  • 开启流式 SSR(默认)+ Suspense,表单用 Server Actions 提交可带来极佳 TTFB。

四、工程与可维护

  1. 目录与约定

  • app/(marketing)/、app/(app)/ 用分组路由管理布局与权限。

  • 复用 UI 放在 components;仅客户端交互的组件专用 clients/ 目录,顶部统一 "use client"。

  1. 类型与安全

  • zod/superstruct 校验入参;边界尽量在 Server Actions 或 route handler。

  • TypeScript 严格模式。公共类型放 @types 或 lib/types。

  1. 错误与边界

  • error.tsx 捕获局部错误,global-error.tsx 捕获全局。

  • not-found.tsx 与 generateStaticParams 配合,防止 404/空状态尴尬。

  1. 日志与监控

  • 接入 Vercel Analytics/Speed Insights 或 OpenTelemetry。

  • 关键路径(登录、结算)加上 RUM 事件与后端 tracing。

五、迁移与团队协作

  • 从 pages/ 向 app/ 迁移时,先把数据获取移到服务端(RSC),再切页面结构与 layout。

  • 规范“何时 use client”:有状态/事件/浏览器 API/第三方 UI 需在客户端,其余尽量保持服务端。

六、快速决策树(记住这几句就够)

  • 需要交互?组件标记 use client,否则用服务端组件。

  • 数据是否可缓存?能缓存就设 revalidate,不能就 no-store。

  • 是否需要 API?只是表单/按钮触发后端逻辑,用 Server Actions;需要被多端复用或外部调用,再做 API 路由。

  • 是否追求首屏快?RSC + Suspense 分块 + 图片/字体优化 + 动态 import 客户端组件。

  • 是否需要边缘?读多写少、低延迟优先边缘;复杂 Node 依赖留在 Node runtime。

七、常用片段(记下来)

  • 强制动态
    export const dynamic = 'force-dynamic'
    // 或在 fetch 中用 { cache: 'no-store' }

  • ISR 页面
    export const revalidate = 300

  • 带标签的缓存与失效
    const data = await fetch(url, { next: { tags: ['post:1'] } }).then(r => r.json())
    // 变更后触发
    // await revalidateTag('post:1')

  • Server Action
    'use server'
    export async function save(formData: FormData) { /* ... */ }

  • 路由处理器
    export async function GET(req: Request) { return Response.json({ ok: true }) }

把上述当成“操作手册 + 速查表”。先把页面尽量写成 Server Components,遇到交互再下沉到客户端组件,数据默认走缓存,有变化再指定动态,这样心智负担会小很多。


评论