1191 字
6 分钟
服务端渲染 SSR 实战:Next.js 与 Nuxt.js 深度解析

服务端渲染 SSR 实战:Next.js 与 Nuxt.js 深度解析#

🌐 前言:服务端渲染(SSR)是解决 SPA 首屏加载慢、SEO 不友好的有效方案。Next.js 和 Nuxt.js 是目前最流行的 SSR 框架,这篇文章将深入讲解它们的原理和实战技巧。


一、为什么需要 SSR#

1.1 SPA 的问题#

首屏加载慢:

用户请求 -> 下载 HTML -> 下载 JS -> 执行 JS -> 渲染页面
(白屏时间 = 下载 + 执行 JS 的时间)

SEO 不友好:

  • 搜索引擎爬虫可能无法执行 JavaScript
  • 页面内容为空,影响收录和排名

1.2 SSR 的优势#

更快的首屏:

用户请求 -> 服务端渲染 -> 返回完整 HTML -> 展示页面
(用户可以立即看到内容)

更好的 SEO:

  • 搜索引擎直接抓取渲染好的 HTML
  • 内容完整,有利于排名

1.3 渲染模式对比#

模式首屏SEO交互适用场景
CSR后台管理系统
SSR内容网站、电商
SSG极快一般博客、文档站
ISR新闻、大流量站点

二、Next.js 实战#

2.1 项目结构#

my-app/
├── app/ # App Router (Next.js 13+)
│ ├── layout.tsx
│ ├── page.tsx
│ └── loading.tsx
├── pages/ # Pages Router (传统)
│ ├── index.tsx
│ └── api/
├── public/
├── components/
└── next.config.js

2.2 数据获取#

服务端组件(推荐):

app/page.tsx
async function getData() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // ISR: 60秒重新验证
});
return res.json();
}
export default async function Page() {
const posts = await getData();
return (
<main>
<h1>博客文章</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}

客户端组件:

'use client';
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// 客户端逻辑
}, []);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}

2.3 路由和导航#

app/layout.tsx
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="zh">
<body>
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
</nav>
{children}
</body>
</html>
);
}

2.4 API 路由#

app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const users = await db.users.findMany();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.users.create(body);
return NextResponse.json(user, { status: 201 });
}

2.5 中间件#

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 && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*']
};

三、Nuxt.js 实战#

3.1 项目结构#

nuxt-app/
├── components/ # Vue 组件
├── composables/ # 组合式函数
├── layouts/ # 布局
├── middleware/ # 中间件
├── pages/ # 页面(自动路由)
├── public/ # 静态文件
├── server/ # 服务端代码
│ ├── api/ # API 路由
│ └── middleware/ # 服务端中间件
└── nuxt.config.ts

3.2 页面和数据获取#

pages/index.vue
<script setup>
// 服务端获取数据
const { data: posts } = await useFetch('/api/posts');
// 客户端获取数据
const { data: users } = await useFetch('/api/users', {
server: false // 只在客户端获取
});
// ISR
const { data: articles } = await useFetch('/api/articles', {
key: 'articles',
initialCache: false
});
</script>
<template>
<div>
<h1>文章列表</h1>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
</div>
</template>

3.3 服务端 API#

server/api/posts.get.ts
export default defineEventHandler(async (event) => {
const posts = await prisma.post.findMany({
orderBy: { createdAt: 'desc' }
});
return posts;
});
// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const post = await prisma.post.create({
data: body
});
return post;
});

3.4 组合式函数#

composables/useAuth.ts
export const useAuth = () => {
const user = useState('user', () => null);
const login = async (credentials) => {
const { data } = await useFetch('/api/auth/login', {
method: 'POST',
body: credentials
});
user.value = data.value;
};
const logout = async () => {
await useFetch('/api/auth/logout', { method: 'POST' });
user.value = null;
};
return {
user,
login,
logout,
isLoggedIn: computed(() => !!user.value)
};
};

四、性能优化#

4.1 图片优化#

Next.js:

import Image from 'next/image';
export default function Gallery() {
return (
<Image
src="/photo.jpg"
alt="照片"
width={800}
height={600}
priority // 优先加载
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
);
}

Nuxt.js:

<template>
<NuxtImg
src="/photo.jpg"
alt="照片"
width="800"
height="600"
loading="lazy"
placeholder
/>
</template>

4.2 代码分割#

// Next.js 动态导入
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(
() => import('../components/HeavyComponent'),
{ loading: () => <p>Loading...</p> }
);
// Nuxt.js 动态导入
const HeavyComponent = defineAsyncComponent(() =>
import('~/components/HeavyComponent.vue')
);

4.3 缓存策略#

// Next.js 缓存配置
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 60, // ISR: 60秒重新生成
notFound: !data
};
}
// Nuxt.js 缓存
const { data } = await useFetch('/api/data', {
key: 'unique-key',
server: true,
default: () => ({})
});

五、部署#

5.1 Vercel(推荐)#

Terminal window
# Next.js
npm i -g vercel
vercel
# Nuxt.js
npm i -g vercel
vercel

5.2 Node.js 服务器#

package.json
{
"scripts": {
"build": "next build",
"start": "next start"
}
}

5.3 Docker#

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

六、总结#

Next.js vs Nuxt.js:

特性Next.jsNuxt.js
框架ReactVue
路由App Router / Pages文件系统自动路由
数据获取Server Components / getXPropsuseFetch / 自动导入
生态更大活跃
学习曲线中等较低

选择建议:

  • React 团队选 Next.js
  • Vue 团队选 Nuxt.js
  • 新项目两者都是优秀选择

SSR 不再是可选项,而是现代 Web 应用的标配。掌握 Next.js 或 Nuxt.js,将帮助你构建更快、更友好的 Web 应用!

服务端渲染 SSR 实战:Next.js 与 Nuxt.js 深度解析
https://www.oferry.com/posts/a86/
作者
晨平安
发布于
2026-02-27
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00