使用 Next.js 的 pages 路由 SSG 方案,接管博客渲染层,实现纯静态+单页应用+预渲染效果

视图层升级:从 PHP 到 Next.js

作为博客站点,从 SEO 等角度,HTML 代码预生成的能力是必须的。由于成本考虑,博客一直使用 PHP 虚拟主机,所以一直使用了 PHP 的服务端渲染。

但是 PHP 视图层的开发体验,与现有的前端生态较难接轨,开发效率不佳。在了解 Next.js 时,看到了静态页面生成 SSG (Static Site Generation) 的方案,这让我眼前一亮。

用了几个小时的试验,成功将视图层从 PHP 升级到了 Next.js。

实现博客列表页、详情页

使用 npx create-next-app@latest 快速创建了 ts 的项目模板,结合 Pre-rendering and Data Fetching官方文档教程,很快的将博客列表页、详情页渲染了出来。

比如,对于博客的列表页,创建 /posts/${page} 规则的路由。这里为了减少 DOM 渲染,还是保留了分页逻辑。

getStaticPaths 中,计算一共需要生成多少个“列表页”。先拉去总共的文章数量,每 20 篇文章生成一个列表页。

interface Params extends ParsedUrlQuery {
  page: string;
}
export const getStaticPaths: GetStaticPaths<Params> = async () => {
  let { totalPage } = await getPostList({ page: 1, count: 20 });
  return {
    paths: Array.from({ length: totalPage }).map((_, index) => {
      return {
        params: { page: (index + 1).toString() },
      };
    }),
    fallback: false,
  };
};

getStaticProps 中,为每个页面拉取数据,比如第 5 页,就会去接口拿第五页的 20 篇文章进行渲染。

export const getStaticProps: GetStaticProps<PostListProps, Params> = async (context) => {
  const { page } = context.params! as Params;
  let { posts, current, pageSize, totalCount } = await getPostList({
    page: Number(page) || 1,
    count: 20,
  });
  return {
    props: {posts, current, pageSize, totalCount,},
  };
};

通过这样的数据获取,在构建时,页面组件会接受入参,进行渲染和处理即可。

export default function PostDataListPage({ posts, current, pageSize, totalCount }: PostListProps) {
  return <div> ... </div>;
}

实现搜索页面

对于动态搜索,也是必不可少的。 SSG 方案的妙处在于,它会将入参数据生成为 json 文件,能作为动态数据处理。

在文章列表页的基础上,提取了文章列表、分页等样式作为文章列表样式组件,在原有的 pages 下只做数据处理,并创建 /pages/search 页面用于处理搜索:

  • 入参 posts 代表全部文章数据,这个会在构建时存储到页面中。
  • 使用 data 表示过滤后的数据,使用 page 表示当前的页码,通过 slice 计算出当前页应当渲染的 20 篇文章。
  • 使用 qeury.v 来表示用户输入的过滤文本,结合路由 query 参数的改变,更新过滤页面应该展示的 data 数据。
const router = useRouter();
const [data, setData] = useState<Post[]>([]);
const [page, setPage] = useState(1);

const getData = () => {
  let val = router.query.v.toLocaleLowerCase();
  setData(
    posts.filter((item) => {
      return (
        item.title.toLocaleLowerCase().includes(val) ||
        item.remark.toLocaleLowerCase().includes(val) ||
        item.content.includes(val)
      );
    }),
  );
  setPage(1);
};

useEffect(() => {
  getData();
}, [router.query.v, posts]);

const limitNum = (page - 1) * 20;
const viewPosts = data.slice(limitNum, limitNum + 20);

在文章数据不多的情况下,这个搜索方案也可以获得较快的速度。

升级效果

本次视图层的升级,开发体验和站点性能都得到了较大提升。

开发体验:

  • 甩开视图层的 PHP ,接口服务变得更加纯粹,只需专注于后端业务逻辑。
  • Next.js 让廉价的虚拟主机能够保持 SEO 能力的同时,轻易的用上 React、TypeScript 等主流的前端技术。
  • 纯静态一直以来是 PHP 站点的重要优化方案,但会增加了较大复杂度,而在 Next.js 中使用很简单的前端代码,就能够完成这一切。

站点性能:

  • 比起传统的纯静态方案,Next.js 的 SSG 更具备了预渲染的能力。
  • 并且,它在进入下一个路由时,不是直接跳转页面,而是作为单页应用,去加载 json 数据,直接渲染。
  • 这使得页面之间的切换变得极快,无需访问数据库,也无需加载额外的前端页面及静态资源。

由于构建是在本地进行,所以本地始终会保留一份最新的静态站点和 JSON 数据,也算是一种数据备份。

Next.js 升级到 app 路由

博客升级到 Next.js 14 ,迁移到 app 路由,升级 SSG 模式 文章 SSG 升级 中说明。

本文收录于专栏
一些博客建设的记录
使用 Next.js 搭建 SSR 全栈 demo ,以及构建 SSG 纯静态博客,记录学习和使用笔记