这是 Next.js 构建博客的第三篇文章,上一篇文章介绍了博客的搭建过程,这篇文章则重点介绍一下如何将资源打包成 SSG。
SSG 的全称是 Static Site Generation 即静态渲染,不过在介绍之前先说一下主流的渲染方式:
- 客户端渲染,我们常见的 Vue、React 等默认就是客户端渲染,不过这种方式最大弊端就是首屏速度以及 seo 抓取,这刚好不符合我们博客场景;
- ssr,其实就是代码跑在 nodejs 上,然后输出渲染的 html 字符串,之后浏览器获取 js 资源后执行同构代码,并将 DOM 元素绑定事件等,不过这里也不符合场景,代码最终部署在 GitHub 上其实是没有额外的服务器让我们执行;
- SSG,就是将动态数据输出成固定的 html 文件,兼顾了文件缓存以及 seo 的需求,完美符合我们使用场景;
开启 SSG
上一篇中我们列举了一下页面的路由:
- /:首页
- pages:分页页面
- details/:id:详情页面
- types/:id:分类页面
对于固定的页面我们不需要做什么,但是对于携带 :id
的动态页面,我们需要把所有的条件都枚举出来,只有这样才能输出所有的页面信息,不至于点击某一个文章直接 404 了。
枚举所有条件可以使用 generateStaticParams 完成,这里看一个官方给出的例子:
ts
// app/blog/[slug]/page.js
// Return a list of `params` to populate the [slug] dynamic segment
export async function generateStaticParams() {
const posts = await fetch("https://.../posts").then((res) => res.json());
return posts.map((post) => ({
slug: post.slug,
}));
}
// Multiple versions of this page will be statically generated
// using the `params` returned by `generateStaticParams`
export default function Page({ params }) {
const { slug } = params;
// ...
}
其中 [slug]
代表 slug 这个必填,下面的 generateStaticParams 函数作用就是把所有 slug 通过数组的形式进行返回出来,之后 Page 就正常渲染即可。
下面抛砖引玉对 types 页面进行改造
tsx
import { classification } from "@blog/side-effect";
interface Params {
id: [string];
}
interface Props {
params: Params;
searchParams: Record<string, string>;
}
export function generateStaticParams(): Params[] {
const result: Params[] = [];
classification.forEach((value, key) => {
result.push({
rest: [key],
});
});
return result;
}
这里就把页面所需的参数给枚举结束,其他页面也这样处理即可,如果页面参数涉及的是多个,例如页面需要 id 和 name,那你也只需要在 generateStaticParams 按照顺序返回即可,例如:
js
// app/pages/[...rest]/page.tsx
return [
{
rest: [id, name],
},
{
rest: [id, name],
},
// ...
];
之后调整 next.config.js 文件
js
const nextConfig = {
// ...
output: "export",
};
module.exports = nextConfig;
之后在 app/layout.tsx 文件添加
js
export const dynamic = "error";
这里防止在代码中引用 SSG 不支持的一些功能,具体不支持功能可以点击查看。
动态标题
有一些页面,例如 types 这个分类页面其中标题应当是固定的,如果按照 Next 设置成一个固定的也不太符合要求,幸好可以使用 generateMetadata 函数来完成动态设置。
js
import { Metadata } from "next";
// either Static metadata
export const metadata: Metadata = {
title: "...",
};
// or Dynamic metadata
export async function generateMetadata({ params }) {
return {
title: "...",
};
}
看一下给出的示例,大概就明白如何使用了,不过这里在涉及博客的场景通常有一部分的标题是固定的,例如 yliu 的个人博客| css 选择器讲解
,其中前缀可能是固定的,一个个写也太繁琐了,下面就介绍一下如何编写动态标题以及固定前缀。
app/layout.tsx
js
export const metadata: Metadata = {
title: {
template: `%s | ${data.user.name} 的个人博客`,
default: `${data.user.name} 的个人博客`,
},
description: "记录生活随笔以及技术博客",
};
这里 %s
是占位符,default 则是如果页面没有设置标题默认输出 xxx 的个人博客标题。
app/types/page.tsx
js
export async function generateMetadata({
params: {
rest: [id],
},
}: Props) {
const current = data.label.find((f) => f.id === +id);
return {
title: current?.name,
};
}
这里就达到我们的最终效果了,当然其实 Metadata 对象包含很多属性,例如 description、keywords 等,所以如果想针对每个文章输出关键词都不相同可以使用 generateMetadata 来达到效果。
最后
如果文章有书写错误地方欢迎指出。下一篇会介绍构建以及开发中常见的一些问题,以及如何处理。