nextjs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js Skill

Next.js 使用指南

Next.js is a React framework for building full-stack web applications with server-side rendering, static generation, and powerful optimization features built-in.
Next.js 是一款基于React的全栈Web应用框架,内置服务端渲染、静态生成功能以及强大的优化特性。

Reference

参考资料

When to Use This Skill

适用场景

Use this skill when:
  • Building new Next.js applications (v15+)
  • Implementing App Router architecture
  • Working with Server Components and Client Components
  • Setting up routing, layouts, and navigation
  • Implementing data fetching patterns
  • Optimizing images, fonts, and performance
  • Configuring metadata and SEO
  • Setting up API routes and route handlers
  • Migrating from Pages Router to App Router
  • Deploying Next.js applications
以下场景适用本指南:
  • 构建全新Next.js应用(v15及以上版本)
  • 实现App Router架构
  • 使用Server Components与Client Components
  • 配置路由、布局与导航
  • 实现数据获取模式
  • 优化图片、字体与性能
  • 配置元数据与SEO
  • 配置API路由与路由处理器
  • 从Pages Router迁移至App Router
  • 部署Next.js应用

Core Concepts

核心概念

App Router vs Pages Router

App Router 与 Pages Router 对比

App Router (Recommended for v13+):
  • Modern architecture with React Server Components
  • File-system based routing in
    app/
    directory
  • Layouts, loading states, and error boundaries
  • Streaming and Suspense support
  • Nested routing with layouts
Pages Router (Legacy):
  • Traditional page-based routing in
    pages/
    directory
  • Uses
    getStaticProps
    ,
    getServerSideProps
    ,
    getInitialProps
  • Still supported for existing projects
App Router(v13及以上版本推荐使用):
  • 基于React Server Components的现代化架构
  • 基于
    app/
    目录的文件系统路由
  • 布局、加载状态与错误边界
  • 支持流式渲染与Suspense
  • 支持带布局的嵌套路由
Pages Router(旧版):
  • 基于
    pages/
    目录的传统页面路由
  • 使用
    getStaticProps
    getServerSideProps
    getInitialProps
  • 仍支持现有项目使用

Key Architectural Principles

核心架构原则

  1. Server Components by Default: Components in
    app/
    are Server Components unless marked with
    'use client'
  2. File-based Routing: File system defines application routes
  3. Nested Layouts: Share UI across routes with layouts
  4. Progressive Enhancement: Works without JavaScript when possible
  5. Automatic Optimization: Images, fonts, scripts auto-optimized
  1. 默认使用Server Components
    app/
    目录下的组件默认是Server Components,除非标记
    'use client'
  2. 基于文件的路由:由文件系统定义应用路由
  3. 嵌套布局:通过布局在多个路由间共享UI
  4. 渐进式增强:在无JavaScript环境下也可正常运行(如果可能)
  5. 自动优化:图片、字体、脚本自动优化

Installation & Setup

安装与配置

Create New Project

创建新项目

bash
npx create-next-app@latest my-app
bash
npx create-next-app@latest my-app

or

or

yarn create next-app my-app
yarn create next-app my-app

or

or

pnpm create next-app my-app
pnpm create next-app my-app

or

or

bun create next-app my-app

**Interactive Setup Prompts:**

- TypeScript? (Yes recommended)
- ESLint? (Yes recommended)
- Tailwind CSS? (Optional)
- `src/` directory? (Optional)
- App Router? (Yes for new projects)
- Import alias? (Default: @/\*)
bun create next-app my-app

**交互式配置提示:**

- 是否使用TypeScript?(推荐选择是)
- 是否使用ESLint?(推荐选择是)
- 是否使用Tailwind CSS?(可选)
- 是否创建`src/`目录?(可选)
- 是否使用App Router?(新项目推荐选择是)
- 是否配置导入别名?(默认:@/*)

Manual Setup

手动配置

bash
npm install next@latest react@latest react-dom@latest
package.json scripts:
json
{
	"scripts": {
		"dev": "next dev",
		"build": "next build",
		"start": "next start",
		"lint": "next lint"
	}
}
bash
npm install next@latest react@latest react-dom@latest
package.json 脚本配置:
json
{
	"scripts": {
		"dev": "next dev",
		"build": "next build",
		"start": "next start",
		"lint": "next lint"
	}
}

Project Structure

项目结构

my-app/
├── app/                    # App Router (v13+)
│   ├── layout.tsx         # Root layout
│   ├── page.tsx           # Home page
│   ├── loading.tsx        # Loading UI
│   ├── error.tsx          # Error UI
│   ├── not-found.tsx      # 404 page
│   ├── global.css         # Global styles
│   └── [folder]/          # Route segments
├── public/                # Static assets
├── components/            # React components
├── lib/                   # Utility functions
├── next.config.js         # Next.js configuration
├── package.json
└── tsconfig.json
my-app/
├── app/                    # App Router(v13+)
│   ├── layout.tsx         # 根布局
│   ├── page.tsx           # 首页
│   ├── loading.tsx        # 加载UI
│   ├── error.tsx          # 错误UI
│   ├── not-found.tsx      # 404页面
│   ├── global.css         # 全局样式
│   └── [folder]/          # 路由片段
├── public/                # 静态资源
├── components/            # React组件
├── lib/                   # 工具函数
├── next.config.js         # Next.js配置
├── package.json
└── tsconfig.json

Routing

路由

File Conventions

文件约定

  • page.tsx
    - Page UI for route
  • layout.tsx
    - Shared UI for segment and children
  • loading.tsx
    - Loading UI (wraps page in Suspense)
  • error.tsx
    - Error UI (wraps page in Error Boundary)
  • not-found.tsx
    - 404 UI
  • route.ts
    - API endpoint (Route Handler)
  • template.tsx
    - Re-rendered layout UI
  • default.tsx
    - Parallel route fallback
  • page.tsx
    - 路由对应的页面UI
  • layout.tsx
    - 为当前路由片段及子路由共享的UI
  • loading.tsx
    - 加载UI(将页面包裹在Suspense中)
  • error.tsx
    - 错误UI(将页面包裹在错误边界中)
  • not-found.tsx
    - 404 UI
  • route.ts
    - API端点(路由处理器)
  • template.tsx
    - 可重新渲染的布局UI
  • default.tsx
    - 并行路由的回退UI

Basic Routing

基础路由

Static Route:
app/
├── page.tsx              → /
├── about/
│   └── page.tsx         → /about
└── blog/
    └── page.tsx         → /blog
Dynamic Route:
tsx
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
	return <h1>Post: {params.slug}</h1>;
}
Catch-all Route:
tsx
// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
	return <h1>Category: {params.slug.join("/")}</h1>;
}
Optional Catch-all:
tsx
// app/docs/[[...slug]]/page.tsx
// Matches /docs, /docs/a, /docs/a/b, etc.
静态路由:
app/
├── page.tsx              → /
├── about/
│   └── page.tsx         → /about
└── blog/
    └── page.tsx         → /blog
动态路由:
tsx
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
	return <h1>文章:{params.slug}</h1>;
}
捕获所有路由:
tsx
// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
	return <h1>分类:{params.slug.join("/")}</h1>;
}
可选捕获所有路由:
tsx
// app/docs/[[...slug]]/page.tsx
// 匹配 /docs、/docs/a、/docs/a/b 等路径

Route Groups

路由组

Organize routes without affecting URL:
app/
├── (marketing)/          # Group without URL segment
│   ├── about/page.tsx   → /about
│   └── blog/page.tsx    → /blog
└── (shop)/
    ├── products/page.tsx → /products
    └── cart/page.tsx     → /cart
在不影响URL的前提下组织路由:
app/
├── (marketing)/          # 路由组,不生成URL片段
│   ├── about/page.tsx   → /about
│   └── blog/page.tsx    → /blog
└── (shop)/
    ├── products/page.tsx → /products
    └── cart/page.tsx     → /cart

Parallel Routes

并行路由

Render multiple pages in same layout:
app/
├── @team/               # Slot
│   └── page.tsx
├── @analytics/          # Slot
│   └── page.tsx
└── layout.tsx           # Uses both slots
tsx
// app/layout.tsx
export default function Layout({
	children,
	team,
	analytics,
}: {
	children: React.ReactNode;
	team: React.ReactNode;
	analytics: React.ReactNode;
}) {
	return (
		<>
			{children}
			{team}
			{analytics}
		</>
	);
}
在同一布局中渲染多个页面:
app/
├── @team/               # 插槽
│   └── page.tsx
├── @analytics/          # 插槽
│   └── page.tsx
└── layout.tsx           # 使用上述两个插槽
tsx
// app/layout.tsx
export default function Layout({
	children,
	team,
	analytics,
}: {
	children: React.ReactNode;
	team: React.ReactNode;
	analytics: React.ReactNode;
}) {
	return (
		<>
			{children}
			{team}
			{analytics}
		</>
	);
}

Intercepting Routes

拦截路由

Intercept routes to show in modal:
app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx
└── (..)photo/           # Intercepts /photo/[id]
    └── [id]/
        └── page.tsx
拦截路由以模态框形式展示:
app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx
└── (..)photo/           # 拦截 /photo/[id]
    └── [id]/
        └── page.tsx

Layouts

布局

Root Layout (Required)

根布局(必填)

tsx
// app/layout.tsx
export default function RootLayout({
	children,
}: {
	children: React.ReactNode;
}) {
	return (
		<html lang="en">
			<body>{children}</body>
		</html>
	);
}
tsx
// app/layout.tsx
export default function RootLayout({
	children,
}: {
	children: React.ReactNode;
}) {
	return (
		<html lang="en">
			<body>{children}</body>
		</html>
	);
}

Nested Layouts

嵌套布局

tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
	children,
}: {
	children: React.ReactNode;
}) {
	return (
		<section>
			<nav>Dashboard Nav</nav>
			{children}
		</section>
	);
}
Layouts are:
  • Shared across multiple pages
  • Preserve state on navigation
  • Do not re-render on navigation
  • Can fetch data
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
	children,
}: {
	children: React.ReactNode;
}) {
	return (
		<section>
			<nav>控制台导航</nav>
			{children}
		</section>
	);
}
布局特性:
  • 在多个页面间共享
  • 导航时保留状态
  • 导航时不会重新渲染
  • 可进行数据获取

Server and Client Components

Server Components 与 Client Components

Server Components (Default)

Server Components(默认)

Components in
app/
are Server Components by default:
tsx
// app/page.tsx (Server Component)
async function getData() {
	const res = await fetch("https://api.example.com/data");
	return res.json();
}

export default async function Page() {
	const data = await getData();
	return <div>{data.title}</div>;
}
Benefits:
  • Fetch data on server
  • Access backend resources directly
  • Keep sensitive data on server
  • Reduce client-side JavaScript
  • Improve initial page load
Limitations:
  • Cannot use hooks (useState, useEffect)
  • Cannot use browser APIs
  • Cannot add event listeners
app/
目录下的组件默认是Server Components:
tsx
// app/page.tsx(Server Component)
async function getData() {
	const res = await fetch("https://api.example.com/data");
	return res.json();
}

export default async function Page() {
	const data = await getData();
	return <div>{data.title}</div>;
}
优势:
  • 在服务端获取数据
  • 可直接访问后端资源
  • 敏感数据保留在服务端
  • 减少客户端JavaScript体积
  • 提升初始页面加载速度
限制:
  • 无法使用钩子(useState、useEffect)
  • 无法使用浏览器API
  • 无法添加事件监听器

Client Components

Client Components

Mark components with
'use client'
directive:
tsx
// components/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
	const [count, setCount] = useState(0);

	return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
Use Client Components for:
  • Interactive UI (onClick, onChange)
  • State management (useState, useReducer)
  • Effects (useEffect, useLayoutEffect)
  • Browser APIs (localStorage, navigator)
  • Custom hooks
  • React class components
使用
'use client'
指令标记组件:
tsx
// components/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
	const [count, setCount] = useState(0);

	return <button onClick={() => setCount(count + 1)}>计数:{count}</button>;
}
Client Components 适用场景:
  • 交互式UI(onClick、onChange)
  • 状态管理(useState、useReducer)
  • 副作用(useEffect、useLayoutEffect)
  • 浏览器API(localStorage、navigator)
  • 自定义钩子
  • React类组件

Composition Pattern

组合模式

tsx
// app/page.tsx (Server Component)
import { ClientComponent } from "./client-component";

export default function Page() {
	return (
		<div>
			<h1>Server-rendered content</h1>
			<ClientComponent />
		</div>
	);
}
tsx
// app/page.tsx(Server Component)
import { ClientComponent } from "./client-component";

export default function Page() {
	return (
		<div>
			<h1>服务端渲染内容</h1>
			<ClientComponent />
		</div>
	);
}

Data Fetching

数据获取

Server Component Data Fetching

Server Components 数据获取

tsx
// app/posts/page.tsx
async function getPosts() {
	const res = await fetch("https://api.example.com/posts", {
		next: { revalidate: 3600 }, // Revalidate every hour
	});

	if (!res.ok) {
		throw new Error("Failed to fetch");
	}

	return res.json();
}

export default async function PostsPage() {
	const posts = await getPosts();

	return (
		<ul>
			{posts.map((post) => (
				<li key={post.id}>{post.title}</li>
			))}
		</ul>
	);
}
tsx
// app/posts/page.tsx
async function getPosts() {
	const res = await fetch("https://api.example.com/posts", {
		next: { revalidate: 3600 }, // 每小时重新验证一次
	});

	if (!res.ok) {
		throw new Error("获取数据失败");
	}

	return res.json();
}

export default async function PostsPage() {
	const posts = await getPosts();

	return (
		<ul>
			{posts.map((post) => (
				<li key={post.id}>{post.title}</li>
			))}
		</ul>
	);
}

Caching Strategies

缓存策略

Force Cache (Default):
tsx
fetch("https://api.example.com/data", { cache: "force-cache" });
No Store (Dynamic):
tsx
fetch("https://api.example.com/data", { cache: "no-store" });
Revalidate:
tsx
fetch("https://api.example.com/data", {
	next: { revalidate: 3600 }, // Seconds
});
Tag-based Revalidation:
tsx
fetch("https://api.example.com/data", {
	next: { tags: ["posts"] },
});

// Revalidate elsewhere:
import { revalidateTag } from "next/cache";
revalidateTag("posts");
强制缓存(默认):
tsx
fetch("https://api.example.com/data", { cache: "force-cache" });
不缓存(动态):
tsx
fetch("https://api.example.com/data", { cache: "no-store" });
重新验证:
tsx
fetch("https://api.example.com/data", {
	next: { revalidate: 3600 }, // 单位:秒
});
基于标签的重新验证:
tsx
fetch("https://api.example.com/data", {
	next: { tags: ["posts"] },
});

// 在其他地方触发重新验证:
import { revalidateTag } from "next/cache";
revalidateTag("posts");

Parallel Data Fetching

并行数据获取

tsx
async function getData() {
	const [posts, users] = await Promise.all([
		fetch("https://api.example.com/posts").then((r) => r.json()),
		fetch("https://api.example.com/users").then((r) => r.json()),
	]);

	return { posts, users };
}
tsx
async function getData() {
	const [posts, users] = await Promise.all([
		fetch("https://api.example.com/posts").then((r) => r.json()),
		fetch("https://api.example.com/users").then((r) => r.json()),
	]);

	return { posts, users };
}

Sequential Data Fetching

顺序数据获取

tsx
async function getData() {
	const post = await fetch(`https://api.example.com/posts/${id}`).then((r) =>
		r.json(),
	);
	const author = await fetch(
		`https://api.example.com/users/${post.authorId}`,
	).then((r) => r.json());

	return { post, author };
}
tsx
async function getData() {
	const post = await fetch(`https://api.example.com/posts/${id}`).then((r) =>
		r.json(),
	);
	const author = await fetch(
		`https://api.example.com/users/${post.authorId}`,
	).then((r) => r.json());

	return { post, author };
}

Route Handlers (API Routes)

路由处理器(API路由)

Basic Route Handler

基础路由处理器

tsx
// app/api/hello/route.ts
export async function GET(request: Request) {
	return Response.json({ message: "Hello" });
}

export async function POST(request: Request) {
	const body = await request.json();
	return Response.json({ received: body });
}
tsx
// app/api/hello/route.ts
export async function GET(request: Request) {
	return Response.json({ message: "你好" });
}

export async function POST(request: Request) {
	const body = await request.json();
	return Response.json({ received: body });
}

Dynamic Route Handler

动态路由处理器

tsx
// app/api/posts/[id]/route.ts
export async function GET(
	request: Request,
	{ params }: { params: { id: string } },
) {
	const post = await getPost(params.id);
	return Response.json(post);
}

export async function DELETE(
	request: Request,
	{ params }: { params: { id: string } },
) {
	await deletePost(params.id);
	return new Response(null, { status: 204 });
}
tsx
// app/api/posts/[id]/route.ts
export async function GET(
	request: Request,
	{ params }: { params: { id: string } },
) {
	const post = await getPost(params.id);
	return Response.json(post);
}

export async function DELETE(
	request: Request,
	{ params }: { params: { id: string } },
) {
	await deletePost(params.id);
	return new Response(null, { status: 204 });
}

Request Helpers

请求辅助工具

tsx
export async function GET(request: Request) {
	const { searchParams } = new URL(request.url);
	const id = searchParams.get("id");

	const cookies = request.headers.get("cookie");

	return Response.json({ id });
}
tsx
export async function GET(request: Request) {
	const { searchParams } = new URL(request.url);
	const id = searchParams.get("id");

	const cookies = request.headers.get("cookie");

	return Response.json({ id });
}

Response Types

响应类型

tsx
// JSON
return Response.json({ data: "value" });

// Text
return new Response("Hello", { headers: { "Content-Type": "text/plain" } });

// Redirect
return Response.redirect("https://example.com");

// Status codes
return new Response("Not Found", { status: 404 });
tsx
// JSON
return Response.json({ data: "值" });

// 文本
return new Response("你好", { headers: { "Content-Type": "text/plain" } });

// 重定向
return Response.redirect("https://example.com");

// 状态码
return new Response("未找到", { status: 404 });

Navigation

导航

Link Component

Link 组件

tsx
import Link from "next/link";

export default function Page() {
	return (
		<>
			<Link href="/about">About</Link>
			<Link href="/blog/post-1">Post 1</Link>
			<Link href={{ pathname: "/blog/[slug]", query: { slug: "post-1" } }}>
				Post 1 (alternative)
			</Link>
		</>
	);
}
tsx
import Link from "next/link";

export default function Page() {
	return (
		<>
			<Link href="/about">关于我们</Link>
			<Link href="/blog/post-1">文章1</Link>
			<Link href={{ pathname: "/blog/[slug]", query: { slug: "post-1" } }}>
				文章1(写法二)
			</Link>
		</>
	);
}

useRouter Hook (Client)

useRouter 钩子(客户端)

tsx
"use client";

import { useRouter } from "next/navigation";

export function NavigateButton() {
	const router = useRouter();

	return <button onClick={() => router.push("/dashboard")}>Dashboard</button>;
}
Router Methods:
  • router.push(href)
    - Navigate to route
  • router.replace(href)
    - Replace current history
  • router.refresh()
    - Refresh current route
  • router.back()
    - Navigate back
  • router.forward()
    - Navigate forward
  • router.prefetch(href)
    - Prefetch route
tsx
"use client";

import { useRouter } from "next/navigation";

export function NavigateButton() {
	const router = useRouter();

	return <button onClick={() => router.push("/dashboard")}>控制台</button>;
}
Router 方法:
  • router.push(href)
    - 导航至指定路由
  • router.replace(href)
    - 替换当前历史记录
  • router.refresh()
    - 刷新当前路由
  • router.back()
    - 返回上一页
  • router.forward()
    - 前进到下一页
  • router.prefetch(href)
    - 预获取路由

Programmatic Navigation (Server)

程序化导航(服务端)

tsx
import { redirect } from "next/navigation";

export default async function Page() {
	const session = await getSession();

	if (!session) {
		redirect("/login");
	}

	return <div>Protected content</div>;
}
tsx
import { redirect } from "next/navigation";

export default async function Page() {
	const session = await getSession();

	if (!session) {
		redirect("/login");
	}

	return <div>受保护内容</div>;
}

Metadata & SEO

元数据与SEO

Static Metadata

静态元数据

tsx
// app/page.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
	title: "My Page",
	description: "Page description",
	keywords: ["nextjs", "react"],
	openGraph: {
		title: "My Page",
		description: "Page description",
		images: ["/og-image.jpg"],
	},
	twitter: {
		card: "summary_large_image",
		title: "My Page",
		description: "Page description",
		images: ["/twitter-image.jpg"],
	},
};

export default function Page() {
	return <div>Content</div>;
}
tsx
// app/page.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
	title: "我的页面",
	description: "页面描述",
	keywords: ["nextjs", "react"],
	openGraph: {
		title: "我的页面",
		description: "页面描述",
		images: ["/og-image.jpg"],
	},
	twitter: {
		card: "summary_large_image",
		title: "我的页面",
		description: "页面描述",
		images: ["/twitter-image.jpg"],
	},
};

export default function Page() {
	return <div>内容</div>;
}

Dynamic Metadata

动态元数据

tsx
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
	const post = await getPost(params.slug);

	return {
		title: post.title,
		description: post.excerpt,
		openGraph: {
			title: post.title,
			description: post.excerpt,
			images: [post.coverImage],
		},
	};
}
tsx
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
	const post = await getPost(params.slug);

	return {
		title: post.title,
		description: post.excerpt,
		openGraph: {
			title: post.title,
			description: post.excerpt,
			images: [post.coverImage],
		},
	};
}

Metadata Files

元数据文件

  • favicon.ico
    ,
    icon.png
    ,
    apple-icon.png
    - Favicons
  • opengraph-image.png
    ,
    twitter-image.png
    - Social images
  • robots.txt
    - Robots file
  • sitemap.xml
    - Sitemap
  • favicon.ico
    ,
    icon.png
    ,
    apple-icon.png
    - 网站图标
  • opengraph-image.png
    ,
    twitter-image.png
    - 社交分享图片
  • robots.txt
    - 爬虫规则文件
  • sitemap.xml
    - 站点地图

Image Optimization

图片优化

Image Component

Image 组件

tsx
import Image from "next/image";

export default function Page() {
	return (
		<>
			{/* Local image */}
			<Image src="/profile.png" alt="Profile" width={500} height={500} />

			{/* Remote image */}
			<Image
				src="https://example.com/image.jpg"
				alt="Remote"
				width={500}
				height={500}
			/>

			{/* Responsive fill */}
			<div style={{ position: "relative", width: "100%", height: "400px" }}>
				<Image src="/hero.jpg" alt="Hero" fill style={{ objectFit: "cover" }} />
			</div>

			{/* Priority loading */}
			<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
		</>
	);
}
Image Props:
  • src
    - Image path (local or URL)
  • alt
    - Alt text (required)
  • width
    ,
    height
    - Dimensions (required unless fill)
  • fill
    - Fill parent container
  • sizes
    - Responsive sizes
  • quality
    - 1-100 (default 75)
  • priority
    - Preload image
  • placeholder
    - 'blur-sm' | 'empty'
  • blurDataURL
    - Data URL for blur
tsx
import Image from "next/image";

export default function Page() {
	return (
		<>
			{/* 本地图片 */}
			<Image src="/profile.png" alt="头像" width={500} height={500} />

			{/* 远程图片 */}
			<Image
				src="https://example.com/image.jpg"
				alt="远程图片"
				width={500}
				height={500}
			/>

			{/* 响应式填充 */}
			<div style={{ position: "relative", width: "100%", height: "400px" }}>
				<Image src="/hero.jpg" alt="首页横幅" fill style={{ objectFit: "cover" }} />
			</div>

			{/* 优先加载 */}
			<Image src="/hero.jpg" alt="首页横幅" width={1200} height={600} priority />
		</>
	);
}
Image 组件属性:
  • src
    - 图片路径(本地或URL)
  • alt
    - 替代文本(必填)
  • width
    ,
    height
    - 尺寸(使用fill时可选)
  • fill
    - 填充父容器
  • sizes
    - 响应式尺寸
  • quality
    - 图片质量1-100(默认75)
  • priority
    - 预加载图片
  • placeholder
    - 占位符:'blur-sm' | 'empty'
  • blurDataURL
    - 模糊占位符的Data URL

Remote Image Configuration

远程图片配置

js
// next.config.js
module.exports = {
	images: {
		remotePatterns: [
			{
				protocol: "https",
				hostname: "example.com",
				pathname: "/images/**",
			},
		],
	},
};
js
// next.config.js
module.exports = {
	images: {
		remotePatterns: [
			{
				protocol: "https",
				hostname: "example.com",
				pathname: "/images/**",
			},
		],
	},
};

Font Optimization

字体优化

Google Fonts

Google Fonts

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

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

const robotoMono = Roboto_Mono({
	subsets: ["latin"],
	display: "swap",
	variable: "--font-roboto-mono",
});

export default function RootLayout({ children }) {
	return (
		<html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
			<body>{children}</body>
		</html>
	);
}
tsx
// app/layout.tsx
import { Inter, Roboto_Mono } from "next/font/google";

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

const robotoMono = Roboto_Mono({
	subsets: ["latin"],
	display: "swap",
	variable: "--font-roboto-mono",
});

export default function RootLayout({ children }) {
	return (
		<html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
			<body>{children}</body>
		</html>
	);
}

Local Fonts

本地字体

tsx
import localFont from "next/font/local";

const myFont = localFont({
	src: "./fonts/my-font.woff2",
	display: "swap",
	variable: "--font-my-font",
});
tsx
import localFont from "next/font/local";

const myFont = localFont({
	src: "./fonts/my-font.woff2",
	display: "swap",
	variable: "--font-my-font",
});

Loading States

加载状态

Loading File

加载状态文件

tsx
// app/dashboard/loading.tsx
export default function Loading() {
	return <div>Loading dashboard...</div>;
}
tsx
// app/dashboard/loading.tsx
export default function Loading() {
	return <div>正在加载控制台...</div>;
}

Streaming with Suspense

结合Suspense的流式渲染

tsx
// app/page.tsx
import { Suspense } from "react";

async function Posts() {
	const posts = await getPosts();
	return (
		<ul>
			{posts.map((p) => (
				<li key={p.id}>{p.title}</li>
			))}
		</ul>
	);
}

export default function Page() {
	return (
		<div>
			<h1>My Posts</h1>
			<Suspense fallback={<div>Loading posts...</div>}>
				<Posts />
			</Suspense>
		</div>
	);
}
tsx
// app/page.tsx
import { Suspense } from "react";

async function Posts() {
	const posts = await getPosts();
	return (
		<ul>
			{posts.map((p) => (
				<li key={p.id}>{p.title}</li>
			))}
		</ul>
	);
}

export default function Page() {
	return (
		<div>
			<h1>我的文章</h1>
			<Suspense fallback={<div>正在加载文章...</div>}>
				<Posts />
			</Suspense>
		</div>
	);
}

Error Handling

错误处理

Error File

错误状态文件

tsx
// 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>
	);
}
tsx
// app/error.tsx
"use client";

export default function Error({
	error,
	reset,
}: {
	error: Error & { digest?: string };
	reset: () => void;
}) {
	return (
		<div>
			<h2>出错了!</h2>
			<p>{error.message}</p>
			<button onClick={() => reset()}>重试</button>
		</div>
	);
}

Global Error

全局错误处理

tsx
// app/global-error.tsx
"use client";

export default function GlobalError({
	error,
	reset,
}: {
	error: Error & { digest?: string };
	reset: () => void;
}) {
	return (
		<html>
			<body>
				<h2>Something went wrong!</h2>
				<button onClick={() => reset()}>Try again</button>
			</body>
		</html>
	);
}
tsx
// app/global-error.tsx
"use client";

export default function GlobalError({
	error,
	reset,
}: {
	error: Error & { digest?: string };
	reset: () => void;
}) {
	return (
		<html>
			<body>
				<h2>出错了!</h2>
				<button onClick={() => reset()}>重试</button>
			</body>
		</html>
	);
}

Not Found

404 页面

tsx
// app/not-found.tsx
export default function NotFound() {
	return (
		<div>
			<h2>404 - Not Found</h2>
			<p>Could not find requested resource</p>
		</div>
	);
}

// Trigger programmatically
import { notFound } from "next/navigation";

export default async function Page({ params }) {
	const post = await getPost(params.id);

	if (!post) {
		notFound();
	}

	return <div>{post.title}</div>;
}
tsx
// app/not-found.tsx
export default function NotFound() {
	return (
		<div>
			<h2>404 - 页面未找到</h2>
			<p>无法找到请求的资源</p>
		</div>
	);
}

// 程序化触发404
import { notFound } from "next/navigation";

export default async function Page({ params }) {
	const post = await getPost(params.id);

	if (!post) {
		notFound();
	}

	return <div>{post.title}</div>;
}

Middleware

中间件

tsx
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
	// Authentication check
	const token = request.cookies.get("token");

	if (!token) {
		return NextResponse.redirect(new URL("/login", request.url));
	}

	// Add custom header
	const response = NextResponse.next();
	response.headers.set("x-custom-header", "value");

	return response;
}

export const config = {
	matcher: ["/dashboard/:path*", "/api/:path*"],
};
tsx
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
	// 身份验证检查
	const token = request.cookies.get("token");

	if (!token) {
		return NextResponse.redirect(new URL("/login", request.url));
	}

	// 添加自定义响应头
	const response = NextResponse.next();
	response.headers.set("x-custom-header", "value");

	return response;
}

export const config = {
	matcher: ["/dashboard/:path*", "/api/:path*"],
};

Environment Variables

环境变量

env
undefined
env
undefined

.env.local

.env.local

DATABASE_URL=postgresql://... NEXT_PUBLIC_API_URL=https://api.example.com

```tsx
// Server-side only
const dbUrl = process.env.DATABASE_URL;

// Client and server (NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
DATABASE_URL=postgresql://... NEXT_PUBLIC_API_URL=https://api.example.com

```tsx
// 仅服务端可用
const dbUrl = process.env.DATABASE_URL;

// 客户端与服务端均可用(需添加NEXT_PUBLIC_前缀)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;

Configuration

配置

next.config.js

next.config.js

js
/** @type {import('next').NextConfig} */
const nextConfig = {
	// React strict mode
	reactStrictMode: true,

	// Image domains
	images: {
		remotePatterns: [{ protocol: "https", hostname: "example.com" }],
	},

	// Redirects
	async redirects() {
		return [
			{
				source: "/old-page",
				destination: "/new-page",
				permanent: true,
			},
		];
	},

	// Rewrites
	async rewrites() {
		return [
			{
				source: "/api/:path*",
				destination: "https://api.example.com/:path*",
			},
		];
	},

	// Headers
	async headers() {
		return [
			{
				source: "/(.*)",
				headers: [{ key: "X-Frame-Options", value: "DENY" }],
			},
		];
	},

	// Environment variables
	env: {
		CUSTOM_KEY: "value",
	},
};

module.exports = nextConfig;
js
/** @type {import('next').NextConfig} */
const nextConfig = {
	// React严格模式
	reactStrictMode: true,

	// 图片域名配置
	images: {
		remotePatterns: [{ protocol: "https", hostname: "example.com" }],
	},

	// 重定向规则
	async redirects() {
		return [
			{
				source: "/old-page",
				destination: "/new-page",
				permanent: true,
			},
		];
	},

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

	// 响应头配置
	async headers() {
		return [
			{
				source: "/(.*)",
				headers: [{ key: "X-Frame-Options", value: "DENY" }],
			},
		];
	},

	// 环境变量
	env: {
		CUSTOM_KEY: "value",
	},
};

module.exports = nextConfig;

Best Practices

最佳实践

  1. Use Server Components: Default to Server Components, use Client Components only when needed
  2. Optimize Images: Always use
    next/image
    for automatic optimization
  3. Metadata: Set proper metadata for SEO
  4. Loading States: Provide loading UI with Suspense
  5. Error Handling: Implement error boundaries
  6. Route Handlers: Use for API endpoints instead of separate backend
  7. Caching: Leverage built-in caching strategies
  8. Layouts: Use nested layouts to share UI
  9. TypeScript: Enable TypeScript for type safety
  10. Performance: Use
    priority
    for above-fold images, lazy load below-fold
  1. 使用Server Components:默认使用Server Components,仅在需要时使用Client Components
  2. 优化图片:始终使用
    next/image
    组件以实现自动优化
  3. 元数据配置:设置合适的元数据以优化SEO
  4. 加载状态:使用Suspense提供加载UI
  5. 错误处理:实现错误边界
  6. 路由处理器:使用路由处理器替代独立后端API
  7. 缓存策略:利用内置缓存策略
  8. 布局使用:使用嵌套布局共享UI
  9. TypeScript:启用TypeScript以保证类型安全
  10. 性能优化:首屏图片使用
    priority
    属性,首屏以下图片懒加载

Common Patterns

常见模式

Protected Routes

受保护路由

tsx
// app/dashboard/layout.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";

export default async function DashboardLayout({ children }) {
	const session = await getSession();

	if (!session) {
		redirect("/login");
	}

	return <>{children}</>;
}
tsx
// app/dashboard/layout.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";

export default async function DashboardLayout({ children }) {
	const session = await getSession();

	if (!session) {
		redirect("/login");
	}

	return <>{children}</>;
}

Data Mutations (Server Actions)

数据变更(Server Actions)

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

import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
	const title = formData.get("title");

	await db.post.create({ data: { title } });

	revalidatePath("/posts");
}

// app/posts/new/page.tsx
import { createPost } from "@/app/actions";

export default function NewPost() {
	return (
		<form action={createPost}>
			<input name="title" type="text" required />
			<button type="submit">Create</button>
		</form>
	);
}
tsx
// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
	const title = formData.get("title");

	await db.post.create({ data: { title } });

	revalidatePath("/posts");
}

// app/posts/new/page.tsx
import { createPost } from "@/app/actions";

export default function NewPost() {
	return (
		<form action={createPost}>
			<input name="title" type="text" required />
			<button type="submit">创建文章</button>
		</form>
	);
}

Static Generation

静态生成

tsx
// Generate static params for dynamic routes
export async function generateStaticParams() {
	const posts = await getPosts();

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

export default async function Post({ params }) {
	const post = await getPost(params.slug);
	return <article>{post.content}</article>;
}
tsx
// 为动态路由生成静态参数
export async function generateStaticParams() {
	const posts = await getPosts();

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

export default async function Post({ params }) {
	const post = await getPost(params.slug);
	return <article>{post.content}</article>;
}

Deployment

部署

Vercel (Recommended)

Vercel(推荐)

bash
undefined
bash
undefined

Install Vercel CLI

安装Vercel CLI

npm i -g vercel
npm i -g vercel

Deploy

部署

vercel
undefined
vercel
undefined

Self-Hosting

自托管

bash
undefined
bash
undefined

Build

构建生产版本

npm run build
npm run build

Start production server

启动生产服务器

npm start

**Requirements:**

- Node.js 18.17 or later
- `output: 'standalone'` in next.config.js (optional, reduces size)
npm start

**要求:**

- Node.js 18.17或更高版本
- next.config.js中配置`output: 'standalone'`(可选,可减小包体积)

Docker

Docker

dockerfile
FROM node:18-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base 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"]
dockerfile
FROM node:18-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base 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"]

Troubleshooting

故障排查

Common Issues

常见问题

  1. Hydration errors
    • Ensure server and client render same content
    • Check for browser-only code in Server Components
    • Verify no conditional rendering based on browser APIs
  2. Images not loading
    • Add remote domains to
      next.config.js
    • Check image paths (use leading
      /
      for public)
    • Verify width/height provided
  3. API route 404
    • Check file is named
      route.ts/js
      not
      index.ts
    • Verify export named GET/POST not default export
    • Ensure in
      app/api/
      directory
  4. "use client" errors
    • Add
      'use client'
      to components using hooks
    • Import Client Components in Server Components, not vice versa
    • Check event handlers have
      'use client'
  5. Metadata not updating
    • Clear browser cache
    • Check metadata export is named correctly
    • Verify async generateMetadata returns Promise<Metadata>
  1. Hydration错误
    • 确保服务端与客户端渲染内容一致
    • 检查Server Components中是否包含仅浏览器可用的代码
    • 确认没有基于浏览器API的条件渲染
  2. 图片无法加载
    • next.config.js
      中添加远程图片域名
    • 检查图片路径(静态资源需以
      /
      开头)
    • 确认已提供width/height参数
  3. API路由404
    • 检查文件命名为
      route.ts/js
      而非
      index.ts
    • 确认导出的是GET/POST等命名函数而非默认导出
    • 确保文件位于
      app/api/
      目录下
  4. "use client"错误
    • 为使用钩子的组件添加
      'use client'
      指令
    • 在Server Components中导入Client Components,反之则不行
    • 检查事件处理函数所在组件是否添加了
      'use client'
  5. 元数据未更新
    • 清除浏览器缓存
    • 检查元数据导出的命名是否正确
    • 确认异步generateMetadata返回的是Promise<Metadata>

Resources

资源

Implementation Checklist

实施检查清单

When building with Next.js:
  • Create project with
    create-next-app
  • Configure TypeScript and ESLint
  • Set up root layout with metadata
  • Implement routing structure
  • Add loading and error states
  • Configure image optimization
  • Set up font optimization
  • Implement data fetching patterns
  • Add API routes as needed
  • Configure environment variables
  • Set up middleware if needed
  • Optimize for production build
  • Test in production mode
  • Configure deployment platform
  • Set up monitoring and analytics
使用Next.js构建应用时,请完成以下检查:
  • 使用
    create-next-app
    创建项目
  • 配置TypeScript与ESLint
  • 设置带元数据的根布局
  • 实现路由结构
  • 添加加载与错误状态
  • 配置图片优化
  • 设置字体优化
  • 实现数据获取模式
  • 根据需要添加API路由
  • 配置环境变量
  • 按需配置中间件
  • 优化生产构建
  • 在生产模式下测试
  • 配置部署平台
  • 设置监控与分析