Next.js 速查表 - Next.js 框架常用方法大全

面向用 React 搭全栈、需要决定页面用 SSR 还是 SSG 的开发者。Next.js 的核心不是列 API,而是把"谁在服务端渲染、谁在客户端交互、数据何时失效"理顺——服务端组件不能直接加 onClick,ISR 会缓存旧数据影响时效。读完能搭出实用的 App Router 页面与布局,区分客户端/服务端组件的边界,理解静态生成与 ISR 的取舍,并处理中间件与数据获取。

Web 服务·共 43 条命令·最后更新 2026-07-21
nextjsreactssrssg前端

典型使用场景

面向正在 App Router 上把一个站点从「本地跑得通」推到「线上行为可预期」的工程师。Next.js 最贵的成本不是写页面,而是渲染边界没划清:哪些数据在构建期取、哪些在请求期取、哪些在客户端取。本表覆盖 App Router 的约定式文件(page/layout/loading/error/not-found)、路由组与拦截式路由、数据获取的缓存语义(force-cache / no-store / revalidate)、渲染策略开关(dynamic / revalidate / runtime)、Route Handler 与中间件、以及 metadata 与 next/image、next/font 这套内建优化。适合在 code review 与「为什么这个页面每次请求都变慢」排查时对照检查一遍。

App Router 基础 5

app/page.tsx
页面组件,默认是服务端组件(Server Component)
app/layout.tsx
根布局或嵌套布局,包裹子页面
app/loading.tsx
加载状态 UI,配合 Suspense 自动显示
app/error.tsx
错误边界 UI,捕获子组件运行时错误
app/not-found.tsx
404 页面,找不到路由时显示

路由与导航 6

useRouter()
客户端路由跳转 Hook,来自 next/navigation
<Link href="/about">
客户端导航组件,自动预取可见链接
usePathname()
获取当前路径名,客户端路由监听用
useSearchParams()
获取 URL 查询参数 Hook
app/blog/[slug]/page.tsx
动态路由,[slug] 是参数占位符
app/(marketing)/about/page.tsx
路由组,不影响 URL 路径

数据获取 6

fetch(url, { cache: "force-cache" })
静态数据获取,默认缓存策略
fetch(url, { cache: "no-store" })
动态数据获取,每次请求重新拉取
fetch(url, { next: { revalidate: 60 } })
ISR 增量静态生成,60 秒后重新验证
generateStaticParams()
静态生成动态路由的路径参数列表
"use server"
标记服务端 Action,在客户端表单中调用
server-only
确保代码只在服务端运行,不会泄漏到客户端

渲染策略 5

export const dynamic = "force-static"
强制静态渲染,构建时生成 HTML
export const dynamic = "force-dynamic"
强制动态渲染,每次请求实时生成
export const revalidate = 3600
页面级 ISR 间隔,单位秒
"use client"
标记为客户端组件,使用浏览器 API 和交互
export const runtime = "edge"
使用 Edge Runtime 运行,延迟更低

布局与模板 5

export const metadata = { title: "页面标题" }
静态元数据,支持 title/description/OG 等
export async function generateMetadata()
动态生成元数据,接收 params 和 searchParams
app/@modal/default.tsx
平行路由插槽,独立渲染区域
app/(...)/[...slug]/page.tsx
拦截路由,在当前布局中拦截外部链接
template.tsx
模板组件,类似 layout 但切换时重新挂载

API 与中间件 6

app/api/route.ts
Route Handler,创建 API 端点
export async function GET(request)
处理 GET 请求的 Route Handler
middleware.ts
中间件文件,在请求前执行路由判断
NextResponse.redirect(new URL("/login", request.url))
中间件中重定向请求
NextResponse.next()
中间件中继续处理请求
cookies().get("token")
服务端读取 Cookie

静态资源与优化 5

import Image from "next/image"
优化图片组件,支持懒加载和 WebP 自动转换
import { Inter } from "next/font/google"
Google 字体优化,自托管避免外部请求
import Script from "next/script"
脚本加载优化,支持 afterInteractive/lazyOnload 策略
export const metadata = { robots: { index: true, follow: true } }
SEO 爬虫配置
<Link href="/about" prefetch={true}>
Link 自动预取,hover 时加载页面资源

部署与配置 5

output: "standalone"
独立部署输出,包含所需依赖,适合 Docker 部署
output: "export"
静态导出为纯 HTML/CSS/JS,可部署到任何静态托管
process.env.NEXT_PUBLIC_API_URL
客户端环境变量,以 NEXT_PUBLIC_ 前缀暴露
next build
生产构建,生成优化后的代码和静态资源
next start
启动生产服务器,读取 .next 目录的构建产物

参数矩阵

参数作用示例
app/page.tsx路由 / 的唯一页面文件;文件名即 URL,一个路由只能有一个 page.tsxapp/page.tsx
app/layout.tsx布局,父级布局在切换子路由时不重渲染;必须包含 <html> 与 <body>app/layout.tsx
app/loading.tsx同级 Suspense 回退界面,路由段切换与数据等待期间自动显示app/loading.tsx
app/error.tsx捕获同级页面段的渲染错误并要求导出 client component,可拿到 reset() 重试app/error.tsx
app/not-found.tsxnotFound() 抛出的 404 页面,全局 404 放根目录即可app/not-found.tsx
app/(marketing)/about/page.tsx路由组,用括号包住且不出现在 URL 里,用来放共享布局而不污染路径app/(marketing)/about/page.tsx
app/blog/[slug]/page.tsx动态段,[slug] 由 params 传入;配合 generateStaticParams 在构建期预渲染全部条目app/blog/[slug]/page.tsx
app/(...)/[...slug]/page.tsx捕获所有段 (..)、可选段 (...) 省略号写法,用来匹配 app 之外的多层路径app/(...)/[...slug]/page.tsx
useRouter()客户端组件里拿到 router,用 router.push / replace 做导航,不能在服务端组件调用useRouter()
<Link href="/about">客户端路由跳转,默认预取后续路由的 JS 与数据,替代裸 <a> 避免整页刷新<Link href="/about">
useSearchParams()读取查询串;它会把整棵子树标为动态渲染(CSR bailout),需用 Suspense 包住useSearchParams()
fetch(url, { cache: "force-cache" })强制缓存并长期复用,等价于静态渲染该处数据fetch(url, { cache: "force-cache" })
fetch(url, { cache: "no-store" })每次请求都回源,一个请求只用一次缓存;适合个性化与实时数据fetch(url, { cache: "no-store" })
fetch(url, { next: { revalidate: 60 } })ISR 语义,缓存 60 秒后过期并在下次请求后台重取fetch(url, { next: { revalidate: 60 } })
generateStaticParams()构建期为动态路由枚举参数,返回的每个值都会被静态预渲染generateStaticParams()
"use server"把函数标记为 Server Action,可被客户端表单直接调用;内含的第三方凭据不会进包"use server"
server-only模块级导入守卫,一旦被客户端组件引用即构建报错,用于保护密钥与 DB 连接server-only
"use client"声明客户端边界,此文件及其 import 的模块进入浏览器 bundle;useState/useEffect 就属于这一侧"use client"
export const dynamic = "force-dynamic"整段强制动态渲染,每次请求都跑,适合实时页面;会关闭该段所有缓存export const dynamic = "force-dynamic"
export const dynamic = "force-static"强制静态化,忽略动态 API 并把结果固化,适合内容不变的说明页export const dynamic = "force-static"
export const revalidate = 3600页面级 ISR 间隔(秒),比逐个 fetch 设置 revalidate 更省事export const revalidate = 3600
export const runtime = "edge"切到 Edge Runtime,冷启动更低但无 Node API;用 Node 专属库会构建报错export const runtime = "edge"
export async function generateMetadata()按 params 生成动态 title/description,产出各路由的 <head> 与 OG 标签export async function generateMetadata()
app/@modal/default.tsx平行路由(Parallel Routes)的插槽目录,default.tsx 是必填的软回退app/@modal/default.tsx
template.tsx每次导航都重新挂载的模板,放进入动画;与 layout 的差别就在「会不会重挂载」template.tsx
app/api/route.tsRoute Handler 入口,按方法导出 GET/POST 等函数,取代 pages/api 写法app/api/route.ts
export async function GET(request)GET 处理函数,request 是标准 Web Request,可直接用 new URL(request.url) 取查询串export async function GET(request)
middleware.ts在根目录(或 src)定义,匹配器决定拦截范围,用于鉴权与重写middleware.ts
NextResponse.redirect(new URL("/login", request.url))服务端重定向,origin 必须由 request.url 派生,不能写绝对域名(多域名部署会错跳)NextResponse.redirect(new URL("/login", request.url))
NextResponse.next()放行继续执行,不写这一行 middleware 默认也是继续但显式写出更清楚NextResponse.next()
cookies().get("token")在服务端组件/路由处理器读 Cookie;一旦读取即让该路由变为动态渲染cookies().get("token")
import Image from "next/image"内建图片组件,自动生成 srcset/尺寸并默认走优化管线;忘记给宽高会报尺寸缺失警告import Image from "next/image"
import { Inter } from "next/font/google"构建期自托管字体,消除外部请求与 CLS;网络受限时构建会失败import { Inter } from "next/font/google"
output: "standalone"构建产物自带精简 node_modules 的独立服务目录,适合打进 Docker 镜像output: "standalone"
output: "export"纯静态导出,只能用于完全静态的站点;任何 Route Handler、ISR、动态路由都会失败output: "export"
process.env.NEXT_PUBLIC_API_URLNEXT_PUBLIC_ 前缀的变量被打进客户端 bundle;改动必须重新构建且会暴露给所有人process.env.NEXT_PUBLIC_API_URL
next build / next start构建并以生产模式启动;开发模式与生产模式的缓存与路由行为并不一致,判断线上问题必须用 next startnext build

易错点与避坑指南

现象服务端组件里调用 useRouter / useState,报 "useState only works in a Client Component"。

原因App Router 默认为服务端组件,只有加了 "use client" 的文件及其 import 链才有这些 Hook。

处置把需要交互的最小叶子组件单独抽成一个文件并顶部写 "use client",不要在整个 layout 上加,否则整棵子树都会进 bundle。

现象页面首屏 HTML 里数据是空的,页面加载后才闪现出真实内容(hydration mismatch 警告)。

原因服务端渲染时取了缓存值、客户端又取了实时值,两侧输出不一致;常见于直接读 localStorage 或随机数。

处置把不一致的部分放进 useEffect 里设置状态,或对该组件加 "use client" 并让服务端与客户端输出一致。

现象明明用了 fetch 缓存,接口请求量还是等于访问量。

原因fetch 缓存只作用于服务端渲染与静态生成;请求里出现 cookies()、headers()、searchParams 等动态 API 会让整段变成动态渲染,缓存全部失效。

处置把数据获取放进单独的静态子段(不带动态 API),在页面层用 Suspense + 子组件消费;或确认 dynamic 未被设为 force-dynamic。

现象在 page.tsx 里直接用 useSearchParams(),构建时整页都变动态并抛 Suspense 边界缺失错误。

原因searchParams 是动态 API,读取它会让该路由段 opt out 静态渲染;没有 Suspense 包裹时 Next 无法确定流式边界。

处置把读取 searchParams 的逻辑单独放进一个客户端子组件,外层用 <Suspense> 包住。

现象NextResponse.redirect(new URL("https://example.com/login", request.url)) 在预发环境把用户跳到生产域名。

原因重定向 URL 写死了绝对域名,忽略了两层:请求的 origin 与部署域名可能不同(如预发子域)。

处置一律用 new URL("/login", request.url) 相对构造,让 Next 从当前请求派生 origin。

现象把 API 密钥写进客户端组件能读到的变量里,密钥出现在浏览器 Network 里。

原因NEXT_PUBLIC_ 前缀的变量与未加前缀但在客户端组件中 import 的模块都会被内联进 bundle。

处置密钥只放服务端:用 server-only 保护模块,把调用放进 Route Handler 或 Server Action。

现象export const runtime = "edge" 后构建报某个 Node 库在 Edge Runtime 下不可用。

原因Edge Runtime 只有 Web API,没有 fs、net、部分 npm 原生模块。

处置改回默认 Node runtime;确需 Edge 时把重依赖留在服务端并通过 Route Handler 调用。

现象next build 通过、next dev 正常,但 next start 后页面 404。

原因文件写在错误的目录层级(例如放在 pages/ 而非 app/,或 app 目录未位于根或 src 下),dev 与 build 的路由解析差异让问题被掩盖。

处置核对目录位置与文件名;用 next build 的路由表确认路径是否被收录。

现象部署到 CDN 后页面一直拿到旧数据,revalidate 似乎没生效。

原因CDN 层的缓存头优先于 Next 的 ISR,或 output: "export" 下完全没有 ISR,revalidate 被忽略。

处置检查响应头 Cache-Control 与 CDN 规则;静态导出场景改用构建期取数或改回标准部署。

排障路径

  1. 1用生产模式复现线上问题

    next build && next start

    next dev 不做完整优化且路由行为不同,判断缓存与构建错误必须用 next start。

  2. 2查看构建产物的路由与渲染模式

    next build

    输出里每个路由会标注 Static / Dynamic / ISR 以及大小与首次加载 JS。

  3. 3确认客户端变量的取值是否进了 bundle

    grep -r "NEXT_PUBLIC" .next/static

    在 .next/static 里搜到明文即说明该变量被内联进客户端产物。

  4. 4排查静态导出是否可行

    grep -r "export const dynamic" app

    任何 Route Handler、ISR 或 force-dynamic 段都会让 output: "export" 构建失败。

  5. 5检查中间件实际拦截了哪些路径

    grep -n "matcher" middleware.ts

    matcher 配置过宽会让每个请求都过一遍中间件,是性能问题的常见来源。

  6. 6核对约定式文件是否被路由识别

    ls -R app

    确认 page.tsx / layout.tsx 的文件名与层级,拼写错误(如 page.ts)不会报错只是没有路由。

命令示例

Route Handler 处理 GET 请求

// app/api/items/route.ts import { NextResponse } from "next/server"; export async function GET() { const res = await fetch("https://api.example.com/items", { cache: "no-store" }); return NextResponse.json(await res.json()); }

Route Handler 里导出的函数名就是 HTTP 方法名,没有前缀路径。no-store 让每次请求都真实回源,否则结果会被缓存成一份陈旧数据。返回 NextResponse.json(...) 而不是裸对象,响应头和状态码才可控。

ISR:60 秒后后台重新验证

// app/products/[id]/page.tsx export const revalidate = 60; export async function generateStaticParams() { const ids = await getIds(); return ids.map((id) => ({ id: String(id) })); }

revalidate = 60 让页面 60 秒后过期,并在下次有人访问时后台重新生成——首个请求仍是旧内容但立刻返回,SEO 与首屏速度都保住。generateStaticParams 决定构建期预渲染哪些动态路由路径,少写它这些页面会退化成首次访问才生成。

中间件拦截未登录请求

// middleware.ts import { NextResponse } from "next/server"; export function middleware(request) { const token = request.cookies.get("token"); if (!token) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); }

中间件运行在请求到达路由之前,是做鉴权重定向的最早切入点。注意 NextResponse.redirect(new URL(..., request.url)) 必须基于完整 request.url 才能解析出正确的绝对地址,只写 "/login" 会抛错。

客户端组件里用 Hook 做导航

"use client"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; export function Nav() { const router = useRouter(); const pathname = usePathname(); const params = useSearchParams(); return <button onClick={() => router.push("/about")}>{pathname}</button>; }

next/navigation 的三个 Hook 只能在客户端组件里调用,服务端组件用它们会直接报错。useSearchParams 会让该组件进入客户端渲染边界,静态导出的页面里读取它需要额外的 Suspense 包裹。

metadata 与动态 generateMetadata

export const metadata = { title: "页面标题", description: "页面描述", robots: { index: true, follow: true }, }; export async function generateMetadata({ params }) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; }

静态 metadata 适合固定页面,动态路由必须用 generateMetadata 并接收 params 取出内容填 title/description——否则所有详情页会共用一个标题,SEO 上是硬伤。两处不要同时写,同一文件里 generateMetadata 会覆盖静态项。

常见坑与注意事项

  • 「use client」是文件级的,一旦加上,该文件里导入的所有模块都会被打进客户端 bundle。把只在服务端用的数据库/密钥相关工具 import 进来,等于把代码和依赖一起发给浏览器。
  • 同一文件里混用 process.env 与 import.meta.env 这类构建期常量访问:构建工具会把源码里的这些访问替换成字面量,客户端与服务端拿到的值不同就会 hydration mismatch,页面结构在刷新瞬间跳变。环境变量请只走 Next 的约定方式并在服务端读取。
  • 忘记 server-only 标记:只靠目录约定来保证「这段代码只在服务端跑」,一次 import 路径写错就可能把密钥拼接逻辑打进客户端产物。用 server-only 包在模块顶部,让打包器直接报错。
  • 把 fetch 的缓存策略想当然:默认缓存可能让你看到过期数据,no-store 又会让每个请求都打上游、账单和延迟一起上升。数据变更频繁用 no-store 或 revalidate 明确声明,不要靠猜。
  • middleware.ts 里做重逻辑(查库、调第三方 API)会拖慢每一条经过它的请求,而 Edge Runtime 还不支持所有 Node API,报错往往指向第三方库。middleware 只做轻量判断,重活交给路由或 Server Action。
  • output: "export" 静态导出后,任何依赖服务端运行时的东西都会构建失败或运行时为空:Route Handler、middleware、服务端数据获取、动态路由的 on-demand 生成。选静态导出前先确认页面确实能纯静态产出。

提示

  • App Router 是 Next.js 13+ 默认路由方案,取代了 Pages Router
  • 页面组件默认是服务端组件(Server Component),需要客户端交互时加 "use client"
  • 使用 next/link 的 Link 组件代替 a 标签,自动预取和客户端导航
  • 数据获取推荐在服务端组件中直接 async/await fetch,无需 useEffect
  • 使用 generateStaticParams 实现静态生成动态路由

常见问题

页面组件报 "use client" 相关的错误,该在哪个文件加?

App Router 下 app/page.tsx 默认是服务端组件,只有需要浏览器 API、事件处理或状态时才加 "use client",把它及其所有导入树都切换为客户端组件。判断标准很简单:组件里出现 onClick、useState、useEffect 这类只能在浏览器工作的东西,就自底向上地把最小的那一层(往往是一棵叶子组件)标记为客户端,而不是在顶层页面文件一把梭加上。服务端组件里的数据获取不需要 useEffect,直接在组件中 async/await fetch 即可。另一个方向的选择是 server-only:它确保某段代码只在服务端运行,避免被误打包进客户端导致密钥泄漏。

静态生成、ISR 和动态渲染该怎么按需求选?

三条路线对应三类数据。fetch(url, { cache: "force-cache" }) 是静态数据获取,默认构建时就渲染成 HTML,适合内容不常变的页面。fetch(url, { next: { revalidate: 60 } }) 是 ISR,60 秒后后台重新验证并刷新页面,兼顾速度与新鲜度。fetch(url, { cache: "no-store" }) 是动态,每次请求都重新拉取,适合个性化或实时数据。页面级可以导出 const dynamic = "force-static" 或 "force-dynamic" 统一锁定策略,也可以导出 const revalidate = 3600 设定整页的 ISR 间隔。动态路由的静态化还需要 generateStaticParams() 提供要生成的路径列表。

form 提交要改数据,为什么优先用 Server Action 而不是 Route Handler?

因为这是组件内的一等能力:在函数顶部写 "use server" 就把它标记为服务端 Action,可以直接在客户端表单的 action 属性里调用,省掉自己写 fetch 和状态管理。Route Handler(app/api/route.ts 中导出 async function GET(request))面向的是外部消费方——需要被其它程序按 HTTP 语义调用、需要自定义请求方法或响应结构时用它。权限与安全上,server-only 能确保密钥只留在服务端;读取会话用 cookies().get("token") 在服务端完成,不要把 token 传给客户端再回传。

什么场景该用 middleware.ts 而不是每个页面各写判断?

middleware.ts 在请求抵达路由之前执行,适合做全局、统一的横切逻辑:登录校验、重定向、请求头改写。用 NextResponse.redirect(new URL("/login", request.url)) 把未登录用户送走,需要放行时调用 NextResponse.next()。写在页面里重复实现的问题是容易漏——新增一个路由就可能忘了加判断,而且逻辑散落各处难以统一调整。middleware 里拿得到 request 对象,适合读路径和 cookie 做决策;但它跑在服务端到路由之间,不要在里面做重量级数据查询,否则每个请求都要付这份开销,重活应放在页面或 Route Handler 里。

静态导出和 standalone 部署,配置文件里怎么写?

看部署目标。output: "export" 把应用导出为纯 HTML/CSS/JS,可以放到任何静态托管上,但代价是服务端能力(Route Handler、Server Action、ISR 重新验证)全部失效,页面必须能在构建期确定。需要 Node 运行时且希望部署产物自包含时用 output: "standalone",它会连同所需的 node_modules 片段一起输出,非常适合打进 Docker 镜像。本地验证生产行为用 next build 生成 .next 目录、再 next start 启动服务器读取该目录,而不是用 next dev。跨环境地址用 ${PROCESS_ENV}.NEXT_PUBLIC_API_URL 暴露给客户端,注意 NEXT_PUBLIC_ 前缀的变量会被内联进产物。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们