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

相关阅读

继续阅读

探索更多技术文章

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

全部文章 返回首页

「frontend」更多文章