前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
第十章 getStaticPaths 与动态路由的静态生成
一、理论讲解
1. getStaticPaths 的作用与底层原理
getStaticPaths 是 Next.js SSG(静态站点生成)中用于动态路由的核心 API。它允许开发者在构建时为动态路由(如 [id].js)生成所有需要的静态页面路径,实现大规模内容的高性能静态化。
- 动态路由静态化:为每个动态参数生成独立的 HTML 文件,提升访问速度和 SEO。
- 与 getStaticProps 协同:getStaticPaths 提供路径,getStaticProps 提供每个路径的数据。
- 支持 ISR:结合 revalidate,实现动态内容的增量静态再生。
- 底层原理:构建时 Next.js 遍历所有 paths,生成 HTML/JSON 文件,结合 CDN 分发。
fallback 机制底层流程
- fallback: false:构建时生成所有页面,未声明路径直接 404。
- fallback: true:未声明路径首次访问时,Next.js 先返回 loading,后台生成静态页面,后续访问命中缓存。
- fallback: 'blocking':未声明路径首次访问时,Next.js 服务端生成完整页面并返回,后续访问命中缓存。
与 CDN/ISR 协同细节
- CDN 缓存所有静态页面,未命中时回源 Next.js 服务器。
- ISR 配合 getStaticPaths 支持大规模内容的定时/手动再生。
- fallback 页面生成后自动推送到 CDN,保证全球访问速度。
2. 企业级动态路由设计
- 多级动态路由(如 /category/[cat]/post/[id])支持无限层级嵌套。
- 路径生成可批量、分批、按需(如热门优先、冷门 fallback)。
- 路由参数需严格校验,防止注入和安全隐患。
- 支持多语言、多区域(i18n)路径生成。
3. 常见误区与优化建议
- 路径过多时全部静态化导致构建极慢,应分批或用 fallback。
- fallback: true 页面未处理骨架屏,用户体验差。
- fallback: false 忽略了新增内容的需求,需重新构建。
- 动态参数未做校验,易出现 404 或安全隐患。
- 路径生成未自动化,易遗漏或重复。
二、代码示例
1. 多级动态路由与批量路径生成
ts
// pages/category/[cat]/post/[id].tsx
export async function getStaticPaths() {
const categories = await fetchAllCategories();
let paths = [];
for (const cat of categories) {
const posts = await fetchPostsByCategory(cat);
paths = paths.concat(posts.map(p => ({ params: { cat, id: p.id } })));
}
return {
paths,
fallback: 'blocking',
};
}
export async function getStaticProps({ params }) {
const post = await fetchPostById(params.id);
if (!post) return { notFound: true };
return {
props: { post },
revalidate: 300,
};
}
2. fallback 页面骨架屏动画
tsx
import { useRouter } from 'next/router';
export default function PostPage({ post }) {
const router = useRouter();
if (router.isFallback) return <div className="skeleton">加载中...</div>;
if (!post) return <div>文章不存在</div>;
return <PostDetail post={post} />;
}
css
.skeleton {
background: linear-gradient(90deg, #eee 25%, #f5f5f5 50%, #eee 75%);
background-size: 200% 100%;
animation: skeleton 1.2s infinite linear;
height: 80px;
border-radius: 8px;
margin-bottom: 12px;
}
@keyframes skeleton {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
3. API 路由安全校验与错误监控
ts
// pages/api/posts/[id].ts
export default async function handler(req, res) {
const { id } = req.query;
if (!/^[0-9a-zA-Z_-]+$/.test(id)) return res.status(400).json({ message: '参数非法' });
try {
const post = await fetchPostById(id);
if (!post) return res.status(404).json({ message: '未找到' });
res.json(post);
} catch (err) {
reportError(err);
res.status(500).json({ message: '服务异常' });
}
}
4. 移动端交互优化
css
@media (max-width: 600px) {
.post-detail { padding: 8px; font-size: 16px; }
.skeleton { height: 60px; }
}
5. 错误监控与埋点
js
// pages/_app.tsx
import { useEffect } from 'react';
useEffect(() => {
window.addEventListener('error', (e) => {
// 上报动态路由相关错误
reportError(e);
});
}, []);
三、实战项目:博客系统文章详情页(多级动态路由+ISR)
1. 项目需求
- 文章详情页通过 getStaticPaths 生成,支持多级动态路径(如 /category/前端/post/123)
- 结合 ISR 实现增量再生,内容自动更新
- SEO 友好,性能高,移动端适配
- 支持 fallback: 'blocking',骨架屏动画,极端场景降级
- 支持结构化数据、性能监控
2. 技术选型
- Next.js + TypeScript
- getStaticPaths + getStaticProps + revalidate
- fallback: 'blocking'
- CDN 缓存
- Sentry/自研埋点监控
3. 目录结构
text
/blog-demo
|-- pages/
|-- category/
|-- [cat]/
|-- post/
|-- [id].tsx
|-- api/
|-- posts/
|-- [id].ts
|-- components/
|-- PostDetail.tsx
|-- Skeleton.tsx
|-- styles/
|-- globals.css
4. 文章详情页实现
ts
// pages/category/[cat]/post/[id].tsx
import PostDetail from '../../../components/PostDetail';
import Skeleton from '../../../components/Skeleton';
import Head from 'next/head';
import { useRouter } from 'next/router';
export async function getStaticPaths() {
const cats = await fetchAllCategories();
let paths = [];
for (const cat of cats) {
const posts = await fetchPostsByCategory(cat);
paths = paths.concat(posts.map(p => ({ params: { cat, id: p.id } })));
}
return {
paths,
fallback: 'blocking',
};
}
export async function getStaticProps({ params }) {
const post = await fetchPostById(params.id);
if (!post) return { notFound: true };
return {
props: { post },
revalidate: 300,
};
}
export default function PostPage({ post }) {
const router = useRouter();
if (router.isFallback) return <Skeleton />;
if (!post) return <div>文章不存在</div>;
return (
<>
<Head>
<title>{post.title} - 博客</title>
<meta name="description" content={post.summary} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Article",
"headline": post.title,
"author": post.author,
"datePublished": post.date
}) }} />
</Head>
<PostDetail post={post} />
</>
);
}
ts
// components/Skeleton.tsx
export default function Skeleton() {
return <div className="skeleton">加载中...</div>;
}
四、最佳实践
- 路径生成自动化与分批:批量生成热门路径,冷门内容用 fallback,避免构建过慢。
ts
const hotPosts = await fetchHotPostIds();
return { paths: hotPosts.map(id => ({ params: { id } })), fallback: 'blocking' };
- fallback 页面体验优化:配合骨架屏动画,提升加载体验。
- 性能优化:合理设置 revalidate,结合 CDN,避免服务端压力。
- 错误处理与监控报警:未找到内容返回 notFound: true,集成 Sentry/自研埋点。
- 团队协作:文档化路径生成、fallback 策略,自动化测试。
- 极端场景降级:如接口超时、数据异常,前端兜底提示。
tsx
if (error) return <div>文章加载失败,请稍后重试</div>;
五、常见问题与解决方案
- Q: 路径过多导致构建慢?
- A: 只生成热门路径,冷门用 fallback,或分批构建。
- Q: 访问未声明路径 404?
- A: fallback 设为 true 或 'blocking'。
- Q: 新增内容未及时上线?
- A: 配合 ISR,或手动 revalidate。
- Q: SEO 有问题?
- A: fallback: 'blocking' 可保证首屏完整内容,利于 SEO。
- Q: fallback 页面体验差?
- A: fallback: true 时前端需处理骨架屏动画。
- Q: 动态参数安全隐患?
- A: 校验参数合法性,防止注入。
- Q: 多级动态路由如何组织?
- A: 目录结构清晰,参数命名规范,接口统一。
- Q: 如何监控动态路由异常?
- A: 集成 Sentry/自研埋点,自动报警。
- Q: 极端场景如何降级?
- A: 前端兜底提示,简化页面,保证体验。
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!