yliu

时来天地皆同力,运去英雄不自由

Next.js 构建博客之打包SSG


image.png

  1. Next.js 构建博客之资源抓取
  2. Next.js 构建博客之博客搭建
  3. Next.js 构建博客之打包 SSG
  4. Next.js 构建博客之常见问题处理
  5. Next.js 构建博客之功能拓展
  6. Next.js 构建博客之自动构建

这是 Next.js 构建博客的第三篇文章,上一篇文章 Next.js 构建博客之博客搭建 介绍了博客的搭建过程,这篇文章则重点介绍一下如何将资源打包成 SSG。

如果你想看已经部署博客的地址可以点击查看,代码仓库地址点击查看

SSG 的全称是 Static Site Generation 即静态渲染,不过在介绍之前先说一下主流的渲染方式:

  1. 客户端渲染,我们常见的 Vue、React 等默认就是客户端渲染,不过这种方式最大弊端就是首屏速度以及 seo 抓取,这刚好不符合我们博客场景;

image-1.png

  1. ssr,其实就是代码跑在 nodejs 上,然后输出渲染的 html 字符串,之后浏览器获取 js 资源后执行同构代码,并将 DOM 元素绑定事件等,不过这里也不符合场景,代码最终部署在 GitHub 上其实是没有额外的服务器让我们执行;

image-2.png

  1. SSG,就是将动态数据输出成固定的 html 文件,兼顾了文件缓存以及 seo 的需求,完美符合我们使用场景;

image.png

开启 SSG

上一篇中我们列举了一下页面的路由:

  • /:首页
  • pages:分页页面
  • details/:id:详情页面
  • types/:id:分类页面

对于固定的页面我们不需要做什么,但是对于携带 :id 的动态页面,我们需要把所有的条件都枚举出来,只有这样才能输出所有的页面信息,不至于点击某一个文章直接 404 了。

枚举所有条件可以使用 generateStaticParams 完成,这里看一个官方给出的例子:

// 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 页面进行改造

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 按照顺序返回即可,例如:

// app/pages/[...rest]/page.tsx
return [
  {
    rest: [id, name],
  },
  {
    rest: [id, name],
  },
  // ...
];

之后调整 next.config.js 文件

const nextConfig = {
  // ...
  output: "export",
};

module.exports = nextConfig;

之后在 app/layout.tsx 文件添加

export const dynamic = "error";

这里防止在代码中引用 SSG 不支持的一些功能,具体不支持功能可以点击查看

动态标题

有一些页面,例如 types 这个分类页面其中标题应当是固定的,如果按照 Next 设置成一个固定的也不太符合要求,幸好可以使用 generateMetadata 函数来完成动态设置。

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

export const metadata: Metadata = {
  title: {
    template: `%s | ${data.user.name} 的个人博客`,
    default: `${data.user.name} 的个人博客`,
  },
  description: "记录生活随笔以及技术博客",
};

这里 %s 是占位符,default 则是如果页面没有设置标题默认输出 xxx 的个人博客标题。

app/types/page.tsx

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 来达到效果。

最后

如果文章有书写错误地方欢迎指出。下一篇 Next.js 构建博客之常见问题处理 会介绍构建以及开发中常见的一些问题,以及如何处理。