前言
大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!
第八章:静态生成 (SSG) 与 getStaticProps 深度实践
一、理论讲解
1.1 什么是静态生成(SSG)?
静态生成(Static Site Generation,简称 SSG)是指在构建时(build 阶段)将页面内容预渲染为静态 HTML 文件,用户访问时直接返回静态内容。相比 SSR,SSG 具有极致的性能和极低的服务器压力。
- SSG 优势:
- 首屏极快,CDN 可全局加速
- 服务器压力小,适合高并发
- 内容安全,静态文件无后端漏洞
- 适合内容型、文档型、营销型网站
- SSG 劣势:
- 构建时间随页面数量增长
- 不适合强实时、个性化内容
- 数据更新需重新构建或增量更新
1.2 Next.js SSG 的实现方式
Next.js 通过 getStaticProps 和 getStaticPaths 实现 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:传递给页面组件的 propsrevalidate:启用 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。
- 日志监控,自动告警。
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!