Next.js静态生成SSG #

一、SSG基础 #

1.1 什么是SSG #

静态生成是在构建时生成HTML:

text
构建时 → 生成HTML → 部署 → 请求直接返回HTML

1.2 SSG优点 #

  • 访问速度快
  • CDN友好
  • 服务器压力小
  • SEO友好

1.3 SSG缺点 #

  • 构建时间长
  • 更新需要重新部署
  • 不适合实时数据

1.4 适用场景 #

  • 博客文章
  • 产品页面
  • 文档网站
  • 营销页面

二、静态页面 #

2.1 默认静态 #

tsx
export default async function Page() {
    const data = await fetch('https://api.example.com/data')
    
    return <div>{data}</div>
}

默认情况下,Next.js 会静态渲染页面。

2.2 强制静态 #

tsx
export const dynamic = 'force-static'

export default async function Page() {
    const data = await fetch('https://api.example.com/data', {
        cache: 'force-cache',
    })
    
    return <div>{data}</div>
}

2.3 缓存数据 #

tsx
export default async function Page() {
    const data = await fetch('https://api.example.com/data', {
        cache: 'force-cache',
    })
    
    return <div>{data}</div>
}

三、generateStaticParams #

3.1 基本用法 #

tsx
export async function generateStaticParams() {
    const posts = await getPosts()
    
    return posts.map(post => ({
        slug: post.slug,
    }))
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return <article>{post.content}</article>
}

3.2 多参数 #

tsx
export async function generateStaticParams() {
    const products = await getProducts()
    
    return products.map(product => ({
        category: product.category,
        id: product.id.toString(),
    }))
}

export default async function Page({ params }: { params: Promise<{ category: string; id: string }> }) {
    const { category, id } = await params
    const product = await getProduct(category, id)
    
    return <div>{product.name}</div>
}

3.3 捕获所有路由 #

tsx
export async function generateStaticParams() {
    return [
        { slug: ['getting-started'] },
        { slug: ['api', 'reference'] },
        { slug: ['guide', 'basics', 'intro'] },
    ]
}

export default async function Page({ params }: { params: Promise<{ slug: string[] }> }) {
    const { slug } = await params
    const path = slug.join('/')
    
    return <div>文档: {path}</div>
}

3.4 动态参数 #

tsx
export const dynamicParams = true

export async function generateStaticParams() {
    const popularPosts = await getPopularPosts()
    
    return popularPosts.map(post => ({
        slug: post.slug,
    }))
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return <article>{post.content}</article>
}

dynamicParams = true 允许访问未预生成的路由。

四、静态导出 #

4.1 配置 #

tsx
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
    output: 'export',
}

export default nextConfig

4.2 构建命令 #

bash
npm run build

输出到 out 目录。

4.3 限制 #

静态导出不支持:

  • API Routes
  • 动态路由(需要generateStaticParams)
  • 服务端功能
  • 中间件

4.4 图片优化 #

tsx
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
    output: 'export',
    images: {
        unoptimized: true,
    },
}

export default nextConfig

五、静态数据获取 #

5.1 构建时获取 #

tsx
export default async function Page() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'force-cache',
    })
    const data = await res.json()
    
    return <div>{data.title}</div>
}

5.2 数据库访问 #

tsx
import { prisma } from '@/lib/prisma'

export default async function Page() {
    const posts = await prisma.post.findMany({
        where: { published: true },
    })
    
    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    )
}

5.3 本地文件 #

tsx
import { readFile } from 'fs/promises'
import path from 'path'

export default async function Page() {
    const filePath = path.join(process.cwd(), 'data', 'posts.json')
    const data = await readFile(filePath, 'utf-8')
    const posts = JSON.parse(data)
    
    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    )
}

六、元数据生成 #

6.1 静态元数据 #

tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
    title: '我的博客',
    description: '技术博客',
}

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

6.2 动态元数据 #

tsx
import type { Metadata } from 'next'

export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
    const { slug } = await params
    const post = await getPost(slug)
    
    return {
        title: post.title,
        description: post.excerpt,
    }
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return <article>{post.content}</article>
}

七、站点地图 #

7.1 sitemap.ts #

tsx
import { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const posts = await getPosts()
    
    const postUrls = posts.map(post => ({
        url: `https://example.com/blog/${post.slug}`,
        lastModified: post.updatedAt,
        changeFrequency: 'weekly' as const,
        priority: 0.8,
    }))
    
    return [
        {
            url: 'https://example.com',
            lastModified: new Date(),
            changeFrequency: 'daily',
            priority: 1,
        },
        {
            url: 'https://example.com/about',
            lastModified: new Date(),
            changeFrequency: 'monthly',
            priority: 0.7,
        },
        ...postUrls,
    ]
}

7.2 robots.txt #

tsx
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
    return {
        rules: {
            userAgent: '*',
            allow: '/',
            disallow: ['/api/', '/admin/'],
        },
        sitemap: 'https://example.com/sitemap.xml',
    }
}

八、最佳实践 #

8.1 预生成热门页面 #

tsx
export async function generateStaticParams() {
    const posts = await getPosts()
    
    return posts
        .filter(post => post.views > 1000)
        .map(post => ({
            slug: post.slug,
        }))
}

8.2 分页静态生成 #

tsx
export async function generateStaticParams() {
    const totalPosts = await getTotalPosts()
    const postsPerPage = 10
    const totalPages = Math.ceil(totalPosts / postsPerPage)
    
    return Array.from({ length: totalPages }, (_, i) => ({
        page: (i + 1).toString(),
    }))
}

8.3 缓存策略 #

tsx
export default async function Page() {
    const staticData = await fetch('/api/static', {
        cache: 'force-cache',
    })
    
    const semiStaticData = await fetch('/api/semi-static', {
        next: { revalidate: 3600 },
    })
    
    return (
        <div>
            <StaticSection data={staticData} />
            <SemiStaticSection data={semiStaticData} />
        </div>
    )
}

九、总结 #

SSG要点:

要点 说明
静态页面 默认静态渲染
generateStaticParams 预生成动态路由
静态导出 output: ‘export’
缓存数据 cache: ‘force-cache’
元数据 静态/动态生成
站点地图 sitemap.ts

下一步,让我们学习增量静态再生!

最后更新:2026-03-28