Next.js 教程系列(八)静态生成 (SSG) 与 getStaticProps 深度实践

292 阅读4分钟

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!


第八章:静态生成 (SSG) 与 getStaticProps 深度实践

一、理论讲解

1.1 什么是静态生成(SSG)?

静态生成(Static Site Generation,简称 SSG)是指在构建时(build 阶段)将页面内容预渲染为静态 HTML 文件,用户访问时直接返回静态内容。相比 SSR,SSG 具有极致的性能和极低的服务器压力。

  • SSG 优势
    • 首屏极快,CDN 可全局加速
    • 服务器压力小,适合高并发
    • 内容安全,静态文件无后端漏洞
    • 适合内容型、文档型、营销型网站
  • SSG 劣势
    • 构建时间随页面数量增长
    • 不适合强实时、个性化内容
    • 数据更新需重新构建或增量更新

1.2 Next.js SSG 的实现方式

Next.js 通过 getStaticPropsgetStaticPaths 实现 SSG:

  • getStaticProps:构建时获取数据,生成静态页面
  • getStaticPaths:用于动态路由,生成所有静态路径

1.3 SSG 适用场景

  • 博客、文档、产品展示、官网等内容型页面
  • SEO 要求高、内容更新频率低的页面
  • 需要 CDN 全球加速的场景

二、getStaticProps 详解

2.1 基本用法

getStaticProps 只能用于 pages 目录下的页面组件,在构建时执行,生成静态 HTML。

// pages/blog.tsx
import React from 'react';
import type { GetStaticProps, NextPage } from 'next';

interface BlogProps {
  posts: { id: number; title: string; content: string }[];
}

const Blog: NextPage<BlogProps> = ({ posts }) => (
  <div>
    <h1>博客列表</h1>
    <ul>
      {posts.map((item) => (
        <li key={item.id}>
          <h2>{item.title}</h2>
          <p>{item.content}</p>
        </li>
      ))}
    </ul>
  </div>
);

export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  return {
    props: { posts },
    revalidate: 60, // ISR 增量静态生成,60秒自动更新
  };
};

export default Blog;
2.2 参数与返回值
  • props:传递给页面组件的 props
  • revalidate:启用 ISR(增量静态生成),定时自动更新
  • notFound:返回 404 页面
  • redirect:重定向
2.3 动态路由与 getStaticPaths

对于动态路由页面(如 /blog/[id].tsx),需配合 getStaticPaths 生成所有静态路径。

// pages/blog/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';

export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  const paths = posts.map((post) => ({ params: { id: post.id.toString() } }));
  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps = async (context) => {
  const { id } = context.params!;
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post = await res.json();
  if (!post) return { notFound: true };
  return { props: { post }, revalidate: 60 };
};

三、实战项目:SSG 博客系统

3.1 需求分析

  • 实现一个博客系统,所有文章静态生成,支持动态路由
  • 支持增量静态生成(ISR),内容可自动更新
  • 支持移动端适配、SEO 优化、国际化
  • 错误处理与性能优化

3.2 目录结构

/pages
  ├── blog.tsx
  └── blog/
      └── [id].tsx
/components
  └── BlogList.tsx
  └── BlogDetail.tsx
/utils
  └── fetcher.ts

3.3 代码实现

3.3.1 通用数据获取工具
// utils/fetcher.ts
export async function fetcher(url: string) {
  const res = await fetch(url);
  if (!res.ok) throw new Error('数据获取失败');
  return res.json();
}
3.3.2 博客列表页面
// pages/blog.tsx
import { GetStaticProps } from 'next';
import BlogList from '../components/BlogList';
import { fetcher } from '../utils/fetcher';

export default function BlogPage({ posts }: any) {
  return <BlogList posts={posts} />;
}

export const getStaticProps: GetStaticProps = async () => {
  const posts = await fetcher('https://api.example.com/posts');
  return { props: { posts }, revalidate: 60 };
};
3.3.3 博客详情页面(动态路由)
// pages/blog/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import BlogDetail from '../../components/BlogDetail';
import { fetcher } from '../../utils/fetcher';

export default function BlogDetailPage({ post }: any) {
  return <BlogDetail post={post} />;
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await fetcher('https://api.example.com/posts');
  const paths = posts.map((post: any) => ({ params: { id: post.id.toString() } }));
  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps = async (context) => {
  const { id } = context.params!;
  try {
    const post = await fetcher(`https://api.example.com/posts/${id}`);
    if (!post) return { notFound: true };
    return { props: { post }, revalidate: 60 };
  } catch {
    return { notFound: true };
  }
};
3.3.4 组件拆分与样式
// components/BlogList.tsx
import React from 'react';
export default function BlogList({ posts }: { posts: any[] }) {
  return (
    <ul>
      {posts.map((item) => (
        <li key={item.id}>
          <a href={`/blog/${item.id}`}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
// components/BlogDetail.tsx
import React from 'react';
export default function BlogDetail({ post }: { post: any }) {
  if (!post) return <div>文章不存在</div>;
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}
3.3.5 移动端适配与 SEO
  • 使用响应式布局,适配不同屏幕
  • 设置 meta、title、OG 标签提升 SEO
3.3.6 错误处理与性能优化
  • try/catch 捕获异常,返回 notFound 或 fallback
  • 合理设置 revalidate,平衡实时性与性能

四、企业级最佳实践

4.1 代码组织与团队协作

4.1.1 目录结构与分层

企业级 SSG 项目推荐采用分层结构,提升可维护性和团队协作效率:

├── pages/           # 路由页面
├── components/      # 通用 UI 组件
├── modules/         # 业务模块(可选)
├── services/        # 数据获取与 API 封装
├── utils/           # 工具函数
├── hooks/           # 通用自定义 Hook
├── styles/          # 样式文件
4.1.2 类型定义与复用
  • 所有数据结构、接口响应都用 TypeScript 类型定义,统一放在 types/ 目录。
  • 组件、服务、页面都严格类型约束,减少线上 bug。
// types/blog.ts
export interface BlogPost {
  id: number;
  title: string;
  content: string;
}
4.1.3 团队协作规范
  • 统一代码风格(ESLint、Prettier、Husky)
  • Pull Request + 代码评审
  • 约定分支命名、提交规范(如 Conventional Commits)
  • 代码自动化检查与测试集成到 CI

4.2 自动化与 CI/CD

4.2.1 自动化构建与部署
  • 使用 GitHub Actions、GitLab CI、Jenkins 等自动化工具实现一键构建、测试、部署。
  • 构建产物自动上传到 CDN 或静态托管平台(如 Vercel、Netlify、OSS、COS)。
# .github/workflows/deploy.yml
name: Deploy Blog SSG
on:
  push:
    branches: [main]
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install deps
        run: npm ci
      - name: Build
        run: npm run build
      - name: Deploy
        run: npm run deploy
4.2.2 Webhook 增量构建
  • 内容变更后自动触发 Webhook,增量构建并发布最新静态页面。
  • 适合与 CMS(如 Strapi、Contentful)集成。
// 伪代码:CMS 内容变更后调用 Webhook
fetch('https://vercel.com/api/webhook/deploy', { method: 'POST' });

4.3 权限与安全

4.3.1 静态页面与敏感数据
  • 静态页面不包含任何敏感信息,所有鉴权、权限逻辑在 API 层处理。
  • 动态内容通过客户端鉴权或 API 保护。
4.3.2 API 安全
  • API 层需做身份校验、权限校验、速率限制、参数校验。
  • 推荐使用 JWT/OAuth2 等标准认证协议。
// services/api.ts
export async function fetchProtectedData(token: string) {
  const res = await fetch('/api/protected', {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error('未授权');
  return res.json();
}
4.3.3 防御 XSS/CSRF
  • 所有外部输入严格校验和转义,防止 XSS。
  • API 层加 CSRF Token 校验。

4.4 性能优化

4.4.1 图片与静态资源优化
  • 使用 next/image 自动图片压缩、懒加载、WebP 格式。
  • 静态资源全部走 CDN,减少主站压力。
import Image from 'next/image';
export default function Banner() {
  return <Image src="/banner.jpg" width={800} height={400} alt="banner" priority />;
}
4.4.2 预渲染与增量静态生成(ISR)
  • 关键页面全部预渲染,提升首屏速度。
  • 高频变更页面设置较短 revalidate,低频页面设置较长。
4.4.3 构建优化
  • 页面数量过多时,采用分批构建、并行构建。
  • 结合缓存策略,减少重复构建。

4.5 移动端适配

4.5.1 响应式设计
  • 使用 CSS-in-JS、Tailwind CSS、styled-components 等方案实现响应式。
  • 组件级别适配不同屏幕,保证移动端体验。
// 使用 Tailwind CSS
export default function Card({ title, content }) {
  return (
    <div className="p-4 md:p-8 bg-white rounded shadow-md">
      <h2 className="text-lg md:text-2xl font-bold">{title}</h2>
      <p className="text-base md:text-lg">{content}</p>
    </div>
  );
}
4.5.2 关键内容优先渲染
  • 重要内容优先渲染在首屏,减少 CLS(布局偏移)。
  • 关键 CSS 内联,提升渲染速度。

4.6 自动化测试与监控

4.6.1 单元测试
  • 使用 Jest/Testing Library 对组件、工具函数、数据获取逻辑做单元测试。
// __tests__/fetcher.test.ts
import { fetcher } from '../utils/fetcher';
test('fetcher 正常返回数据', async () => {
  global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => [1, 2, 3] })) as any;
  const data = await fetcher('/api/test');
  expect(data).toEqual([1, 2, 3]);
});
4.6.2 E2E 测试
  • 使用 Playwright/Cypress 模拟用户端到端访问,验证页面渲染、跳转、SEO 标签。
4.6.3 监控与告警
  • 接入 Sentry、Datadog、阿里云前端监控等平台,监控构建、运行时错误。
  • 关键路径设置告警,及时发现问题。

4.7 常见问题与解决方案

4.7.1 SSG 页面数量过多导致构建慢
  • 采用 ISR,减少全量构建压力。
  • 拆分项目,分批构建。
4.7.2 动态内容如何实时更新?
  • 使用 ISR + Webhook,或客户端 SWR/React Query 拉取最新数据。
4.7.3 SEO 优化
  • 静态页面设置完整 meta、OG 标签。
  • 动态路由页面建议预渲染所有主路径。
4.7.4 SSG 与 SSR 如何选择?
  • 内容型、低实时性页面优先 SSG。
  • 个性化、强实时性页面用 SSR。
4.7.5 构建失败/数据异常
  • try/catch 捕获异常,返回 notFound 或 fallback。
  • 日志监控,自动告警。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!