Next.js 速查表(2025 App Router):API、配置与决策矩阵

Next.js 15 App Router 开发者速查表:文件路由约定、渲染模式决策树、fetch/cache 选项、Server Actions 语法、Metadata API、Error 处理、Middleware、Config 配置等高频 API 与最佳实践,一页纸可即查即用。

本文是《Next.js 完全指南》系列的速查参考页,适合在开发时快速定位 API 用法和配置选项。如需深入理解,请参考对应章节。


一、文件路由约定

app/
├── page.tsx              # / 路由页面
├── layout.tsx            # 根布局(包裹所有子路由)
├── loading.tsx           # 加载状态(Suspense fallback)
├── error.tsx             # 错误边界
├── not-found.tsx         # 404 页面
├── template.tsx          # 类似 layout 但每次导航重新挂载
├── route.ts              # API Route(GET/POST/...)
├── default.tsx           # 并行路由默认 fallback
├── global-error.tsx      # 全局错误处理(根 layout error)
│
├── blog/
│   ├── page.tsx          # /blog
│   ├── layout.tsx        # /blog/* 的布局
│   └── [slug]/
│       ├── page.tsx      # /blog/:slug
│       ├── loading.tsx
│       └── error.tsx
│
├── (marketing)/          # Route Group(无 URL 前缀)
│   ├── about/
│   │   └── page.tsx      # /about
│   └── pricing/
│       └── page.tsx      # /pricing
│
├── [locale]/             # 动态段:/en, /zh
│   └── [...catchAll]/    # 捕获所有:/anything/here
│       └── page.tsx
文件作用域备注
page.tsx当前路由唯一必需文件
layout.tsx当前路由及子路由导航时保持状态
template.tsx当前路由及子路由导航时重新挂载
loading.tsx当前路由及子路由Suspense fallback
error.tsx当前路由及子路由错误边界
not-found.tsx当前路由404 或 notFound() 触发
route.ts当前路由API endpoint

二、渲染模式决策树

页面需要动态数据?
  ├── 否 → 数据是否可能变化?
  │       ├── 否 → export const dynamicParams = false;
  │       │        # SSG(构建时生成,永不更新)
  │       └── 是 → export const revalidate = 3600;
  │                # ISR(增量静态再生)
  └── 是 → 数据是否用户相关/实时?
          ├── 是 → export const dynamic = 'force-dynamic';
          │        # SSR(每次请求实时渲染)
          └── 否 → export const revalidate = 60;
                   # ISR(定期更新 + 按需失效)

需要流式加载?
  └── 部分组件用 Suspense + loading.tsx
      # Streaming(渐进式渲染,先骨架后内容)

只有前端交互?
  └── page.tsx 中不需要 async
      # CSR(纯客户端渲染,"use client" 组件)
模式首次访问缓存数据新鲜度适用配置
SSG构建时CDN 永久永不更新法律页/关于默认(无 async fetch)
ISR首次访问生成CDN + TTLTTL 后后台更新博客/商品页revalidate = N
SSR每次请求无最新用户中心/搜索dynamic = 'force-dynamic'
Streaming渐进式部分缓存流内实时大数据量页Suspense + loading.tsx
CSR客户端渲染浏览器客户端实时后台管理"use client"

三、fetch 与缓存

// 默认行为(Next.js 15):请求记忆 + 数据缓存
fetch('https://api.example.com/data');

// 完全禁用缓存(类似 SSR)
fetch('https://api.example.com/data', { cache: 'no-store' });

// ISR:缓存 1 小时后后台刷新
fetch('https://api.example.com/data', { next: { revalidate: 3600 } });

// 带标签缓存(支持按需失效)
fetch('https://api.example.com/posts', { next: { tags: ['posts'] } });

// 永久缓存(类似 SSG)
fetch('https://api.example.com/static', { cache: 'force-cache' });
// 页面级别缓存控制
export const revalidate = 60;           // ISR 60 秒
export const dynamic = 'force-dynamic';  // SSR(无缓存)
export const dynamicParams = false;      // 只预生成已知路径

四、Server Actions

// 定义 Server Action(可直接绑定到 <form>)
async function createPost(formData: FormData) {
  'use server';
  const title = formData.get('title') as string;
  await prisma.post.create({ data: { title } });
  revalidatePath('/posts');
}

// 带错误处理和返回值的 Server Action
async function submitContact(formData: FormData) {
  'use server';
  const email = formData.get('email') as string;

  if (!email.includes('@')) {
    return { error: 'Invalid email' };
  }

  try {
    await sendEmail(email);
    return { success: true };
  } catch {
    return { error: 'Failed to send' };
  }
}
// 前端:用 useFormState 处理返回
'use client';
import { useFormState } from 'react-dom';

export default function ContactForm() {
  const [state, formAction] = useFormState(submitContact, { error: null });

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <button type="submit">Send</button>
      {state?.error && <p>{state.error}</p>}
      {state?.success && <p>Sent!</p>}
    </form>
  );
}
// useOptimistic:乐观更新
const [optimisticState, addOptimistic] = useOptimistic(
  initialState,
  (current, newItem) => [...current, newItem]
);

async function handleSubmit(formData: FormData) {
  addOptimistic({ id: 'temp', title: formData.get('title') });
  await serverAction(formData);
}

五、Metadata API

// page.tsx / layout.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description for SEO',

  openGraph: {
    title: 'My Page',
    description: 'OG description',
    images: ['/og-image.jpg'],
  },

  twitter: {
    card: 'summary_large_image',
    title: 'My Page',
    images: ['/twitter-image.jpg'],
  },

  robots: {
    index: true,
    follow: true,
  },

  alternates: {
    canonical: 'https://example.com/page',
    languages: {
      'en-US': '/en/page',
      'zh-CN': '/zh/page',
    },
  },
};
// 动态 Metadata(基于路由参数)
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
  };
}

六、Error 处理

// app/error.tsx(局部错误边界)
'use client';

export default function Error({ error, reset }: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}
// app/global-error.tsx(全局错误)
'use client';

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <h2>Global Error</h2>
        <button onClick={reset}>Reload</button>
      </body>
    </html>
  );
}
// 主动抛出 404
import { notFound } from 'next/navigation';

export default async function Page({ params }) {
  const post = await getPost(params.slug);
  if (!post) notFound(); // 渲染 not-found.tsx
  return <Post post={post} />;
}

七、Middleware

// middleware.ts(项目根目录)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 认证检查
  const token = request.cookies.get('token')?.value;
  if (!token && pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // i18n 重定向
  if (!pathname.startsWith('/en') && !pathname.startsWith('/zh')) {
    return NextResponse.redirect(new URL('/en' + pathname, request.url));
  }

  // URL 重写(URL 不变,内部路由改变)
  if (pathname === '/docs') {
    return NextResponse.rewrite(new URL('/documentation', request.url));
  }

  // 注入请求头
  const headers = new Headers(request.headers);
  headers.set('x-user-country', request.geo?.country || 'US');
  return NextResponse.next({ request: { headers } });
}

// 匹配规则
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

八、next.config.js / next.config.ts

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // 独立运行模式(Docker 必需)
  output: 'standalone',

  // 图片配置
  images: {
    domains: ['cdn.example.com'],
    remotePatterns: [
      { protocol: 'https', hostname: '**.example.com' },
    ],
    formats: ['image/avif', 'image/webp'],
  },

  // 重写规则
  async rewrites() {
    return [
      { source: '/old-path', destination: '/new-path' },
      { source: '/api/:path*', destination: 'https://api.example.com/:path*' },
    ];
  },

  // 重定向
  async redirects() {
    return [
      { source: '/about-us', destination: '/about', permanent: true },
    ];
  },

  // 请求头
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
        ],
      },
    ];
  },

  // TypeScript/ESLint 构建时检查(临时禁用)
  typescript: { ignoreBuildErrors: false },
  eslint: { ignoreDuringBuilds: false },

  // 实验性功能
  experimental: {
    ppr: true,              // 部分预渲染
    dynamicIO: true,        // 动态 IO(Next.js 15+)
    after: true,            // after() API
  },
};

export default nextConfig;

九、Server/Client 组件边界速查

能力Server ComponentClient Component
async/await✅❌(需用 useEffect + useState)
访问数据库/ORM✅❌(通过 API 或 props)
访问 fs / path✅❌
headers() / cookies()✅❌(通过 API)
useState / useEffect❌✅
onClick / onChange❌✅
window / document❌✅
localStorage❌✅
第三方库(Framer Motion)部分✅✅

十、CLI 命令速查

# 创建项目
npx create-next-app@latest my-app --typescript --tailwind --app

# 开发
npm run dev          # 启动开发服务器(默认 Turbopack)
npm run dev --turbo  # 显式启用 Turbopack

# 构建
npm run build        # 生产构建
npm start            # 启动生产服务器(需先 build)

# 分析 Bundle
npm install @next/bundle-analyzer
# next.config.js: ANALYZE=true npm run build

# 类型检查
npx tsc --noEmit

十一、并行路由与拦截路由(Parallel & Intercepting Routes)

并行路由

通过 @folder 约定,将多个页面渲染到同一布局的不同插槽(slot)中。

dashboard/
├── @analytics/
│   ├── page.tsx        # /dashboard 中展示分析面板
│   └── loading.tsx
├── @revenue/
│   ├── page.tsx        # 同时展示收入面板
│   └── error.tsx
├── layout.tsx          # 接收 { children, analytics, revenue }
└── page.tsx            # children 插槽(默认页面)
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  analytics,
  revenue,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  revenue: React.ReactNode;
}) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div>{analytics}</div>
      <div>{revenue}</div>
      <div className="col-span-2">{children}</div>
    </div>
  );
}

每个 @slot 独立加载和报错,互不影响,default.tsx 为未匹配到子路径时的回退画面。

拦截路由

通过 (.)、(..)、(..)(..)、(...) 前缀实现局部路由拦截,常用于模态框和抽屉面板。

feed/
├── page.tsx            # /feed 列表页(常规渲染)
└── @modal/
    └── (.)photo/[id]/  # 拦截 /feed → /feed/photo/123(模态框渲染)
        └── page.tsx    # 展开图片时以模态框呈现

直接访问 /feed/photo/123(如通过 URL 或刷新)则仍以全页面渲染(不拦截)。拦截路由与并行路由配合,是构建沉浸式导航体验的核心模式。

十二、Route Handler 进阶

route.ts 支持完整的 Web API:中间件、Request 流、Response 流、WebSocket 升级。

// app/api/stream/route.ts
export async function GET() {
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    start(controller) {
      let count = 0;
      const interval = setInterval(() => {
        controller.enqueue(encoder.encode(`data: ${++count}\n\n`));
        if (count >= 5) {
          clearInterval(interval);
          controller.close();
        }
      }, 1000);
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  });
}

// 支持多种 HTTP 方法的统一文件
export async function POST(request: Request) {
  const body = await request.json();
  // ...处理逻辑
  return Response.json({ id: 1, ...body }, { status: 201 });
}

export async function PUT(request: Request) { /* ... */ }
export async function DELETE(request: Request) { /* ... */ }
特性Server ComponentRoute Handler
访问 request body❌✅ await request.json()
流式响应 (SSE)❌✅ ReadableStream
文件上传间接✅ FormData / multipart
第三方 API 代理间接✅ 带 CORS 头的透明代理

Route Handler 是 Server Component 无法直接处理的 HTTP 交互场景的互补方案。

十三、四层缓存详解与失效策略

Next.js 15 的缓存体系远比 fetch 的 cache 选项复杂。理解四层缓存有助于排查数据不更新、重复渲染等问题:

┌─────────────────────────────────────────┐
│  Layer 4: Full Route Cache              │  → CDN / Edge 缓存 HTML
│  (Next.js 渲染结果,ISR/SSG output)      │
├─────────────────────────────────────────┤
│  Layer 3: Router Cache                  │  → 客户端导航缓存
│  (浏览器内存,保留最近访问的 RSC payload) │
├─────────────────────────────────────────┤
│  Layer 2: Data Cache                    │  → fetch 数据缓存
│  (Next.js 服务端,按 URL+options 键值)   │
├─────────────────────────────────────────┤
│  Layer 1: Request Memoization             │  → React 请求去重
│  (组件树内,同渲染周期相同 fetch 只执行一次)│
└─────────────────────────────────────────┘
层级控制方式默认行为手动失效
Request MemoizationReact.cache()渲染周期自动去重无法手动清除
Data Cachefetch() 的 cache/revalidate/tags自动缓存外部 fetchrevalidateTag() / revalidatePath()
Router Cache客户端导航行为30s 软过期router.refresh() / router.push()
Full Route Cacherevalidate / dynamic静态路由启用revalidatePath() / 重新部署

手动按需失效(Cache Invalidation)是 ISR 场景的关键能力:

// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';

export async function POST(request: Request) {
  const { slug } = await request.json();

  // 方式 1:按标签失效(fine-grained)
  revalidateTag(`post-${slug}`);

  // 方式 2:按路径失效(coarse-grained)
  revalidatePath(`/blog/${slug}`);

  return Response.json({ revalidated: true, now: Date.now() });
}

生产环境中,推荐 CMS/Webhook 触发 "POST /api/revalidate" 实现内容更新秒级推送。

十四、Image 组件优化与 Core Web Vitals

Next.js 的 <Image> 组件自动处理图片优化、懒加载、尺寸调整:

import Image from 'next/image';

// 使用外部图片需先在 next.config.js 中配置 domains/remotePatterns
<Image
  src="https://cdn.example.com/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority        // 首屏图片:preload,跳过懒加载
  quality={85}    // 默认 75,可调整
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
  sizes="(max-width: 768px) 100vw, 50vw"
/>
CWV 指标Image 优化手段目标值
LCP (最大内容渲染)首屏 priority + placeholder + WebP 自动转换< 2.5s
CLS (累积布局偏移)必须提供 width/height 或使用 fill + sizes< 0.1
INP (交互延迟)懒加载非首屏图片,减少主线程阻塞< 200ms

十五、i18n 路由与国际化

App Router 使用中间件实现多语言路由:

// middleware.ts —— 语言路由 + 内容协商
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const locales = ['en', 'zh', 'ja'];
const defaultLocale = 'en';

export function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;
  const pathnameLocale = locales.find((l) => pathname.startsWith(`/${l}`));

  if (!pathnameLocale) {
    // 根据 Accept-Language 或 Cookie 推断语言
    const acceptLang = request.headers.get('accept-language')?.split(',')[0]?.split('-')[0];
    const locale = locales.includes(acceptLang) ? acceptLang : defaultLocale;
    return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next|\\..*).*)'],
};

配合 next-intl 或 react-i18next 管理翻译文件,推荐目录结构:

app/
├── [locale]/
│   ├── layout.tsx      # 根据 locale 注入方向(dir)和元数据
│   ├── page.tsx
│   └── about/
│       └── page.tsx
└── i18n/
    ├── en.ts
    └── zh.ts

十六、Edge Runtime vs Node.js Runtime

Next.js 15 支持三种运行时,根据业务需求选择:

运行时位置特性限制适用场景
Node.js自建服务器 / Docker完整 Node 生态(fs、db driver)冷启动慢复杂业务逻辑、数据库直连
EdgeVercel Edge Network全球低延迟 (<50ms)、冷启动快无 Node API,30s 超时边缘认证、A/B 测试、轻量代理
ServerlessVercel Functions自动扩缩容10s (Hobby) / 60s (Pro) 超时常规 API 路由
// 在 Route Handler 中指定运行时
export const runtime = 'edge';  // 'nodejs' | 'edge'
export const preferredRegion = 'iad1';  // 强制部署到华盛顿地区

Auth.js (NextAuth v5) 的 Edge 兼容配置:

import NextAuth from 'next-auth';
import { authConfig } from './auth.config'; // 不含 Prisma 等 Node-only 依赖

export const { auth, signIn, signOut, handlers } = NextAuth(authConfig);

将数据库查询逻辑放在 auth() 之外的 Server Component 中使用,保留 Edge 运行时的低延迟优势。

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章

  1. GraphQL BFF 与微前端:多前端团队的 Schema 分片与协作模式
  2. 前端 GraphQL 客户端集成:Apollo Client、Relay 与 urql 选型
  3. 前端性能调试实战:从首屏加载到运行时瓶颈