DevToolBox免费
博客

Next.js 深度指南 2026:App Router、服务端组件与生产模式

16 分钟阅读作者 DevToolBox

TL;DR — 核心要点

Next.js 15 App Router 是新项目的推荐方案。默认使用服务端组件,只在需要交互时添加 "use client"。Server Actions 替代 API 路由处理数据变更。结合 revalidatePath 的 ISR 兼顾静态与动态的优势。

关键要点

  • App Router 默认使用 React 服务端组件,减少客户端 JS,提升性能
  • 基于文件的路由系统:layout.tsx、loading.tsx、error.tsx 处理不同 UI 状态
  • 数据获取直接在组件中使用 async/await,通过 fetch 选项控制缓存策略
  • Server Actions 简化表单处理和数据变更,无需手写 API 路由
  • next/image 和 next/font 自动优化媒体资源,提升 Core Web Vitals
  • 中间件在 Edge Runtime 运行,适合身份验证和 A/B 测试
  • standalone 输出模式支持 Docker 自托管部署

1. App Router vs Pages Router:如何选择

Next.js 13 引入了 App Router,这是一种基于 React Server Components 构建的新路由范式。它与原有的 Pages Router 并存,允许逐步迁移。理解两者的差异对于做出正确的架构决策至关重要。

特性App Router (推荐)Pages Router (旧版)
默认渲染React Server Components客户端组件
数据获取async/await + fetch cachegetStaticProps / getServerSideProps
布局layout.tsx (嵌套)_app.tsx (全局)
加载状态loading.tsx (Suspense)手动实现
错误处理error.tsx (Error Boundary)手动实现
Server Actions原生支持需要 API 路由
流式传输内置支持不支持
推荐场景所有新项目维护现有项目

迁移路径:从 Pages Router 到 App Router

Next.js 支持在同一项目中同时使用两种路由器。迁移策略是逐页进行,从非关键路由开始。

// Directory structure showing coexistence
my-app/
  app/                    // App Router
    layout.tsx            // Root layout
    page.tsx              // Home page (migrated)
    dashboard/
      layout.tsx          // Dashboard layout
      page.tsx            // Dashboard page (migrated)
  pages/                  // Pages Router (still active)
    _app.tsx              // Global wrapper
    old-feature.tsx       // Not yet migrated
    api/
      legacy-endpoint.ts  // API route (still works)

// Migration checklist:
// 1. Move page.tsx from pages/ to app/
// 2. Replace getStaticProps with async component + fetch
// 3. Replace getServerSideProps with async Server Component
// 4. Add "use client" to components with hooks/events
// 5. Create layout.tsx for shared UI
// 6. Replace _document.tsx with root layout metadata

2. Server Components vs Client Components

React Server Components(RSC)是 App Router 的核心。理解服务端组件和客户端组件的边界对于编写高性能的 Next.js 应用至关重要。

服务端组件(默认)

服务端组件在服务器上渲染,不向客户端发送任何 JavaScript。它们可以直接访问数据库、文件系统和私密 API。

// app/dashboard/page.tsx - Server Component (no "use client")
import { db } from "@/lib/db";
import { cache } from "react";

// cache() deduplicates requests across the render tree
const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

export default async function DashboardPage() {
  // Direct database access - secure, no API needed
  const user = await getUser("user_123");
  const stats = await db.analytics.findMany({
    where: { userId: user.id },
    orderBy: { date: "desc" },
    take: 30,
  });

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      {/* Client component for interactivity */}
      <StatsChart data={stats} />
    </div>
  );
}

客户端组件("use client")

"use client" 指令将组件及其子树标记为客户端渲染。只在真正需要浏览器能力时使用它。

"use client";

// Must use "use client" when:
// - Using useState, useEffect, useRef, useContext
// - Adding event listeners (onClick, onChange, onSubmit)
// - Using browser APIs (window, localStorage, navigator)
// - Using third-party client-side libraries

import { useState, useEffect } from "react";

interface StatsChartProps {
  data: { date: string; value: number }[];
}

export function StatsChart({ data }: StatsChartProps) {
  const [activeIndex, setActiveIndex] = useState(0);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    // Animate on mount - requires browser
    setIsVisible(true);
  }, []);

  return (
    <div
      style={{ opacity: isVisible ? 1 : 0, transition: "opacity 0.3s" }}
    >
      {data.map((point, i) => (
        <div
          key={i}
          onClick={() => setActiveIndex(i)}
          style={{
            background: i === activeIndex ? "#6366f1" : "#e2e8f0",
            cursor: "pointer",
          }}
        >
          {point.value}
        </div>
      ))}
    </div>
  );
}

Component Boundary 规则

"use client" 形成一个边界:该文件中的所有内容以及它导入的所有内容都变为客户端代码。将客户端组件放在组件树的叶子节点,保持服务端组件处理数据获取。

3. 文件系统路由:布局、模板与特殊文件

App Router 通过 app 目录中的文件和文件夹名称定义路由。特殊文件名约定控制每个路由段的行为。

app/
  layout.tsx          // Required: root layout, wraps all pages
  page.tsx            // Route: /
  loading.tsx         // Automatic Suspense boundary
  error.tsx           // Error boundary for this segment
  not-found.tsx       // 404 for this segment
  template.tsx        // Like layout but re-mounts on nav
  global-error.tsx    // Root error boundary
  blog/
    layout.tsx        // Shared blog layout
    page.tsx          // Route: /blog
    [slug]/
      page.tsx        // Route: /blog/:slug
      opengraph-image.tsx  // OG image generation
  (marketing)/        // Route group - no URL segment
    about/
      page.tsx        // Route: /about
    pricing/
      page.tsx        // Route: /pricing
  @modal/             // Parallel route slot
    (.)photo/[id]/    // Intercepted route
      page.tsx

Root Layout 必须包含的内容

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: {
    template: "%s | My App",
    default: "My App",
  },
  description: "My awesome application",
  openGraph: {
    type: "website",
    siteName: "My App",
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

loading.tsx 和 Suspense 流式传输

loading.tsx 自动将页面包裹在 React Suspense 中。服务器会立即发送 HTML 外壳,然后流式传输内容块。

// app/dashboard/loading.tsx
// Shown instantly while dashboard/page.tsx loads
export default function DashboardLoading() {
  return (
    <div style={{ padding: "2rem" }}>
      <div style={{
        height: "2rem",
        width: "40%",
        background: "#e2e8f0",
        borderRadius: "0.25rem",
        animation: "pulse 2s infinite"
      }} />
    </div>
  );
}

// Fine-grained streaming with Suspense
// app/dashboard/page.tsx
import { Suspense } from "react";

export default function DashboardPage() {
  return (
    <div>
      {/* Renders immediately */}
      <DashboardHeader />
      
      {/* Streams when data resolves */}
      <Suspense fallback={<RevenueChartSkeleton />}>
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<LatestInvoicesSkeleton />}>
        <LatestInvoices />
      </Suspense>
    </div>
  );
}

4. 数据获取:fetch、ISR 与动态路由

App Router 中的数据获取在服务端组件中使用扩展的 fetch API 完成。Next.js 扩展了原生 fetch,添加了细粒度的缓存和重新验证控制。

fetch 缓存策略

// Next.js 15: fetch is NOT cached by default

// 1. Static data (CDN cached indefinitely)
const data = await fetch("https://api.example.com/static", {
  cache: "force-cache",
});

// 2. Time-based revalidation (ISR)
const posts = await fetch("https://api.example.com/posts", {
  next: { revalidate: 3600 }, // Revalidate every hour
});

// 3. Tag-based revalidation
const product = await fetch("https://api.example.com/products/1", {
  next: { tags: ["product", "product-1"] },
});

// 4. Dynamic data (never cached)
const userProfile = await fetch("https://api.example.com/me", {
  cache: "no-store",
  headers: { Authorization: "Bearer " + getToken() },
});

// Trigger on-demand revalidation from a Server Action
import { revalidateTag, revalidatePath } from "next/cache";

async function updateProduct(id: string, data: Partial<Product>) {
  "use server";
  await db.product.update({ where: { id }, data });
  revalidateTag("product-" + id); // Invalidate cached product data
  revalidatePath("/products");    // Revalidate product list page
}

generateStaticParams:SSG + 动态路由

// app/blog/[slug]/page.tsx

// Called at build time to pre-render pages
export async function generateStaticParams() {
  const posts = await fetch("https://api.example.com/posts").then(
    (res) => res.json()
  );

  return posts.map((post: { slug: string }) => ({
    slug: post.slug,
  }));
}

// Optionally control what happens for slugs not in generateStaticParams
// "blocking": generate on-demand and cache (default)
// false: 404 for unknown slugs
export const dynamicParams = true;

// Metadata generation
export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      images: [{ url: post.coverImage }],
    },
  };
}

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

5. Server Actions:表单处理与数据变更

Server Actions 是标记了 "use server" 的异步函数,直接在服务器上运行。它们消除了为简单的 CRUD 操作手动创建 API 路由的需要。

基础 Server Action 与表单

// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";

const CreatePostSchema = z.object({
  title: z.string().min(3).max(100),
  content: z.string().min(10),
});

export type ActionState = {
  errors?: { title?: string[]; content?: string[] };
  message?: string;
};

export async function createPost(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const validated = CreatePostSchema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
  });

  if (!validated.success) {
    return { errors: validated.error.flatten().fieldErrors };
  }

  try {
    await db.post.create({ data: validated.data });
  } catch (error) {
    return { message: "Database error: Failed to create post." };
  }

  revalidatePath("/blog");
  redirect("/blog");
}

在客户端组件中使用 useFormState

"use client";

import { useFormState, useFormStatus } from "react-dom";
import { createPost, type ActionState } from "@/app/actions";

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Creating..." : "Create Post"}
    </button>
  );
}

const initialState: ActionState = {};

export function CreatePostForm() {
  const [state, dispatch] = useFormState(createPost, initialState);

  return (
    <form action={dispatch}>
      <div>
        <label htmlFor="title">Title</label>
        <input id="title" name="title" type="text" />
        {state.errors?.title && (
          <p style={{ color: "red" }}>{state.errors.title[0]}</p>
        )}
      </div>
      <div>
        <label htmlFor="content">Content</label>
        <textarea id="content" name="content" />
        {state.errors?.content && (
          <p style={{ color: "red" }}>{state.errors.content[0]}</p>
        )}
      </div>
      {state.message && (
        <p style={{ color: "red" }}>{state.message}</p>
      )}
      <SubmitButton />
    </form>
  );
}

最佳实践:Server Actions 与 API Routes

对于从 Next.js 应用内部触发的表单提交和数据变更,优先使用 Server Actions。对于需要被外部系统(移动应用、第三方服务、Webhooks)调用的端点,继续使用 Route Handlers(app/api/route.ts)。

6. 图片与字体优化

next/image 和 next/font 是 Next.js 中两个最强大的内置优化工具,对 Core Web Vitals 分数有直接影响。

next/image:自动格式转换与懒加载

import Image from "next/image";

// Local image: import for automatic size detection
import heroImage from "@/public/hero.jpg";

export function HeroSection() {
  return (
    <div style={{ position: "relative", height: "80vh" }}>
      {/* LCP image: priority loads eagerly (no lazy) */}
      <Image
        src={heroImage}
        alt="Hero illustration"
        fill
        priority         // Removes loading="lazy" for LCP
        sizes="100vw"   // Responsive sizes hint
        style={{ objectFit: "cover" }}
        quality={85}    // Default: 75 (AVIF/WebP auto-selected)
      />
    </div>
  );
}

// Remote image: requires explicit dimensions
export function ProductCard({ product }: { product: Product }) {
  return (
    <Image
      src={product.imageUrl}
      alt={product.name}
      width={400}
      height={300}
      loading="lazy"   // Default for non-priority images
      placeholder="blur"
      blurDataURL={product.blurHash}
    />
  );
}

配置远程域名

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cdn.example.com",
        port: "",
        pathname: "/images/**",
      },
      {
        protocol: "https",
        hostname: "**.cloudinary.com",
      },
    ],
    formats: ["image/avif", "image/webp"], // Preferred order
    minimumCacheTTL: 60 * 60 * 24 * 30,   // 30 days
  },
};

export default nextConfig;

next/font:零 CLS 字体加载

// app/layout.tsx
import { Inter, Fira_Code } from "next/font/google";
import localFont from "next/font/local";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",       // Font display strategy
  variable: "--font-inter",  // CSS variable for use in styles
});

const firaCode = Fira_Code({
  subsets: ["latin"],
  weight: ["400", "500"],
  variable: "--font-fira-code",
});

// Self-hosted font
const brandFont = localFont({
  src: [
    { path: "./fonts/Brand-Regular.woff2", weight: "400" },
    { path: "./fonts/Brand-Bold.woff2", weight: "700" },
  ],
  variable: "--font-brand",
});

// Result: fonts are self-hosted by Next.js server,
// no external requests = no CLS, no privacy concerns
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html
      lang="en"
      className={inter.variable + " " + firaCode.variable + " " + brandFont.variable}
    >
      <body style={{ fontFamily: "var(--font-inter)" }}>{children}</body>
    </html>
  );
}

7. 中间件与 Edge Runtime

Next.js 中间件在请求到达页面之前在 Edge Runtime(Vercel 的全球边缘网络,或本地 Node.js 的轻量化 V8 沙箱)运行,延迟极低。

身份验证与重定向中间件

// middleware.ts (at project root)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { verifyJWT } from "@/lib/auth";

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

  // --- Authentication check ---
  if (pathname.startsWith("/dashboard")) {
    const token = request.cookies.get("auth-token")?.value;

    if (!token) {
      return NextResponse.redirect(
        new URL("/login?from=" + encodeURIComponent(pathname), request.url)
      );
    }

    try {
      const payload = await verifyJWT(token);
      // Pass user info to the page via headers
      const response = NextResponse.next();
      response.headers.set("x-user-id", payload.sub);
      response.headers.set("x-user-role", payload.role);
      return response;
    } catch {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }

  // --- A/B testing ---
  if (pathname === "/pricing") {
    const bucket = request.cookies.get("ab-bucket")?.value
      ?? (Math.random() < 0.5 ? "a" : "b");
    const response = NextResponse.rewrite(
      new URL("/pricing-" + bucket, request.url)
    );
    response.cookies.set("ab-bucket", bucket, { maxAge: 60 * 60 * 24 * 30 });
    return response;
  }

  // --- Geolocation-based redirect ---
  const country = request.geo?.country ?? "US";
  if (country === "DE" && !pathname.startsWith("/de")) {
    return NextResponse.redirect(new URL("/de" + pathname, request.url));
  }

  return NextResponse.next();
}

// Control which routes middleware applies to
export const config = {
  matcher: [
    // Match all routes except static files and API
    "/((?!_next/static|_next/image|favicon.ico|api).*)",
  ],
};

Edge Runtime 限制

Edge Runtime 不支持所有 Node.js API。无法使用 fs 模块、原生 Node.js 加密模块(使用 Web Crypto API 代替)或需要 Node.js 特定 API 的数据库驱动程序。对于这些操作,使用 Route Handlers(在 Node.js 运行时中运行)。

8. 部署:Vercel vs 自托管

Next.js 可以部署在任何支持 Node.js 的平台上。两种最常见的选择是 Vercel(官方平台)和使用 standalone 输出模式自托管。

Vercel 部署

# Deploying to Vercel is a single command
npx vercel

# Or push to main branch with Git integration
git push origin main  # Auto-deploys via GitHub integration

# Environment variables
# Set in: vercel.com/project/settings/environment-variables
# Or via CLI:
vercel env add DATABASE_URL production

独立模式 (standalone) + Docker

// next.config.ts
const nextConfig: NextConfig = {
  output: "standalone", // Creates minimal server bundle
};

// Dockerfile (multi-stage build)
// Stage 1: Dependencies
// FROM node:20-alpine AS deps
// RUN apk add --no-cache libc6-compat
// WORKDIR /app
// COPY package.json package-lock.json ./
// RUN npm ci
//
// Stage 2: Builder
// FROM node:20-alpine AS builder
// WORKDIR /app
// COPY --from=deps /app/node_modules ./node_modules
// COPY . .
// ENV NEXT_TELEMETRY_DISABLED=1
// RUN npm run build
//
// Stage 3: Runner (minimal image ~150MB)
// FROM node:20-alpine AS runner
// WORKDIR /app
// ENV NODE_ENV=production
// COPY --from=builder /app/.next/standalone ./
// COPY --from=builder /app/.next/static ./.next/static
// COPY --from=builder /app/public ./public
// EXPOSE 3000
// CMD ["node", "server.js"]

环境变量最佳实践

# .env.local (never commit - for local dev only)
DATABASE_URL=postgresql://user:pass@localhost/mydb
NEXTAUTH_SECRET=local-dev-secret

# .env (safe defaults, can be committed)
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_ANALYTICS_ID=

// Accessing in code:

// Server-only (NOT prefixed with NEXT_PUBLIC_)
// Only accessible in Server Components, Route Handlers, Server Actions
const db = new PrismaClient({
  datasources: { db: { url: process.env.DATABASE_URL } }
});

// Client-accessible (NEXT_PUBLIC_ prefix)
// Inlined at build time into the browser bundle
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;

// Type-safe env with T3 Env
// npm install @t3-oss/env-nextjs zod
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    NEXTAUTH_SECRET: z.string().min(1),
  },
  client: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
  },
  runtimeEnv: process.env,
});

9. 框架对比:Next.js vs Remix vs Nuxt vs SvelteKit

选择 Web 框架是一个重大决策。以下是 2026 年四个主要全栈框架的详细对比。

维度Next.js 15Remix 2Nuxt 3SvelteKit 2
底层框架React 19React 19Vue 3Svelte 5
默认渲染RSC (服务端)SSR (服务端)SSR (服务端)SSR (服务端)
数据获取fetch + cache / Server Componentsloader() / action()useFetch() / useAsyncData()load() in +page.server.ts
表单处理Server Actionsaction() (native forms)useFetch / Server Routesform actions (+page.server.ts)
路由类型文件系统 (app/)文件系统 (routes/)文件系统 (pages/)文件系统 (+page.svelte)
样式方案任意 (Tailwind 官方支持)任意任意 (UnoCSS 推荐)Scoped CSS (内置)
TS 支持优秀 (内置)优秀 (内置)优秀 (内置)优秀 (内置)
Bundle 大小 (框架)~85 KB (React runtime)~85 KB (React runtime)~100 KB (Vue runtime)~10-30 KB (compiled)
学习曲线中等 (RSC 概念)中等 (web standards)低-中 (Vue 生态)低 (Svelte 直观)
GitHub Stars~130K~30K~55K~19K
最适合全栈 React 应用、企业级以表单为中心的应用、Web 标准优先Vue 团队、内容站点性能优先、小型团队
生态系统非常庞大 (React)大 (React)大 (Vue)中等 (成长中)

何时选择 Remix

Remix 对 Web 标准(Fetch API、FormData、HTTP 语义)的强调使其代码更具可移植性,且不依赖框架抽象。如果你的团队重视表单的渐进增强(无 JavaScript 也能工作),Remix 是一个强有力的选择。

何时选择 SvelteKit

Svelte 通过编译器消除虚拟 DOM,生成非常小的 bundle。SvelteKit 非常适合需要最佳运行时性能的项目,以及不需要 React 生态系统的中小型团队。

10. 常见问题(FAQ)

Q1: App Router 和 Pages Router 有什么区别?

App Router 使用 React 服务端组件(默认)、嵌套布局、流式传输和 Server Actions。Pages Router 使用 getStaticProps/getServerSideProps 的传统模式。新项目推荐 App Router,旧项目可以逐步迁移。

Q2: 什么时候应该使用 "use client"?

当组件需要以下功能时使用 "use client":useState/useEffect 等 React 钩子、onClick/onChange 等事件监听器、浏览器 API(window、localStorage)、第三方客户端库。所有其他组件应保持为服务端组件以减少 JS 包大小。

Q3: Next.js 15 的缓存机制是如何工作的?

Next.js 15 改变了默认缓存行为:fetch 请求默认不再缓存(从 force-cache 改为 no-store)。使用 cache: "force-cache" 或 next.revalidate 显式控制缓存。Route handlers 也默认不缓存。这使缓存行为更可预测。

Q4: React 服务端组件(RSC)如何提升性能?

RSC 在服务器上渲染,不向客户端发送 JavaScript。这意味着:更小的 JS 包(组件代码不会被打包)、更快的 FCP(服务器直接发送 HTML)、安全的服务器端数据获取(可直接访问数据库和 API 密钥)、零客户端重新渲染(对于纯展示内容)。

Q5: Server Actions 是什么,如何使用它们?

Server Actions 是运行在服务器上的异步函数,用 "use server" 指令标记。它们可以直接在表单的 action 属性或事件处理器中调用。适合处理表单提交、数据库操作和文件上传,无需手动创建 API 路由。

Q6: ISR(增量静态再生)是什么?

ISR 允许你在构建后更新静态页面,无需重建整个站点。使用 next.revalidate 选项设置重新验证间隔(秒),或使用 revalidatePath/revalidateTag 按需触发重新验证。结合了静态页面的性能和动态内容的灵活性。

Q7: Next.js 中间件有什么用途?

Next.js 中间件在请求完成前运行,适用于:身份验证检查和重定向、A/B 测试(地理位置/cookies)、速率限制、请求头修改、国际化(根据语言重定向)、功能标志。中间件在 Edge Runtime 运行,延迟极低。

Q8: Next.js 应该部署在 Vercel 还是自托管?

Vercel 提供最佳的 Next.js 集成,支持 ISR、Edge Functions、Analytics 和零配置部署,适合大多数项目。自托管(standalone 输出模式 + Docker)适合需要完全控制基础设施、有合规要求或成本敏感的项目。两种方式都完全支持所有 Next.js 功能。

11. 生产环境性能优化清单

在将 Next.js 应用推向生产之前,请检查以下优化点以确保最佳性能。

服务端组件

  • 最大化服务端组件的使用
  • 将 "use client" 推向组件树叶子
  • 避免在客户端组件中导入大型库
  • 使用 React.lazy() 动态导入

图片与字体

  • 所有图片使用 next/image
  • LCP 图片添加 priority prop
  • 提供正确的 sizes 属性
  • 使用 next/font 避免布局偏移

缓存策略

  • 静态内容使用 force-cache
  • 动态内容设置适当的 revalidate
  • 使用 unstable_cache 缓存数据库查询
  • 实施 revalidateTag 按需失效

包大小

  • 使用 @next/bundle-analyzer 分析
  • 按需导入(避免 import * from)
  • 懒加载非关键第三方脚本
  • 使用 next/script 策略加载脚本

使用 Bundle Analyzer 分析包大小

# Install bundle analyzer
npm install @next/bundle-analyzer

// next.config.ts
import withBundleAnalyzer from "@next/bundle-analyzer";

const withAnalyzer = withBundleAnalyzer({
  enabled: process.env.ANALYZE === "true",
});

export default withAnalyzer(nextConfig);

# Generate and open the bundle analysis report
ANALYZE=true npm run build

# Example: Dynamic import for heavy charting library
import dynamic from "next/dynamic";

const HeavyChart = dynamic(
  () => import("@/components/HeavyChart"),
  {
    loading: () => <p>Loading chart...</p>,
    ssr: false, // Skip SSR for browser-only lib
  }
);

// Third-party script with strategy
import Script from "next/script";

export function Analytics() {
  return (
    <Script
      src="https://analytics.example.com/script.js"
      strategy="lazyOnload"  // afterInteractive | lazyOnload | beforeInteractive
      onLoad={() => console.log("Analytics loaded")}
    />
  );
}

总结

Next.js 15 的 App Router 代表了 React 全栈开发的成熟范式。React 服务端组件减少了客户端 JavaScript,Server Actions 简化了数据变更流程,而内置的图片、字体优化和中间件使得构建生产级应用更加简单。

关键建议:从 App Router 开始新项目,默认使用服务端组件,仅在需要交互性时使用 "use client"。用 Server Actions 替代简单的 API 路由,用 ISR(revalidate + revalidatePath)处理动态内容。部署时,Vercel 提供最少配置,而 standalone 模式 + Docker 给予完全的基础设施控制。

相关工具

使用 DevToolBox 的 JSON Formatter 调试 API 响应,Regex Tester 验证路由匹配模式,以及 JWT Decoder 检查 Next-Auth 生成的令牌。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterJWTJWT Decoder.*Regex Tester

相关文章

Next.js App Router: 2026 完整迁移指南

掌握 Next.js App Router 的全面指南。学习 Server Components、数据获取、布局、流式渲染、Server Actions,以及从 Pages Router 的逐步迁移策略。

Astro vs Next.js 2026:Islands 架构 vs React 服务器组件

Astro 与 Next.js 2026 深度对比:islands 架构、RSC、性能基准测试与 SEO。

React Hooks 完全指南:useState、useEffect 和自定义 Hooks

通过实际示例掌握 React Hooks。学习 useState、useEffect、useContext、useReducer、useMemo、useCallback、自定义 Hooks 和 React 18+ 并发 Hooks。