
掌握服务端渲染的核心技术,提升应用性能与SEO
服务端渲染(SSR)是现代前端开发中的重要技术,它能显著提升首屏加载速度和SEO效果。本文将通过实际案例,带你深入了解Next.js和Nuxt.js的SSR实现。
SSR(Server-Side Rendering)是在服务器端生成完整HTML页面的技术,与客户端渲染(CSR)相比:
CSR (客户端渲染)
浏览器 → 下载JS → 执行JS → 渲染页面
SSR (服务端渲染)
浏览器 → 服务器渲染 → 返回HTML → 水合(Hydration)
SSG (静态生成)
构建时 → 预渲染HTML → CDN分发 → 直接展示// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ['example.com'],
},
}
module.exports = nextConfig// pages/posts/[id].tsx
import { GetServerSideProps } from 'next'
interface Post {
id: string
title: string
content: string
author: string
}
interface PostPageProps {
post: Post
}
export default function PostPage({ post }: PostPageProps) {
return (
<div>
<h1>{post.title}</h1>
<p>作者:{post.author}</p>
<div>{post.content}</div>
</div>
)
}
// 服务端数据获取
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params!
try {
const response = await fetch(`https://api.example.com/posts/${id}`)
const post = await response.json()
return {
props: {
post,
},
}
} catch (error) {
return {
notFound: true,
}
}
}// app/posts/[id]/page.tsx
async function getPost(id: string) {
const response = await fetch(`https://api.example.com/posts/${id}`, {
cache: 'force-cache', // 缓存策略
})
if (!response.ok) {
throw new Error('Failed to fetch post')
}
return response.json()
}
export default async function PostPage({
params
}: {
params: { id: string }
}) {
const post = await getPost(params.id)
return (
<div>
<h1>{post.title}</h1>
<p>作者:{post.author}</p>
<div>{post.content}</div>
</div>
)
}
// 生成静态参数
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return posts.map((post: Post) => ({
id: post.id,
}))
}// utils/api.ts
export async function fetchWithCache<T>(
url: string,
options: RequestInit = {}
): Promise<T> {
const response = await fetch(url, {
...options,
next: {
revalidate: 60, // 60秒后重新验证
},
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
// 使用示例
export async function getPosts() {
return fetchWithCache<Post[]>('https://api.example.com/posts')
}
export async function getPost(id: string) {
return fetchWithCache<Post>(`https://api.example.com/posts/${id}`)
}// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // 启用SSR
nitro: {
prerender: {
routes: ['/sitemap.xml']
}
},
runtimeConfig: {
apiSecret: '', // 服务端环境变量
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})<!-- pages/posts/[id].vue -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>作者:{{ post.author }}</p>
<div>{{ post.content }}</div>
</div>
</template>
<script setup>
// 服务端数据获取
const route = useRoute()
const { data: post } = await $fetch(`/api/posts/${route.params.id}`)
// SEO优化
useSeoMeta({
title: post.title,
ogTitle: post.title,
description: post.excerpt,
ogDescription: post.excerpt,
ogImage: post.image,
})
</script>// server/api/posts/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
try {
const post = await $fetch(`https://external-api.com/posts/${id}`)
return {
success: true,
data: post
}
} catch (error) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found'
})
}
})// composables/usePosts.ts
export const usePosts = () => {
const posts = ref([])
const loading = ref(false)
const error = ref(null)
const fetchPosts = async () => {
loading.value = true
error.value = null
try {
const { data } = await $fetch('/api/posts')
posts.value = data
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
return {
posts: readonly(posts),
loading: readonly(loading),
error: readonly(error),
fetchPosts
}
}// Next.js 缓存配置
export const revalidate = 3600 // 1小时重新验证
// Nuxt.js 缓存配置
export default cachedFunction(async (id: string) => {
return await $fetch(`/api/posts/${id}`)
}, {
maxAge: 1000 * 60 * 60, // 1小时缓存
name: 'posts',
getKey: (id: string) => id
})// Next.js 动态导入
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/Heavy'), {
loading: () => <p>Loading...</p>,
ssr: false // 禁用SSR
})
// Nuxt.js 懒加载
const LazyComponent = defineAsyncComponent(() => import('~/components/Heavy.vue'))// Next.js Image组件
import Image from 'next/image'
<Image
src="/hero.jpg"
alt="Hero image"
width={800}
height={400}
priority // 优先加载
placeholder="blur" // 模糊占位符
/><!-- Nuxt.js 图片优化 -->
<template>
<NuxtImg
src="/hero.jpg"
alt="Hero image"
width="800"
height="400"
loading="lazy"
format="webp"
/>
</template>// Next.js SEO
import Head from 'next/head'
export default function PostPage({ post }) {
return (
<>
<Head>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={post.image} />
</Head>
{/* 页面内容 */}
</>
)
}<!-- Nuxt.js SEO -->
<script setup>
const post = await $fetch('/api/posts/1')
useHead({
title: post.title,
meta: [
{ name: 'description', content: post.excerpt },
{ property: 'og:title', content: post.title },
{ property: 'og:description', content: post.excerpt },
{ property: 'og:image', content: post.image }
]
})
</script>// 添加JSON-LD结构化数据
const structuredData = {
"@context": "https://schema.org",
"@type": "Article",
"headline": post.title,
"author": {
"@type": "Person",
"name": post.author
},
"datePublished": post.publishedAt,
"image": post.image
}// 解决方案:使用客户端渲染
import { useEffect, useState } from 'react'
export default function ClientOnly({ children }) {
const [hasMounted, setHasMounted] = useState(false)
useEffect(() => {
setHasMounted(true)
}, [])
if (!hasMounted) {
return null
}
return <>{children}</>
}// Next.js 环境变量
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'
// Nuxt.js 运行时配置
const config = useRuntimeConfig()
const apiUrl = config.public.apiBase// Next.js 错误边界
export default function ErrorPage({ statusCode }) {
return (
<p>
{statusCode
? `服务器发生 ${statusCode} 错误`
: '客户端发生错误'}
</p>
)
}
// Nuxt.js 错误处理
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, context) => {
console.error('Vue error:', error)
}
})// 性能指标监控
export function reportWebVitals(metric) {
console.log(metric)
// 发送到分析服务
}// 确保基础功能在JS加载前可用
export default function ProgressiveForm() {
return (
<form action="/api/submit" method="POST">
<input name="email" type="email" required />
<button type="submit">提交</button>
</form>
)
}SSR技术为现代Web应用带来了显著的性能和SEO优势。通过本文的实践案例,你应该能够:
选择合适的框架和渲染策略,根据项目需求做出最佳决策,是成功实施SSR的关键。
开始你的SSR实践之旅,构建更快更好的Web应用!