服务端渲染¶
📚 章节目标¶
本章节将全面介绍服务端渲染的各种技术和方案,包括Next.js、Nuxt.js、SSR、SSG、ISR等,帮助学习者掌握服务端渲染的核心方法。
学习目标¶
- 理解服务端渲染的核心概念
- 掌握Next.js服务端渲染
- 掌握Nuxt.js服务端渲染
- 理解SSR、SSG、ISR的区别
- 掌握服务端渲染优化
- 理解服务端渲染最佳实践
🌐 服务端渲染概述¶
1. 什么是服务端渲染¶
JavaScript
// 服务端渲染 (SSR)
// 在服务器上渲染HTML,然后发送给客户端
// 优点:
// 1. 更快的首屏加载速度
// 2. 更好的SEO
// 3. 更好的社交媒体分享
// 客户端渲染 (CSR)
// 在浏览器上渲染HTML
// 优点:
// 1. 更好的交互体验
// 2. 更小的服务器负载
// 3. 更容易实现复杂交互
// 混合渲染 (SSR + CSR)
// 结合SSR和CSR的优点
// 首屏使用SSR,后续交互使用CSR
2. 渲染模式对比¶
JavaScript
// SSR (Server-Side Rendering)
// 每次请求都在服务器上渲染
// 适合:动态内容、个性化内容
// SSG (Static Site Generation)
// 构建时生成静态HTML
// 适合:静态内容、博客、文档
// ISR (Incremental Static Regeneration)
// 静态生成 + 按需更新
// 适合:内容不经常变化但需要更新的页面
// CSR (Client-Side Rendering)
// 在浏览器上渲染
// 适合:管理后台、SPA应用
⚛️ Next.js服务端渲染¶
1. Next.js基础¶
1.1 基本配置¶
JavaScript
// 安装Next.js
npx create-next-app@latest my-app
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// swcMinify 已在 Next.js 15 中默认启用且不再需要显式设置
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'example.com' },
],
},
};
module.exports = nextConfig;
1.2 页面路由¶
JavaScript
// pages/index.js
export default function Home() {
return (
<div>
<h1>Welcome to Next.js!</h1>
<p>Get started by editing pages/index.js</p>
</div>
);
}
// pages/about.js
export default function About() {
return (
<div>
<h1>About</h1>
<p>This is the about page</p>
</div>
);
}
// 动态路由
// pages/users/[id].js
export default function User({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/users');
const users = await res.json();
return {
paths: users.map((user) => ({
params: { id: user.id.toString() },
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/users/${params.id}`);
const user = await res.json();
return {
props: { user },
};
}
2. Next.js数据获取¶
2.1 getServerSideProps¶
JavaScript
// getServerSideProps - 每次请求都执行
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
};
}
export default function Page({ data }) {
return (
<div>
<h1>Data from Server</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
2.2 getStaticProps¶
JavaScript
// getStaticProps - 构建时执行
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
revalidate: 60, // 60秒后重新生成
};
}
export default function Page({ data }) {
return (
<div>
<h1>Data from Build Time</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
2.3 getInitialProps¶
⚠️ 已废弃:
getInitialProps是 Next.js 早期 API,在 Pages Router 中已被getServerSideProps(SSR)和getStaticProps(SSG)取代。App Router 中请使用 Server Components 或fetch+cache。
JavaScript
// getInitialProps - 兼容旧版本(Legacy API,不推荐在新项目中使用)
class Page extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { data };
}
render() {
return (
<div>
<h1>Data from getInitialProps</h1>
<pre>{JSON.stringify(this.props.data, null, 2)}</pre>
</div>
);
}
}
export default Page;
3. Next.js API路由¶
JavaScript
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from Next.js API' });
}
// pages/api/users/[id].js
export default async function handler(req, res) {
const { id } = req.query;
const user = await fetchUser(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.status(200).json(user);
}
🚀 Next.js App Router(推荐)¶
说明: Next.js 13.4+ 引入的 App Router 现已成为推荐方案。与 Pages Router 相比,App Router 基于 React Server Components,提供更好的性能和开发体验。新项目应优先使用 App Router。
1. App Router 基础¶
1.1 目录结构¶
Text Only
app/
├── layout.tsx # 根布局(必需)
├── page.tsx # 首页 → /
├── loading.tsx # 加载UI(Suspense boundary)
├── error.tsx # 错误UI(Error boundary)
├── not-found.tsx # 404页面
├── about/
│ └── page.tsx # /about
├── users/
│ ├── page.tsx # /users
│ └── [id]/
│ └── page.tsx # /users/:id
└── api/
└── hello/
└── route.ts # API路由 → /api/hello
1.2 布局与页面¶
TSX
// === 类型定义 ===
interface Post { // interface定义类型契约
id: string;
title: string;
slug: string;
excerpt: string;
content: string;
}
interface User {
id: string;
name: string;
email: string;
}
// app/layout.tsx — 根布局(Server Component)
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'My App',
description: 'Built with Next.js App Router',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body className={inter.className}>{children}</body>
</html>
);
}
// app/page.tsx — 首页(默认 Server Component)
export default async function HomePage() {
// 可以直接 await 异步操作,无需 getServerSideProps
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }, // ISR: 60秒后重新验证
}).then(res => res.json()) as Post[];
return (
<main>
<h1>Latest Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
2. 数据获取(App Router 方式)¶
TSX
// === Server Component 直接 async/await(替代 getServerSideProps)===
// app/users/page.tsx
async function getUsers(): Promise<User[]> { // Promise异步操作容器:pending→fulfilled/rejected
const res = await fetch('https://api.example.com/users', {
cache: 'no-store', // SSR: 每次请求都获取最新数据
});
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// === 静态生成 + ISR(替代 getStaticProps + revalidate)===
// app/blog/[slug]/page.tsx
async function getPost(slug: string): Promise<Post> {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 }, // ISR: 1小时后重新生成
});
return res.json();
}
// 替代 getStaticPaths
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return <article><h1>{post.title}</h1><p>{post.content}</p></article>;
}
// > **📝 Next.js 15+ 变更**:动态路由的 `params` 和 `searchParams` 现在是异步的(Promise),
// > 需使用 `const { slug } = await params` 或在服务端组件中解包。上述写法适用于 Next.js 14,
// > Next.js 15 中签名应为 `{ params }: { params: Promise<{ slug: string }> }`。
3. Server Components vs Client Components¶
TSX
// === Server Component(默认)===
// 在服务器端渲染,不发送 JS 到客户端
// 可以直接访问数据库、文件系统、环境变量
async function ProductList() {
const products = await db.product.findMany(); // 直接查数据库
return (
<ul>
{products.map(p => <li key={p.id}>{p.name} - ¥{p.price}</li>)}
</ul>
);
}
// === Client Component(需要交互时)===
'use client'; // 必须在文件顶部声明
import { useState } from 'react';
function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId }),
});
setLoading(false);
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? '添加中...' : '加入购物车'}
</button>
);
}
// === 混合使用:Server Component 包裹 Client Component ===
// app/products/page.tsx (Server Component)
import { AddToCartButton } from './AddToCartButton';
export default async function ProductsPage() {
const products = await db.product.findMany();
return (
<div>
{products.map(p => (
<div key={p.id}>
<h3>{p.name}</h3>
<AddToCartButton productId={p.id} /> {/* Client Component */}
</div>
))}
</div>
);
}
4. 加载与错误处理¶
TSX
// app/users/loading.tsx — 自动 Suspense boundary
export default function Loading() {
return <div className="skeleton">Loading users...</div>;
}
// app/users/error.tsx — 自动 Error boundary
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
// app/api/hello/route.ts — Route Handler(替代 pages/api)
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ message: 'Hello from App Router API' });
}
export async function POST(request: Request) {
const body = await request.json();
return NextResponse.json({ received: body }, { status: 201 });
}
5. Pages Router vs App Router 对照¶
| 功能 | Pages Router | App Router |
|---|---|---|
| 文件位置 | pages/ | app/ |
| SSR数据获取 | getServerSideProps | async Server Component + cache: 'no-store' |
| SSG数据获取 | getStaticProps | async Server Component + next: { revalidate } |
| 动态路由预生成 | getStaticPaths | generateStaticParams |
| API路由 | pages/api/*.ts | app/api/*/route.ts |
| 布局 | _app.tsx + _document.tsx | layout.tsx (嵌套布局) |
| 加载状态 | 手动实现 | loading.tsx (自动Suspense) |
| 错误处理 | _error.tsx | error.tsx (自动Error Boundary) |
| 默认组件类型 | 客户端组件 | 服务端组件 |
🎨 Nuxt.js服务端渲染¶
1. Nuxt.js基础¶
1.1 基本配置¶
JavaScript
// 安装Nuxt.js
npx nuxi init my-app
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
],
runtimeConfig: {
public: {
apiBase: '/api',
},
},
});
1.2 页面路由¶
Vue
<!-- pages/index.vue -->
<template>
<div>
<h1>Welcome to Nuxt.js!</h1>
<p>Get started by editing pages/index.vue</p>
</div>
</template>
<!-- pages/about.vue -->
<template>
<div>
<h1>About</h1>
<p>This is the about page</p>
</div>
</template>
<!-- pages/users/[id].vue -->
<template>
<div>
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>
<script setup>
const route = useRoute();
const { data: user } = await useFetch(`/api/users/${route.params.id}`);
</script>
2. Nuxt.js数据获取¶
2.1 useFetch¶
Vue
<template>
<div>
<h1>Data from useFetch</h1>
<p v-if="pending">Loading...</p>
<p v-else-if="error">Error: {{ error.message }}</p>
<pre v-else>{{ data }}</pre>
</div>
</template>
<script setup>
const { data, pending, error } = await useFetch('/api/data');
</script>
2.2 useAsyncData¶
Vue
<template>
<div>
<h1>Data from useAsyncData</h1>
<pre>{{ data }}</pre>
</div>
</template>
<script setup>
const { data } = await useAsyncData('data', () => $fetch('/api/data'));
</script>
3. Nuxt.js服务端API¶
JavaScript
// server/api/hello.get.ts
export default defineEventHandler((event) => {
return {
message: 'Hello from Nuxt.js API',
};
});
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const user = await fetchUser(id);
if (!user) {
throw createError({
statusCode: 404,
message: 'User not found',
});
}
return user;
});
📊 渲染模式详解¶
1. SSR详解¶
JavaScript
// SSR - Server-Side Rendering
// 每次请求都在服务器上渲染
// 适合:动态内容、个性化内容
// Next.js SSR
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
};
}
// Nuxt.js SSR
<script setup>
const { data } = await useFetch('/api/data', {
server: true,
});
</script>
2. SSG详解¶
JavaScript
// SSG - Static Site Generation
// 构建时生成静态HTML
// 适合:静态内容、博客、文档
// Next.js SSG
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
};
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return {
paths: posts.map((post) => ({ // map转换每个元素;filter筛选;reduce累积
params: { id: post.id.toString() },
})),
fallback: false,
};
}
// Nuxt.js SSG(通过 nuxt.config.ts 配置 routeRules)
// nuxt.config.ts:
// export default defineNuxtConfig({
// routeRules: { '/api/data': { prerender: true } }
// })
<script setup>
const { data } = await useFetch('/api/data');
</script>
3. ISR详解¶
JavaScript
// ISR - Incremental Static Regeneration
// 静态生成 + 按需更新
// 适合:内容不经常变化但需要更新的页面
// Next.js ISR
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
revalidate: 60, // 60秒后重新生成
};
}
// Nuxt.js ISR(通过 nuxt.config.ts 配置 routeRules)
// nuxt.config.ts:
// export default defineNuxtConfig({
// routeRules: { '/api/data': { isr: 60 } } // 60秒后重新生成
// })
<script setup>
const { data } = await useFetch('/api/data'); // 解构赋值:从对象/数组提取值
</script>
🚀 服务端渲染优化¶
1. 性能优化¶
JavaScript
// 1. 代码分割
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('./DynamicComponent'), { // 箭头函数:简洁的函数语法
loading: () => <p>Loading...</p>,
ssr: false, // 禁用SSR
});
// 2. 图片优化
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={800}
height={600}
priority
placeholder="blur"
/>
// 3. 字体优化
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
2. 缓存策略¶
JavaScript
// 1. HTTP缓存
export async function getServerSideProps({ res }) {
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=30');
const data = await fetchData(); // await等待异步操作完成
return {
props: { data },
};
}
// 2. CDN缓存
// 配置CDN缓存静态资源
// 设置适当的缓存头
// 3. ISR缓存
export async function getStaticProps() {
const data = await fetchData(); // const不可重新赋值;let块级作用域变量
return {
props: { data },
revalidate: 3600, // 1小时后重新生成
};
}
📝 练习题¶
1. 基础题¶
题目1:使用Next.js创建SSR页面
JavaScript
// pages/index.js
export default function Home({ data }) {
return (
<div>
{/* 渲染数据 */}
</div>
);
}
export async function getServerSideProps() { // async定义异步函数;await等待Promise完成
// 获取数据
}
2. 进阶题¶
题目2:使用Nuxt.js创建ISR页面
Vue
<!-- pages/index.vue -->
<template>
<div>
<!-- 渲染数据 -->
</div>
</template>
<script setup>
// 获取数据并设置ISR
</script>
3. 面试题¶
题目3:解释SSR、SSG、ISR的区别
JavaScript
// 答案要点:
// SSR (Server-Side Rendering):
// - 每次请求都在服务器上渲染
// - 适合动态内容、个性化内容
// - 首屏加载快,SEO好
// SSG (Static Site Generation):
// - 构建时生成静态HTML
// - 适合静态内容、博客、文档
// - 性能最好,CDN友好
// ISR (Incremental Static Regeneration):
// - 静态生成 + 按需更新
// - 适合内容不经常变化但需要更新的页面
// - 结合SSG和SSR的优点
🎯 本章总结¶
本章节全面介绍了服务端渲染的各种技术和方案,包括Next.js、Nuxt.js、SSR、SSG、ISR等。关键要点:
- 渲染概念:理解SSR、SSG、ISR、CSR的区别
- Next.js:掌握Next.js服务端渲染和数据获取
- Nuxt.js:掌握Nuxt.js服务端渲染和数据获取
- 渲染模式:理解不同渲染模式的适用场景
- 性能优化:掌握服务端渲染优化策略
- 最佳实践:理解服务端渲染最佳实践
下一步将深入学习前端安全。