Skip to content

Next.js

Next.js 是一个基于 React 的全栈 Web 框架,由 Vercel 公司开发并维护。它提供了服务器端渲染、静态站点生成、增量静态再生等功能,帮助开发者构建高性能、可扩展的 Web 应用程序。Next.js 封装了 React 应用开发中的复杂配置,提供了开箱即用的开发体验,使开发者能够专注于业务逻辑的实现。

Next.js 的核心优势在于其灵活的渲染策略。开发者可以根据页面的具体需求,选择静态生成、服务器端渲染或客户端渲染,甚至可以在同一个项目中混合使用多种渲染方式。这种灵活性使得 Next.js 能够适应各种复杂的业务场景,无论是需要 SEO 优化的营销页面,还是需要实时数据更新的交互式应用,Next.js 都能提供最优的解决方案。此外,Next.js 内置了自动代码分割、图片优化、字体优化等性能优化功能,确保应用在生产环境中能够保持出色的性能表现。

核心概念

渲染模式概述

Next.js 支持三种主要的渲染模式,每种模式都有其适用场景和特点。静态站点生成在构建时生成 HTML,适用于内容不频繁变化的页面,如博客文章、产品文档等。服务器端渲染在每次请求时动态生成 HTML,适用于需要实时数据的页面,如个性化仪表板、搜索结果页等。客户端渲染在浏览器端使用 JavaScript 渲染内容,适用于高度交互的组件和需要即时用户反馈的功能。

增量静态再生结合了静态生成和服务器端渲染的优点。它在构建时预渲染页面,同时在后台按需更新已生成的页面。这种方式既享有静态页面的高性能,又保持了数据的实时性。当用户访问一个页面时,Next.js 首先返回已缓存的版本,同时在后台重新验证或重新生成该页面,确保用户始终能够获得相对新鲜的内容,同时享受静态页面带来的快速响应。

目录结构

Next.js 项目遵循约定俗成的目录结构,使代码组织更加清晰和可维护。app 目录是 App Router 的核心,存放页面组件、布局和路由相关文件,每个子目录代表一个路由段,通过文件夹结构自动生成路由路径。public 目录存放静态资源,如图片、字体、robots.txt 等,这些文件直接通过根路径访问。src 目录用于存放源代码,通常包含 appcomponentslibstyles 等子目录,将源代码与配置文件分离。

next-app/
├── app/
│   ├── layout.tsx      # 根布局文件
│   ├── page.tsx        # 首页组件
│   ├── globals.css     # 全局样式
│   ├── about/
│   │   └── page.tsx    # /about 页面
│   └── blog/
│       ├── page.tsx    # /blog 页面
│       └── [slug]/
│           └── page.tsx # /blog/:slug 动态路由
├── public/
│   ├── images/
│   │   └── logo.png
│   └── favicon.ico
├── src/
│   └── app/
│       └── page.tsx
├── next.config.js      # Next.js 配置
├── package.json
├── tsconfig.json
└── tailwind.config.ts

配置文件

next.config.js 是 Next.js 的核心配置文件,用于定制构建过程和运行时行为。在这个文件中,开发者可以配置 React 严格模式、图像优化策略、实验性功能、静态资源处理等选项。例如,启用 React StrictMode 有助于在开发阶段发现潜在的代码问题;配置 images 域允许从外部源加载优化后的图片;设置 experimental 属性可以提前体验尚在测试阶段的新功能。

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
  experimental: {
    serverActions: true,
    optimizePackageImports: ['lucide-react', 'shadcn-ui'],
  },
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

快速开始

安装与初始化

创建新的 Next.js 项目非常简单,只需要几个步骤即可完成。确保你的系统中已安装 Node.js 版本 18.17.0 或更高版本,然后使用 create-next-app 命令快速脚手架一个新项目。这个命令会安装所有必要的依赖,包括 React、TypeScript、ESLint、Tailwind CSS 等,并配置好开箱即用的开发环境。

bash
# 使用 npm 创建项目
npx create-next-app@latest my-next-app --typescript --tailwind --eslint

# 使用 yarn 创建项目
yarn create next-app my-next-app --typescript --tailwind --eslint

# 使用 pnpm 创建项目
pnpm create next-app my-next-app --typescript --tailwind --eslint

# 使用 bun 创建项目
bun create next-app my-next-app --typescript --tailwind --eslint

在创建过程中,你可以选择是否使用 TypeScript、ESLint、Tailwind CSS、App Router 等特性。推荐使用 App Router,因为它提供了更现代的路由系统和服务器组件支持。完成安装后,进入项目目录并启动开发服务器,即可开始构建你的应用。

bash
cd my-next-app
npm run dev
# 在浏览器中访问 http://localhost:3000

开发环境配置

TypeScript 配置是 Next.js 项目的重要组成部分。在项目根目录下的 tsconfig.json 文件中,Next.js 会自动配置适合 React 开发的 TypeScript 选项,包括模块解析路径、严格类型检查、JSX 编译选项等。默认配置已经足够大多数项目使用,但你可以根据团队规范进行定制。

json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

ESLint 配置帮助维护代码质量和一致性。Next.js 内置了 next/core-web-vitals 规则集,涵盖了 React 组件的最佳实践、钩子使用规范、性能优化建议等。在 .eslintrc.json 文件中,你可以扩展这个规则集并添加自定义规则。

json
{
  "extends": ["next/core-web-vitals"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "warn",
    "react/no-unescaped-entities": "off"
  }
}

文件路由

基础路由

Next.js 的 App Router 使用文件系统作为路由的基础。每个 app 目录下的文件夹代表一个路由段,文件夹名称即为路由路径。page.tsx 文件定义了对应路由的 UI 内容,是路由的入口组件。通过嵌套文件夹,可以创建嵌套路由,每个嵌套层级都会在 URL 中体现。

app/
├── page.tsx              # / (首页)
├── about/
│   └── page.tsx          # /about
├── products/
│   ├── page.tsx          # /products
│   └── [id]/
│       └── page.tsx      # /products/:id
└── blog/
    ├── page.tsx          # /blog
    └── [slug]/
        └── page.tsx      # /blog/:slug
tsx
// app/about/page.tsx
export default function AboutPage() {
  return (
    <main className="min-h-screen p-8">
      <h1 className="text-4xl font-bold mb-4">关于我们</h1>
      <p className="text-lg text-gray-600">
        这是一个使用 Next.js 构建的关于页面。
      </p>
    </main>
  );
}

动态路由

动态路由允许你创建匹配任意模式的路由段,适用于博客文章、产品详情页等场景。使用方括号 [param] 语法定义动态段,可以是必需参数,也可以是可选参数。动态段的值会作为 params 属性传递给页面组件,在服务器组件中可以直接异步获取。

tsx
// app/products/[id]/page.tsx
import { notFound } from 'next/navigation';

interface ProductPageProps {
  params: {
    id: string;
  };
}

async function getProduct(id: string) {
  const res = await fetch(`https://api.example.com/products/${id}`, {
    next: { revalidate: 3600 },
  });
  
  if (!res.ok) {
    return undefined;
  }
  
  return res.json();
}

export default async function ProductPage({ params }: ProductPageProps) {
  const product = await getProduct(params.id);
  
  if (!product) {
    notFound();
  }
  
  return (
    <main className="container mx-auto p-8">
      <h1 className="text-3xl font-bold">{product.name}</h1>
      <p className="text-xl mt-4">${product.price}</p>
      <p className="mt-4">{product.description}</p>
    </main>
  );
}

可选参数使用双括号 [[...slug]] 定义,表示该参数可以不存在。这在需要处理多种 URL 模式时非常有用,例如 /blog/blog/:slug 可以共存。

tsx
// app/blog/[[...slug]]/page.tsx
export default function BlogPage({
  params,
}: {
  params: {
    slug?: string[];
  };
}) {
  const slug = params.slug;
  
  if (!slug) {
    return <h1>博客首页</h1>;
  }
  
  return <h1>文章分类: {slug.join(' / ')}</h1>;
}

路由组

路由组使用圆括号 (group) 包裹文件夹名称,它不会影响 URL 路径,但允许你对相关路由进行逻辑分组。这在需要为不同部分的路由设置不同布局时特别有用,例如管理员界面和公开界面可以使用完全不同的布局结构。

app/
├── (marketing)/
│   ├── layout.tsx       # 营销页面布局
│   ├── page.tsx         # /
│   └── about/
│       └── page.tsx     # /about
└── (dashboard)/
    ├── layout.tsx       # 仪表板布局
    ├── dashboard/
    │   └── page.tsx     # /dashboard
    └── settings/
        └── page.tsx     # /dashboard/settings
tsx
// app/(marketing)/layout.tsx
export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="min-h-screen">
      <header className="bg-blue-600 text-white p-4">
        <nav>
          <a href="/" className="mr-4">首页</a>
          <a href="/about">关于</a>
        </nav>
      </header>
      <main>{children}</main>
    </div>
  );
}

布局与页面

根布局

根布局文件 app/layout.tsx 定义了所有页面的共同 UI 结构。它必须包含 <html><body> 标签,是整个应用的顶层容器。在根布局中,你可以放置全局的导航栏、页脚、字体配置、全局样式导入等。每个页面都会包裹在这个布局中,因此放在这里的内容会在页面切换时保持不变。

tsx
// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';

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

export const metadata: Metadata = {
  title: '我的 Next.js 应用',
  description: '使用 Next.js 14 构建的现代化 Web 应用',
  openGraph: {
    title: '我的 Next.js 应用',
    description: '使用 Next.js 14 构建的现代化 Web 应用',
    type: 'website',
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body className={inter.className}>
        <Navbar />
        <div className="min-h-screen">
          {children}
        </div>
        <Footer />
      </body>
    </html>
  );
}

嵌套布局

除了根布局,你还可以在任何路由段创建嵌套布局。这些布局会包裹对应路由段的所有子页面,并在路由切换时保持状态。嵌套布局可以访问路由参数,用于获取当前路由的上下文信息。这在创建需要保持状态的页面区域时非常有用,例如侧边栏中的选中状态。

tsx
// app/dashboard/layout.tsx
import Sidebar from '@/components/Sidebar';

export default function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { teamId: string };
}) {
  return (
    <div className="flex h-screen">
      <Sidebar teamId={params.teamId} />
      <main className="flex-1 overflow-auto p-8">
        {children}
      </main>
    </div>
  );
}

模板布局

模板文件 app/template.tsx 类似于布局,但在每次路由切换时都会重新创建。这对于需要在页面切换时触发动画效果或重置状态的场景非常有用。与布局不同,模板中的状态不会在页面间保持,每次导航都会触发组件的重新挂载。

tsx
// app/template.tsx
'use client';

import { useEffect } from 'react';

export default function Template({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    console.log('模板挂载 - 页面切换');
  }, []);
  
  return (
    <div className="animate-fade-in">
      {children}
    </div>
  );
}

数据获取

服务器组件数据获取

在服务器组件中,你可以直接使用 async/await 进行数据获取,这是最简单和直观的方式。Next.js 会自动缓存 fetch 请求的结果,在同一请求中重复获取相同数据时不会产生额外网络请求。对于需要实时性的数据,可以使用 revalidate 选项控制缓存策略。

tsx
// app/posts/page.tsx
interface Post {
  id: number;
  title: string;
  body: string;
}

async function getPosts() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    next: { revalidate: 60 },
  });
  
  if (!res.ok) {
    throw new Error('获取文章失败');
  }
  
  return res.json();
}

export default async function PostsPage() {
  const posts: Post[] = await getPosts();
  
  return (
    <main className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">文章列表</h1>
      <div className="grid gap-6">
        {posts.map((post) => (
          <article key={post.id} className="p-6 border rounded-lg">
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="mt-2 text-gray-600">{post.body}</p>
          </article>
        ))}
      </div>
    </main>
  );
}

静态站点生成

静态站点生成在构建时预先渲染所有页面,生成纯 HTML 文件。这种方式提供最快的页面加载速度,非常适合内容不频繁变化的页面,如文档站点、博客、营销页面等。在 generateStaticParams 函数中定义需要预生成的路径,Next.js 会在构建时为每个路径生成对应的 HTML。

tsx
// app/blog/[slug]/page.tsx
interface Post {
  slug: string;
  title: string;
  content: string;
}

async function getPost(slug: string): Promise<Post | null> {
  const posts: Record<string, Post> = {
    'hello-world': {
      slug: 'hello-world',
      title: '你好,世界',
      content: '这是我的第一篇 Next.js 博客文章。',
    },
  };
  
  return posts[slug] || null;
}

export async function generateStaticParams() {
  const posts = ['hello-world', 'nextjs-guide'];
  
  return posts.map((slug) => ({
    slug,
  }));
}

export default async function BlogPostPage({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  
  if (!post) {
    return <div>文章不存在</div>;
  }
  
  return (
    <article className="max-w-2xl mx-auto py-12">
      <h1 className="text-4xl font-bold">{post.title}</h1>
      <div className="mt-8 prose">{post.content}</div>
    </article>
  );
}

增量静态再生

增量静态再生允许你在已部署的应用中更新静态页面,而无需重新构建整个站点。通过设置 revalidate 时间间隔,Next.js 会在后台按需重新验证和更新过期的页面。这种方式结合了静态生成的性能和动态内容的实时性。

tsx
// app/products/page.tsx
async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600 }, // 1小时后重新验证
  });
  
  return res.json();
}

export default async function ProductsPage() {
  const products = await getProducts();
  
  return (
    <div className="grid grid-cols-3 gap-6">
      {products.map((product: any) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

API 路由

基础 API 路由

Next.js 允许你在 app/api 目录下创建 API 端点,每个文件会对应一个 RESTful 风格的 API 路由。这些 API 路由本质上是 Next.js 的服务器less 函数,可以访问数据库、执行认证逻辑、处理表单数据等。App Router 中的 API 路由默认使用服务器组件的运行时环境,支持异步操作。

typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server';

interface User {
  id: string;
  name: string;
  email: string;
}

const users: User[] = [
  { id: '1', name: '张三', email: 'zhangsan@example.com' },
  { id: '2', name: '李四', email: 'lisi@example.com' },
];

export async function GET() {
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  
  const newUser: User = {
    id: String(users.length + 1),
    ...body,
  };
  
  users.push(newUser);
  
  return NextResponse.json(newUser, { status: 201 });
}

动态 API 路由

与页面路由类似,API 路由也支持动态参数。你可以使用方括号语法创建动态 API 端点,处理不同的资源标识符。在 API 路由函数中,可以通过 params 对象访问动态参数的值。

typescript
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const userId = params.id;
  
  const user = {
    id: userId,
    name: '张三',
    email: 'zhangsan@example.com',
  };
  
  return NextResponse.json(user);
}

export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();
  
  return NextResponse.json({
    id: params.id,
    ...body,
    updatedAt: new Date().toISOString(),
  });
}

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  return NextResponse.json({ success: true, deletedId: params.id });
}

请求处理

API 路由支持所有 HTTP 方法,包括 GET、POST、PUT、PATCH、DELETE、OPTIONS 等。通过检查 request.method 属性,可以对不同的请求方法执行不同的处理逻辑。对于复杂的请求处理,建议将业务逻辑分离到独立的模块中,保持路由处理程序的简洁。

typescript
// app/api/upload/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  try {
    const formData = await request.formData();
    const file = formData.get('file') as File;
    const name = formData.get('name') as string;
    
    if (!file) {
      return NextResponse.json(
        { error: '缺少文件' },
        { status: 400 }
      );
    }
    
    const bytes = await file.arrayBuffer();
    const buffer = Buffer.from(bytes);
    
    // 这里可以将文件保存到存储服务
    console.log(`上传文件: ${name}, 大小: ${buffer.length} 字节`);
    
    return NextResponse.json({
      success: true,
      filename: name,
      size: buffer.length,
    });
  } catch (error) {
    return NextResponse.json(
      { error: '上传失败' },
      { status: 500 }
    );
  }
}

样式方案

Tailwind CSS

Next.js 与 Tailwind CSS 天然集成,提供了优秀的开发体验。通过 create-next-app 创建项目时,可以选择自动配置 Tailwind。Tailwind 的原子化 CSS 理念使得样式定义变得简洁而强大,同时通过 JIT 编译器确保生成的生产代码只包含实际使用的样式类。

tsx
// app/page.tsx
export default function HomePage() {
  return (
    <main className="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600">
      <div className="container mx-auto px-4 py-16">
        <h1 className="text-5xl font-bold text-white text-center mb-8">
          欢迎来到 Next.js
        </h1>
        <p className="text-xl text-white/90 text-center max-w-2xl mx-auto">
          使用 Tailwind CSS 快速构建美观的用户界面
        </p>
        <div className="mt-12 flex justify-center gap-4">
          <button className="px-8 py-3 bg-white text-blue-600 rounded-lg font-semibold hover:bg-blue-50 transition-colors">
            开始使用
          </button>
          <button className="px-8 py-3 border-2 border-white text-white rounded-lg font-semibold hover:bg-white/10 transition-colors">
            了解更多
          </button>
        </div>
      </div>
    </main>
  );
}

CSS 模块

对于需要更好样式隔离的场景,CSS 模块是理想的选择。CSS 模块会自动生成唯一的类名,避免样式冲突,同时保持代码的组织性。每个模块文件 .module.css 可以导入到组件中使用,生成的类名在生产构建中会被哈希处理。

css
/* app/components/Button.module.css */
.button {
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
}

.primary {
  background-color: #3b82f6;
  color: white;
}

.primary:hover {
  background-color: #2563eb;
}

.secondary {
  background-color: #f3f4f6;
  color: #374151;
}

.secondary:hover {
  background-color: #e5e7eb;
}
tsx
// app/components/Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
}

export default function Button({ 
  children, 
  variant = 'primary',
  onClick 
}: ButtonProps) {
  return (
    <button
      className={`${styles.button} ${styles[variant]}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

全局样式

全局样式文件 app/globals.css 用于定义整个应用的全局样式规则,包括 CSS 重置、自定义 CSS 变量、全局字体设置等。在这个文件中,你可以定义与应用主题相关的 CSS 变量,然后在组件中通过 Tailwind 或直接引用这些变量。

css
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --primary-color: #3b82f6;
  --secondary-color: #8b5cf6;
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  color: var(--foreground);
  background: var(--background);
  font-family: system-ui, -apple-system, sans-serif;
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
}

优化特性

图片优化

Next.js 的 Image 组件提供了强大的图片优化功能,包括自动调整尺寸、格式转换、懒加载等。使用 next/image 替代原生 <img> 标签,可以显著提升页面性能。组件会自动根据设备和浏览器支持情况,选择最优的图片格式和尺寸。

tsx
// app/components/ProductImage.tsx
import Image from 'next/image';

interface ProductImageProps {
  src: string;
  alt: string;
  width?: number;
  height?: number;
}

export default function ProductImage({ 
  src, 
  alt, 
  width = 400, 
  height = 400 
}: ProductImageProps) {
  return (
    <div className="relative aspect-square overflow-hidden rounded-lg">
      <Image
        src={src}
        alt={alt}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover transition-transform hover:scale-105"
        priority={false}
        placeholder="blur"
        blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
      />
    </div>
  );
}

字体优化

Next.js 的 next/font 模块提供了内置的字体优化功能。它会自动下载 Google Fonts 或本地字体,在构建时将其转换为优化的格式,并托管在本地服务器上。这样可以避免字体加载导致的布局偏移,同时减少外部网络请求。

tsx
// app/layout.tsx
import { Inter, JetBrains_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
  fallback: ['system-ui', 'sans-serif'],
});

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-mono',
  fallback: ['monospace'],
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN" className={`${inter.variable} ${jetbrainsMono.variable}`}>
      <body className="font-sans antialiased">
        {children}
      </body>
    </html>
  );
}

脚本优化

next/script 组件提供了对第三方脚本加载的精细控制。通过设置 strategy 属性,可以选择在页面空闲时加载脚本、延迟加载、立即执行或使用 Worker 线程。这对于集成分析工具、广告脚本等第三方服务非常有用,同时不会阻塞主线程影响页面性能。

tsx
// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <head>
        <title>我的应用</title>
      </head>
      <body>
        {children}
        
        <Script
          src="https://analytics.example.com/script.js"
          strategy="afterInteractive"
          onLoad={() => {
            console.log('分析脚本已加载');
          }}
        />
        
        <Script
          id="ads-pixel"
          strategy="afterInteractive"
          dangerouslySetInnerHTML={{
            __html: `
              !function(f,b,e,v,n,t,s)
              {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
              n.callMethod.apply(n,arguments):n.queue.push(arguments)};
              if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
              n.queue=[];t=b.createElement(e);t.async=!0;
              t.src=v;s=b.getElementsByTagName(e)[0];
              s.parentNode.insertBefore(t,s)}(window, document,'script',
              'https://connect.facebook.net/en_US/fbevents.js');
            `,
          }}
        />
      </body>
    </html>
  );
}

部署选项

Vercel 部署

Vercel 是 Next.js 的创建者提供的托管平台,提供了最流畅的部署体验。只需将代码推送到 Git 仓库,Vercel 会自动检测 Next.js 项目并配置构建和部署流程。每次推送到主分支都会触发自动部署,同时提供预览部署功能,让你可以预览 pull request 的效果。Vercel 还提供全球 CDN 边缘网络、自动 HTTPS、自定义域名等特性。

bash
# 安装 Vercel CLI
npm i -g vercel

# 登录 Vercel
vercel login

# 部署项目
vercel

自托管部署

Next.js 也支持在任何支持 Node.js 的环境中部署,包括传统服务器、Docker 容器、Kubernetes 集群等。使用 next start 命令启动生产服务器,或使用自定义服务器实现更复杂的部署需求。Docker 部署是自托管的常用方式,通过 Dockerfile 定义应用的构建和运行过程。

dockerfile
# Dockerfile
FROM node:20-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000

CMD ["node", "server.js"]

Edge 部署

Next.js 支持在边缘运行时部署函数,提供更低的延迟和更好的全球可用性。通过使用 Edge Runtime,可以将 API 路由和部分页面逻辑部署到全球分布的边缘节点。这种方式特别适合需要低延迟的实时应用、个性化内容和 A/B 测试场景。

typescript
// app/api/edge/route.ts
export const runtime = 'edge';

export default async function handler(request: Request) {
  const { searchParams } = new URL(request.url);
  const name = searchParams.get('name') || 'World';
  
  return new Response(
    JSON.stringify({
      message: `你好,${name}!`,
      timestamp: Date.now(),
    }),
    {
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, s-maxage=86400',
      },
    }
  );
}

最佳实践

项目结构组织

良好的项目结构组织是保持代码库可维护性的基础。建议将组件按功能分类存放,使用清晰的命名约定。共享组件放在 components 目录下,按原子级别或功能模块组织。工具函数和业务逻辑分离到 lib 目录。类型定义统一放在 types 目录。API 相关的代码可以放在 servicesapi 目录中。

src/
├── app/                    # App Router 页面
│   ├── (auth)/             # 认证相关路由
│   ├── (dashboard)/        # 仪表板路由
│   ├── api/                # API 路由
│   └── globals.css
├── components/
│   ├── ui/                 # 基础 UI 组件
│   ├── forms/              # 表单组件
│   ├── layout/             # 布局组件
│   └── features/           # 功能组件
├── lib/
│   ├── utils.ts            # 工具函数
│   ├── api.ts              # API 封装
│   └── constants.ts        # 常量定义
├── hooks/                  # 自定义 Hooks
├── services/               # 业务服务层
├── types/                  # TypeScript 类型
└── styles/                 # 全局样式

性能优化策略

性能优化是 Next.js 应用开发的重要环节。首先,确保对静态资源进行适当压缩和缓存。其次,使用 next/imagenext/font 自动优化媒体文件和字体。第三,合理使用动态导入减少初始加载体积。第四,对复杂计算使用 memouseMemo 避免不必要的重复计算。第五,利用服务器组件减少客户端 JavaScript 体积。

tsx
// 使用动态导入优化性能
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <div>加载中...</div>,
  ssr: false, // 禁用服务端渲染
});

export default function Page() {
  return (
    <div>
      <h1>仪表板</h1>
      <HeavyChart data={[1, 2, 3, 4, 5]} />
    </div>
  );
}

安全实践

确保 Next.js 应用的安全性需要关注多个方面。首先,使用 next/headersnext/cookies 时注意敏感信息的处理。其次,API 路由中始终进行输入验证和错误处理。第三,配置适当的 Content Security Policy 防止 XSS 攻击。第四,对用户生成的内容进行适当的转义和清理。第五,使用环境变量管理敏感配置,避免硬编码密钥。

typescript
// app/api/protected/route.ts
import { NextResponse } from 'next/server';
import { headers } from 'next/headers';

export async function GET() {
  const headersList = headers();
  const apiKey = headersList.get('x-api-key');
  
  if (!apiKey || apiKey !== process.env.API_KEY) {
    return NextResponse.json(
      { error: '未授权' },
      { status: 401 }
    );
  }
  
  return NextResponse.json({
    data: '受保护的内容',
    timestamp: new Date().toISOString(),
  });
}

官方资源