使用 Next.js 与 Hexo 重构博客系统

· Technology

在 2019 到 2022 年,我的很多项目都依赖于服务器(或者说 VPS)。我曾经使用过阿里云、腾讯云、Vultr、Digital Ocean、GCP、AWS、Azure 等等,但最终我发现,这些服务器都不是我想要的。服务器的配置臃肿且复杂,尤其是当我手持数十台服务器,对他们的维护成为了一种痛苦。 试图把一个即将到期的服务器上的几个应用迁移到另一台服务器,与 LNMP 奋战了几个小时以后发现我操作的服务器是错误的,这件事恐怕是我一辈子的心理阴影。

于是,在 2023 年我逐步完善我的 Go 开发水平和容器化解决方案后,我将我几乎所有应用迁移到了 Kubernetes 上。自然,我就无需持有什么服务器了。 但我的博客使用 WordPress 构建,且 GCP 的 RWX PVC 价格极其高昂,我的博客便一直保留在服务器上(曾经有过,但是失败了)。

2023 年中,我开始思考,是否应该重构我的博客系统,以此使得我的博客也能够容器化,实现降本提效?在 2023 年末,我由于学业原因放弃了工作,时间也变得闲暇了很多。这给了我重构博客系统的机会,于是我开始了这次重构。

放弃工作自然导致我没有足够的资金来源,迁移到 Next.js 可以几乎把成本降低到 0,这都得益于 Vercel 免费高效的服务。还有 Waline 支持的 Vercel 部署,这也是为什么我选择了 Waline 作为我的评论系统。

Contents

为什么要重构

不仅仅是上文所讲的我不想继续持有 VPS,真正重构并选择使用如此的技术栈的原因还有很多。你可能会问,为什么我不能使用 SaaS 形式的 WordPress 服务?为什么我要使用 Hexo?为什么我在年中开始思考而直到现在才开始重构?

首先,WordPress 是一个非常优秀的 CMS 系统,它的生态也非常完善。但 WordPress 本身是基于 PHP 构建的,且其最大的卖点便是可扩展性,因此我无法将其重写,也很难在低成本的环境下优化其性能。

我曾经使用过 WordPress,并且在优化上奋斗了很久。我还曾发表过一篇文章讲述如何优化 WordPress1,但最终我还是放弃了 WordPress。WordPress 的优化是无止境的,也没什么技术含量,且低成本环境的算力瓶颈对于 PHP 应用实在难以突破。只是不断的缓存、缓存,用空间复杂度换取时间复杂度,仅此而已。其程序设计上的本身的问题仍然没有被解决。

即使将 WordPress 容器化,也面临很多的问题:

  1. WordPress 依赖文件存储,操作文件系统,必须使用 RWX(Read-Write Many) 模式的 PVC。其价格昂贵,相当昂贵。
  2. WordPress 依赖 MySQL,而我使用的 Cloud SQL 为 Postgres,因此我需要额外的数据库来运行 MySQL。

更何况,WordPress 由于历史原因,多适用于传统的网站。而我作为一个深度的 React 开发者,我更喜欢使用 React 开发主题。这就导致我一直不能为我自己的博客开发主题,而是使用了 Argon、MDx 等现成的主题,尽管我也参与到了它们的维护中。

依赖库和框架选用

我选择的框架为 Next.js,一个 Vercel 开发的 React 框架,而文章的存储依赖 Hexo Warehouse。你可能好奇,在诸多的选择中为什么我选择了这两种?这也是经过我深思熟虑的选择。 同时,我也能为你解答为什么我要拖到年末才开始重构的问题。

Next.js

我选择 Next.js 最初的原因很简单,因为它是一个 React 框架。我作为一个 React 开发者,我更喜欢使用 React。Next.js 本身高效、易用,且有着非常完善的生态。我相信每一个 React 开发者在面临需要做 Server-Side Rendering 的时候都会选择 Next.js。而且我有着丰富的 Next.js 开发经验,你可以在我的 GitHub 中找到很多相关开源项目,我在今年年初开发的 V2Board 前端重构项目也是基于 Next.js。

Next.js 走在时代前沿,其对于 React 的支持也是最好的。Vercel 开发了 SWC、Turborepo、Turbopack 等项目,而 Next.js 作为其自家的 T0 产品,自然也是第一时间支持这些的。这些项目的出现,使 Next.js 具有更高的性能。在 React Server Component 出现后,Next.js 也是第一时间发布了 App Router,使得 Next.js 中能够使用 Server Component。(即使用法有点弱智,我不得不把一个文件拆成好几个,就为了用那个 "use client";

RSC 使得我们能够在 Components 中直接调用 Hexo 并传递其内部的数据类型。Hexo 的 API 返回的大多是 Document 或者 Query 类。且为了优化性能,其内容很多为 getter / setter,而这些是无法被传递的。在先前的 Pages Router 中,我们需要将其转换为能够被 JSON serialize 的结构,然后再传递给 Components。因此在 App Router 下,我们能够少写很多数据传递的步骤。2

Hexo Warehouse

Hexo Warehouse 是一个基于 JSON 的数据仓储,其本身是为 Hexo 的构建过程而创建的。但它作为一个数据仓储,其本身并不依赖于 Hexo,能够被我们作为数据库一样使用。在今年下半年,Hexo Warehouse 完成了 TypeScript 的重构,使我能够更轻松的使用它。Hexo 也发布了版本 7.0.0,进一步完善了 API 和 TypeScript 支持。我终于不需要为了 Hexo 写 AnyScript 了!

其实我在今年年中就开始了重构,当时选择使用 Next.js (Pages Router) + Material UI + Hexo (v6),被折磨了好一段时间,最终放弃了。后面等待 Warehouse 和 Hexo 完成了对 TypeScript 的适配才删除项目重新开始。新一次项目使用了 Tailwind CSS,也是我的一个变化吧。

尽管使用了 Hexo Warehouse,但你很难从我的代码中找到 Hexo 的影子。它只在 Server-Side 被使用,而且只是作为一个数据仓储。我并没有使用 Hexo 本身的功能,Markdown 也是另采用的解析库。所以尽管我初始化的是 Hexo 实例,但使用的还是以其 config 和 warehouse 为主。

是的,我使用 Hexo Config 配置大多数的内容设置,比如 Title、Description、Author 等等。因为我不想定义更多的 config,也不想在 Next.js 的 ENV 中定义这些。

Tailwind CSS

Tailwind CSS 是一个 CSS 框架,其本身并不是一个 UI 框架。它的特点是使用 Utility-First 的方式,而不是像 Bootstrap 那样使用。如果你安装了 Wappalyzer,可能会发现我还使用了 shadcn/ui ,这是因为从今年开始我就在追寻设计上的极简主义。这种纯色调的设计可以让读者更加专注于文章本身,而不是花里胡哨的设计。

而且众所周知,在组件复用率足够高的情况下,Tailwind CSS 写起来是非常舒服的。我不需要去写一堆的 CSS,只需要写一堆的 Utility Class 就可以了。而我的习惯是尽可能避免重复,因此我的复用率也是很高的,这也是原因之一。

当然,Tailwind CSS 作为一个 CSS 框架,其本身并不是一个 UI 框架,因此也使我受到一些限制。比如我无法混淆 CSS,且 RSC 传递 CSS 的数据占用是很大的(因为转义),这也是我最为头疼的点之一。在后文会详细解释我当前面临的诸多问题。

Turborepo & pnpm workspace

由于我的项目本质上是 Next.js 的 Web App 调用 Hexo 的实例,因此 Hexo 也需要作为一个 package 使用。但我觉得在一个 package 中存在另一个 package 很不优雅,所以我使用 monorepo 的方案使这两个 package 单独存在。

pnpm 是我常用的依赖管理工具,其本身支持 monorepo,即 pnpm-workspace.yaml。Turbopack 具有更多的功能,且 Vercel 对它提供了完善的支持,因此我加入了 Turbopack 来管理任务、并行构建和远程缓存。于是在此情况下,pnpm workspace 仅作为 monorepo 的依赖管理工具,而构建方面则交给了 Turbopack。

  1. 任务管理:使用 turbo.json 管理和定义任务,包括任务之间的关系,可以进一步提高代码复用率。
  2. 并行构建:使用 turbo build 命令构建项目,其会自动并行构建。
  3. 远程缓存:turbo 会将你本地构建的结果缓存到 Vercel,当我提交代码后 Vercel 会根据缓存的结果进行增量构建,显著提高了部署速度。
  4. 哈希感知:turbo 会根据文件的哈希值来判断文件是否发生了变化,而不是根据文件的修改时间。

当然,我实际仓库中并不只有这两个 packages,也包括了一些依赖库、工具库和 eslint 配置等。

开发过程中遇到的问题

尽管有前人的文章参考和我个人的经验,但我在开发过程中还是遇到了很多问题。这些问题有些是我自己的问题,有些是由于我使用的技术栈导致的。这些问题都是我在开发过程中踩的坑,希望能为后人提供一些参考。

代码块渲染中对于 pre > codecode 的区分

我使用 react-markdown 来渲染 Markdown,它对于代码块的渲染是 <pre><code></code></pre> 。但我需要对于代码块进行一些额外的处理,比如 <code> 中的内容只需要简单的用背景和字体衬托,而 <pre> 中的内容则需要用丰富的样式。

在此情况下,Tailwind CSS 的样式系统无法支撑复杂的严格的样式。因此我需要使用其它的方式提供 CSS。目前我直接引入了 Scss,这也与 PostCss 相适应,但我还是准备把它换成 Style9 或 emotion。

.article {
  pre {
    @apply pt-6 px-4 pb-2 overflow-visible relative;

    tab-size: 2;

    :before {
      content: "";
      position: absolute;
      background: url(data:image/svg+xml;base64,...) no-repeat;
      background-position-y: center;
      top: 22px;
      left: 20px;
      height: 1rem;
      width: 3.5rem;
      margin-left: 0.3rem;
      display: block;
    }

    code {
      @apply px-2 py-4 mt-4 block leading-normal;
    }
  }
}

归根结底,Tailwind CSS 的方便性也是其不足的地方,它的样式系统无法支撑复杂的样式。

自动生成 Excerpt

由于我的文章是使用 Markdown 编写的,因此获取 Excerpt 是一个很麻烦的事情。我早期的文章没有严格遵循格式,很多都没有 Abstract 部分。甚至有的在开头就有图片一类的东西。因此我无法使用常规的替换方式来获取 Excerpt。目前我的解决方案是在每个文章的 Front Matter 部分添加 excerpt 字段,手动填写 Excerpt。

由于我迁移后删除了很多我觉得比较弱智的文章,所以手动操作也不是很麻烦。但我还是希望能够自动获取 Excerpt,这样会显得更 Automation。

目前我仍然没有找到解决方案,我也不想使用 Hexo 的 API 来获取文章的内容。也正是因此,重构 Markdown AST 解析被提上了日程。

文章目录生成和 Heading 组件 id

当前我的文章目录是使用 react-markdownremark-toc 插件生成的。这个插件识别 Heading 中的内容,当其匹配则生成目录。但 remark-toc 生成的目录中携带的 anchor 的 hash 部分无法适配特殊符号,因此你可以看到此文章的一些目录不太正常……

就我看来,我应当单独使用一个组件来生成目录,并将目录放置到合适的位置。但这也就意味着,我还是需要自己去解析 Markdown AST。

Webpack bundle 后 Hexo 无法调用 fs

在我博客开发的早起,引入 Hexo 依赖时,只要我在 Server Components 中使用了 Hexo,都会出现错误:

../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

在 Console 也有错误:

@ahdark-blog/web:dev:  ⨯ ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: Module parse failed: Unexpected character '�' (1:0)
@ahdark-blog/web:dev: You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
@ahdark-blog/web:dev: (Source code omitted for this binary file)
@ahdark-blog/web:dev: 
@ahdark-blog/web:dev: Import trace for requested module:
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/lib/fsevents-handler.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/index.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo-fs@4.1.1/node_modules/hexo-fs/dist/fs.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo@7.0.0/node_modules/hexo/dist/hexo/index.js
@ahdark-blog/web:dev: ./services/hexo/instance.ts
@ahdark-blog/web:dev: ./app/not-found.tsx
@ahdark-blog/web:dev:  ⨯ ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: Module parse failed: Unexpected character '�' (1:0)
@ahdark-blog/web:dev: You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
@ahdark-blog/web:dev: (Source code omitted for this binary file)
@ahdark-blog/web:dev: 
@ahdark-blog/web:dev: Import trace for requested module:
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/lib/fsevents-handler.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/index.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo-fs@4.1.1/node_modules/hexo-fs/dist/fs.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo@7.0.0/node_modules/hexo/dist/hexo/index.js
@ahdark-blog/web:dev: ./services/hexo/instance.ts
@ahdark-blog/web:dev: ./app/post/[slug]/page.tsx
@ahdark-blog/web:dev: <w> [webpack.cache.PackFileCacheStrategy] Restoring pack failed from /Users/ahdark/Development/AH-dark/ahdark-blog/packages/web/.next/cache/webpack/client-development-fallback.pack.gz: Error: invalid literal/lengths set
@ahdark-blog/web:dev:  ⨯ ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: Module parse failed: Unexpected character '�' (1:0)
@ahdark-blog/web:dev: You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
@ahdark-blog/web:dev: (Source code omitted for this binary file)
@ahdark-blog/web:dev: 
@ahdark-blog/web:dev: Import trace for requested module:
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node
@ahdark-blog/web:dev: ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/lib/fsevents-handler.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/chokidar@3.5.3/node_modules/chokidar/index.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo-fs@4.1.1/node_modules/hexo-fs/dist/fs.js
@ahdark-blog/web:dev: ../../node_modules/.pnpm/hexo@7.0.0/node_modules/hexo/dist/hexo/index.js
@ahdark-blog/web:dev: ./services/hexo/instance.ts
@ahdark-blog/web:dev: ./app/post/[slug]/page.tsx

通过查询相关资料,我发现这是由于 Webpack bundle 后,Hexo 无法调用 fs 导致的。Next.js 提供了一个 experimental 的设置项 serverComponentsExternalPackages,可以将使用 Node.js 环境的 Packages 排除在外。3

根据错误内容,我将 hexohexo-fshexo-util 添加到了 serverComponentsExternalPackages 中,以此解决了这个问题。

monorepo 结构下 Vercel 缓存 package 导致文章无法正确更新

在我使用 monorepo 的结构后,Vercel 会缓存 monorepo 下的 package,导致我修改文章后无法正确更新。这是由于 Vercel 的构建缓存机制导致的。

目前我的方案是设置环境变量 VERCEL_FORCE_NO_BUILD_CACHEtrue,以此使得 Vercel 不缓存构建结果。但在某些情况下,Vercel 还是不会同步 hexo package 的更改。此问题仍未完全解决,且当前的解决方案会整体减缓构建速度,仍需进一步优化。

为此,我在 Vercel 的 GitHub Discussions 中提出了这个问题,但目前还没有得到回复。4

ApexCharts 的渲染问题

我使用 ApexCharts 进行图表渲染,这是沿用自 Mantis 的方案。但在 Next.js 中,ApexCharts 的渲染存在一定问题,尤其是在 SSR 方面。

首先,由于 ApexCharts 的 API 使用,其必须在非 SSR 环境下才能正确渲染。因此我需要使用 Next.js 的 dynamic import:

import dynamic from "next/dynamic";

const ReactApexCharts = dynamic(() => import("react-apexcharts"), {
  ssr: false,
});

且这个引入是在 Client Components 中,我的实际调用应当对这个组件调用而非每次都使用 dynamic import。更何况,尽管图表对于 SEO 的影响不大,将其放到客户端渲染也能减少一些性能开销,但总归是不够优雅的。

其次,由于 RSC 无法传递函数等,而 ApexCharts 的 API 需要传递函数进行 formatting 等工作,因此我也不得不把整个图标的数据部分和渲染部分都放到客户端,使用 API Route 调用数据,然后渲染图表。

Vercel 环境下 Static 渲染的内容无法获取 Hexo 实例

由于 Vercel 的静态构建,我无法在 Static 渲染的页面中和带有 POST 方法的 Route 中获取 Hexo 实例。 当我在未使用 SSG 的页面中,Hexo 无法获取到实例,因此我无法获取文章列表。在带有 POST 方法的 Route 中,Hexo 也面临同样的问题。

目前的解决方案是在 dynamic routes (pages) 中使用 SSG,在我的应用场景下这是可行的,也的确提高了 Performance。

而在 Route Handler 中,我通过导出 dynamic 变量来定义组件的渲染模式,同时避免使用 POST 方法。

export const dynamic = 'force-dynamic' // defaults to auto, or 'force-static'

此外,我还建议在 Response 中定义合适的 Cache-Control,这可以使 Vercel 缓存页面,减少不必要的计算。

Background Color 无法使用 transition

由于我的 Theme 由 shadcn/ui 提供,其使用了 CSS Variables 来定义不同主题(Light / Dark)下的颜色。但 CSS Variables 的改变不会触发 Transition,因此我无法使用 Transition 来实现主题切换的动画。所以你可能看到,我的主题切换是瞬间完成的,没有任何动画来过渡。

我不想使用 JavaScript 操作 DOM 来实现这个动画,而是尽可能使用 CSS 来实现这个动画。目前我还没有找到解决方案。我可能会在未来使用 Style9 替代 Tailwind CSS 的类处理方案,使 Tailwind CSS 仅作为 macro 使用。

/* globals.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
    :root {
        --background: 0 0% 100%;
        --foreground: 222.2 47.4% 11.2%;

        --muted: 210 40% 96.1%;
        --muted-foreground: 215.4 16.3% 46.9%;

        --popover: 0 0% 100%;
        --popover-foreground: 222.2 47.4% 11.2%;

        --border: 214.3 31.8% 91.4%;
        --input: 214.3 31.8% 91.4%;

        --card: 0 0% 100%;
        --card-foreground: 222.2 47.4% 11.2%;

        --primary: 222.2 47.4% 11.2%;
        --primary-foreground: 210 40% 98%;

        --secondary: 210 40% 96.1%;
        --secondary-foreground: 222.2 47.4% 11.2%;

        --accent: 210 40% 96.1%;
        --accent-foreground: 222.2 47.4% 11.2%;

        --destructive: 0 100% 50%;
        --destructive-foreground: 210 40% 98%;

        --ring: 215 20.2% 65.1%;

        --radius: 0.5rem;
    }

    .dark {
        --background: 224 71% 4%;
        --foreground: 213 31% 91%;

        --muted: 223 47% 11%;
        --muted-foreground: 215.4 16.3% 56.9%;

        --accent: 216 34% 17%;
        --accent-foreground: 210 40% 98%;

        --popover: 224 71% 4%;
        --popover-foreground: 215 20.2% 65.1%;

        --border: 216 34% 17%;
        --input: 216 34% 17%;

        --card: 224 71% 4%;
        --card-foreground: 213 31% 91%;

        --primary: 210 40% 98%;
        --primary-foreground: 222.2 47.4% 1.2%;

        --secondary: 222.2 47.4% 11.2%;
        --secondary-foreground: 210 40% 98%;

        --destructive: 0 63% 31%;
        --destructive-foreground: 210 40% 98%;

        --ring: 216 34% 17%;

        --radius: 0.5rem;
    }
}

@layer base {
    * {
        @apply border-border;
    }

    body {
        @apply bg-background text-foreground;
        font-feature-settings: "rlig" 1, "calt" 1;
    }
}

未来的展望

当前我的 UI/UX 以及一些功能还不是很完善,很多已经实现的并不够完美,还有很多需要改进的地方。我也有一些想法,但是由于时间和能力的限制,我并没有实现。我会在未来的时间里继续完善我的博客系统。

基于 WASM 重写 Markdown AST 解析

当前的 Markdown AST 由 react-markdown 提供,但其并不是我想要的。其内部调用过于复杂,插件难以开发,难以系统性管理文章内容。我可能通过 WASM 引入一些外部库进行部分或整体的解析,来更好的实现目录、Excerpt 自动获取等功能。

近期我正在学习 Rust,我可能会使用 Rust 来编写这个解析器。

使用 Style9 管理 CSS

当前我使用 Tailwind CSS 来管理 CSS,但其样式系统并不够完美:

  1. 所有人使用统一的类名,容易被盗用。
  2. 依赖于 PostCss 的预处理,我很难将 UI 库分离到另一个 monorepo package 中。
  3. 无法混淆 CSS,导致 CSS 文件过大,尤其是在 RSC 中。
  4. Tailwind CSS 的 Theme 难以使用 Transition。

因此我可能会使用 Style9 来管理 CSS,其样式系统更加完善,且可以使用 CSS Modules 来管理 CSS。但我需要解决的问题是,如何在 Style9 中使用 Tailwind CSS 的类名。一个可行的方案是 stailwc,但在 Next.js 14 + App Router 中我还无法正常使用它。5

致谢

在本次项目的开发过程中,我得到了很多人的帮助,我在此向他们表示感谢。

前人的文章

如果没有 Sukka 和 fengkx 的两篇文章,我可能无法完成这次重构。他们的文章为我提供了很多的参考,使我能够更好的完成这次重构。

此外,在样式上我也参考了 Sukka 的博客,尤其是他的 Layout 部分,在此向他表示感谢。

开源项目

我在开发过程中使用了很多开源项目,他们为我提供了很多的支持,使我能够更好的完成这次重构。

包括为开源社区做出极大贡献的 Vercel,使我能够使用 Next.js、Turborepo 和 SWC,以及免费的构建和部署服务:Vercel

测试期得到的意见

在博客的公开测试时期,很多朋友看到我在各种平台发布的消息后为我提供了很多的意见和建议,使我能够更好的完善我的 UI/UX Design。在此向他们表示感谢。

UI/UX 优化

  • QQ 1257135905 Yui
  • QQ 2639365465 TheOrdinaryWow
  • QQ 2727341319 時间
  • QQ 970704142 青木
  • QQ 2652720816 Eacls
  • QQ 1846405136 Whitebear
  • QQ 1123801846 小桦
  • QQ 844177330 網絡乞丐
  • QQ 2076274471 原罪_超凡
Eacls 提供的 UI 方面建议
Eacls 提供的 UI 方面建议

Bug 反馈:Pagination 造成页面横向出现滚动

  • QQ 2744601427 愛ゆ
  • QQ 1214050656 玻璃晴朗
  • QQ 3216627800 哦

Footnotes

  1. 如何有效提高 WordPress 博客的访问速度

  2. https://blog.skk.moe/post/refactor-my-blog-using-nextjs-app-router

  3. https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages

  4. https://github.com/orgs/vercel/discussions/5277

  5. https://www.fengkx.top/post/rewrite-blog-with-next#CSS-%E6%96%B9%E6%A1%88%E9%80%89%E6%8B%A9

Comments

Send Comments

Markdown supported. Please keep comments clean.