Next.js 简述 - React 全栈框架

289 阅读10分钟

Next.js 是由 Vercel 开发的基于 React 的全栈 Web 应用框架。它提供了生产环境所需的所有功能,包括服务端渲染(SSR)、静态站点生成(SSG)、API 路由等,让开发者能够轻松构建高性能的 Web 应用。

关于路由系统:Next.js 有两种路由系统 - Pages Router(传统方式,基于 pages/ 目录)和 App Router(Next.js 13+ 推荐,基于 app/ 目录)。本文主要介绍 App Router,这是当前推荐的方式,基于 React Server Components,提供更好的性能和开发体验。

版本说明:本文基于 Next.js 15+ 和 App Router 编写。部分特性在早期版本中可能不可用或行为不同。

Next.js 的核心功能

1. 多种渲染策略

Next.js 支持多种页面渲染策略,不同场景下性能表现不同。不同的组件可以使用不同的渲染策略,也可以混合使用。

  1. 静态站点生成 (SSG Static Site Generator): 构建时生成静态 HTML,直接提供给用户,性能最佳,默认的方式
  2. 增量静态再生成 (ISR Incremental Static Regeneration): 构建时生成静态页面,运行时可在后台定期重新生成,兼顾性能和实时性
  3. 服务端渲染 (SSR Server-Side Rendering): 每次请求时在服务器动态生成,数据最新
  4. 客户端渲染 (CSR Client-Side Rendering): 适用于 Client Component,在浏览器中动态渲染

App Router 通过以下多种机制来决定使用哪种渲染策略,下面是常用的几种方式

  • 路由段配置 (Route Segment Config) - 如 dynamic, revalidate, fetchCache 等导出常量
  • 动态 API (Dynamic APIs) - Next.js 提供的用于访问请求时数据的异步函数,如 cookies(), headers(), connection() 等(Next.js 15+ 这些为异步函数)。使用这些 API 会让路由自动选择动态渲染(Dynamic Rendering)
  • Fetch 缓存选项 - fetch() 默认不缓存。可以通过 cache: 'force-cache' 开启缓存,或使用 next.revalidate 配置定期重新验证

2. 基于文件系统的路由

Next.js 使用基于文件系统的路由机制,无需额外配置路由。可用于 页面请求 和 API 请求

app/
  layout.js         → 根布局
  page.js           → /
  about/
    page.js         → /about
  blog/
    page.js         → /blog
    [id]/
      page.js       → /blog/:id (动态路由)
  api/
    users/
      route.js      → /api/users (API 路由)

3. 内置 CSS 支持

支持多种 CSS 方案:

  • CSS Modules
  • Sass/SCSS
  • CSS-in-JS (styled-components, emotion 等)
  • Tailwind CSS

4. 内置性能优化

Next.js 提供了多种内置的性能优化功能,开箱即用:

代码和构建优化

  • 代码分割(Code Splitting):每个页面自动分割为独立的 JavaScript 包,只加载当前页面需要的代码
  • Tree Shaking:构建时自动移除未使用的代码
  • 压缩(Minification):自动压缩 JavaScript、CSS 和 HTML
  • 预加载(Prefetching):自动预加载可见链接的页面代码,提升导航速度
  • 快速刷新(Fast Refresh):开发时修改代码后自动更新,保持组件状态,无需刷新页面

图片和字体优化

  • 图片优化Image 组件自动优化图片格式(WebP、AVIF)、尺寸和懒加载
  • 字体优化:自动内联字体 CSS,消除字体加载闪烁(FOIT/FOUT)
  • 响应式图片:根据设备自动选择合适尺寸的图片

渲染优化

  • Server Components:默认在服务器渲染,减少客户端 JavaScript 包大小
  • 流式渲染(Streaming):页面内容可以逐步发送,用户更快看到内容
  • 并行数据获取:Server Components 中可以并行获取多个数据源
  • 增量静态再生成(ISR):静态页面可以在后台定期更新,兼顾性能和实时性

缓存策略

  • 自动静态优化:没有动态数据的页面自动生成为静态 HTML
  • 全路由缓存:构建时缓存渲染结果
  • 数据缓存fetch 请求默认缓存,避免重复请求
  • 客户端路由缓存:导航时缓存页面数据,实现即时导航

性能监控

  • Web Vitals 支持:内置 Core Web Vitals 监控
  • 性能分析工具:提供 next/bundle-analyzer 分析包大小

示例:性能对比(具体数值因应用而异)

传统 React SPA:
- 首次加载:下载全部 JS(可能较大,如 500KB+)
- 首屏渲染:空白页 → JS 加载 → React 渲染 → 显示内容(通常较慢)
- SEO:搜索引擎只能看到空白 HTML

Next.js (App Router):
- 首次加载:预渲染的 HTML + 必需的 JS(通常更小)
- 首屏渲染:立即显示内容(First Contentful Paint 更快)
- SEO:搜索引擎直接看到完整内容

关键优势:

  1. 更快的首屏渲染:用户立即看到内容
  2. 更小的 JavaScript 包:Server Components 代码不发送到客户端
  3. 更好的 SEO:搜索引擎能抓取完整的 HTML
  4. 零配置:大部分优化自动生效,无需手动配置

如何使用 Next.js

创建项目

使用 create-next-app 快速创建项目:

npx create-next-app@latest my-app
cd my-app
npm run dev

访问 http://localhost:3000 即可看到应用。

项目结构

在 App Router 中,文件名有特殊含义,下面是典型项目结构:

app/
  layout.js         → 根布局(必需)
  page.js           → 首页 (/)
  loading.js        → 加载状态
  error.js          → 错误处理
  not-found.js      → 404 页面
  about/
    page.js         → /about
  blog/
    page.js         → /blog
    [id]/
      page.js       → /blog/:id (动态路由)
  api/
    users/
      route.js      → /api/users (API 端点)
文件名用途说明
page.js页面组件定义可访问的路由,必需
layout.js布局组件包裹所有子路由,可嵌套
loading.js加载状态显示 Suspense 边界,数据加载时展示
error.js错误处理Error Boundary,捕获子路由错误
not-found.js404 页面处理找不到的路由
template.js模板组件类似 layout,但每次导航都重新创建实例
route.jsAPI 路由定义 HTTP 请求处理器 (GET/POST/PUT 等)
default.js平行路由默认页处理平行路由加载失败时的回退

基本页面示例

1. Server Component(默认)

默认情况下,所有组件都是 Server Component,在服务器上渲染:

// app/posts/page.js
async function getPosts() {
  // fetch 默认不缓存,可以添加 { cache: 'force-cache' } 开启缓存
  const res = await fetch("https://api.example.com/posts");
  return res.json();
}

// 默认是 Server Component,在服务器执行
export default async function PostsPage() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

2. Client Component

需要交互(useState、useEffect 等)时,使用 'use client' 标记:

// app/components/Counter.js
"use client"; // 标记为 Client Component

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

3. 动态路由

使用 [参数名] 创建动态路由:

// app/blog/[id]/page.js
export default async function BlogPost({ params }) {
  const { id } = params;
  const post = await fetch(`https://api.example.com/posts/${id}`).then((r) => r.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Next.js 基本原理

1. 渲染流程

Next.js 的核心是其灵活的渲染策略:

用户请求  Next.js Server
    
判断页面类型
    
├─ SSG: 返回预生成的 HTML (构建时生成)
├─ ISR: 检查缓存  返回缓存 / 后台重新生成
├─ SSR: 服务器渲染  生成 HTML  返回
└─ CSR: 返回基础 HTML  客户端渲染
    
返回给客户端
    
React Hydration (水合)
    
交互式应用

2. 页面从加载到交互的完整过程

当用户访问一个 Next.js (App Router) 页面时,会经历以下阶段:

阶段 1: 服务器渲染 Server Components

// app/blog/page.js
// ========== Server Component(只在服务器运行)==========
async function getData() {
  // 可以直接访问数据库、读取文件等
  const data = await db.query("SELECT * FROM posts");
  return data;
}

export default async function BlogPage() {
  const data = await getData();
  // 这个组件在服务器渲染完成后,
  // 只有渲染结果(HTML)会发送到客户端
  // 这段代码本身不会发送到浏览器
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
      {/* 需要交互的部分使用 Client Component */}
      <LikeButton />
    </div>
  );
}

阶段 2: 生成并发送响应

服务器生成并发送:

  • Server Component 渲染后的 HTML(完整内容)
  • RSC Payload(React Server Component Payload)- 服务端组件的序列化表示
  • Client Component 的 JavaScript 代码
  • 必要的 React 运行时

阶段 3: 浏览器显示 + Hydration

// app/components/LikeButton.js
// ========== Client Component(在浏览器运行)==========
"use client";

import { useState } from "react";

export default function LikeButton() {
  const [count, setCount] = useState(0);
  // 这段代码会发送到浏览器并执行
  return <button onClick={() => setCount(count + 1)}>点赞 {count}</button>;
}

完整时间线

用户访问页面
    ↓
[服务器] 执行 Server Component (getData)
    ↓
[服务器] 渲染 Server Component → 生成 HTML[服务器] 生成 RSC Payload(Server Component 的序列化表示)
    ↓
[网络] 发送 HTML + RSC Payload + Client Component JS
    ↓
[浏览器] 显示 HTML(用户立即看到内容)
    ↓
[浏览器] 下载并执行 Client Component JS
    ↓
[浏览器] Hydration(激活 Client Components)
    ↓
[浏览器] 页面完全可交互(按钮可以点击)

关键优势

  • Server Component 代码不会发送到客户端:减少 JavaScript 包大小
  • 更快的首屏渲染:用户立即看到完整的 HTML
  • 更好的 SEO:搜索引擎能抓取完整内容
  • 服务器能力:可以直接访问数据库、文件系统等

3. 代码分割机制

Next.js 自动进行代码分割:

// app/page.js
import dynamic from "next/dynamic";

// 动态导入,只在需要时加载
const HeavyComponent = dynamic(() => import("./components/Heavy"), {
  loading: () => <p>Loading...</p>,
  ssr: false, // 禁用服务端渲染
});

export default function Home() {
  return <HeavyComponent />;
}

原理

  1. 自动分割:每个页面自动成为一个代码块
  2. 动态导入:使用 dynamic()import() 实现按需加载
  3. 预加载:Next.js 自动预加载可见链接的页面代码

4. Server Components 原理

React Server Components 是 Next.js 13+ 的核心:

服务器                              客户端
   ↓
渲染 Server Component
   ↓
生成 RSC Payload(序列化表示)
   ↓
发送到客户端 ──────────────────→ 接收 RSC Payload
                                    ↓
                               重建 React 树
                                    ↓
                               Hydrate Client Components
                                    ↓
                               完整的交互式应用

关键优势

  • Server Component 的代码不会发送到客户端
  • 可以直接访问数据库或文件系统
  • 减少 JavaScript bundle 大小
  • 提升初始加载性能

5. 静态优化

Next.js 会自动分析页面并进行静态优化。对于不需要动态数据的页面,Next.js 会自动在构建时生成静态 HTML:

// app/about/page.js
// 没有 fetch 或动态函数,自动静态优化
export default function About() {
  return <div>About Page</div>;
}

构建输出示例

Route (app)                              Size     First Load JS
┌ ○ /                                   1.2 kB          85 kB
├ ○ /about                              850 B           83 kB
├ ○ /blog                               2.1 kB          87 kB
├ ƒ /blog/[id]                          1.5 kB          86 kB
└ λ /dashboard                          1.8 kB          86 kB

○ (Static)   静态生成 (SSG)
ƒ (Dynamic)  动态路由 + SSG/ISR
λ (Server)   服务端渲染 (SSR)

性能优化建议

使用 Image 组件优化图片

Next.js 的 <Image> 组件提供自动图片优化,包括格式转换、尺寸优化和懒加载。

属性说明示例
src图片路径(本地路径、远程 URL 或导入)src="/profile.png"
alt替代文本(必需,用于无障碍和 SEO)alt="Picture of the author"
width图片宽度(像素)width={500}
height图片高度(像素)height={500}
fill填充父容器fill={true}
sizes响应式尺寸(配合 fill 使用)sizes="(max-width: 768px) 100vw, 33vw"
quality图片质量(1-100,默认 75)quality={80}
preload是否预加载(替代旧的 prioritypreload={true}
placeholder占位符类型placeholder="blur"
loading加载策略(lazyeagerloading="eager"
styleCSS 样式style={{ borderRadius: "50%" }}

性能优化建议

// 1. LCP(最大内容绘制)图片:使用 preload
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} preload={true} />

// 2. 响应式图片:使用 sizes 属性
<Image
  src="/photo.jpg"
  alt="Photo"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

// 3. 低质量占位符:使用 placeholder
<Image
  src="/photo.jpg"
  alt="Photo"
  width={500}
  height={500}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

// 4. 控制图片质量(降低文件大小)
<Image src="/photo.jpg" alt="Photo" width={500} height={500} quality={75} />

使用 Link 预加载

import Link from "next/link";

export default function Nav() {
  return (
    <Link href="/about" prefetch={true}>
      About
    </Link>
  );
}

生态及参考资源

Next.js 拥有丰富的生态系统:

  • Vercel: 官方托管平台,零配置部署
  • next-auth: 身份认证解决方案
  • SWR: 数据获取 Hook
  • next-i18next: 国际化
  • next-seo: SEO 优化
  • next-pwa: PWA 支持

参考