FXFX.GRID

旧 Next.js 框架下的布局取舍

Layout
React
Next.js 13

bilibili-evolved-doc 改造记录 - 异步 MarkDown 加载 中记录了把文档内容部分改造成异步加载的同时,解决了 Next 下布局的问题。 但在网站实际运行一段时间后,依然发现不小问题。

  1. 异步加载只是表现于首屏加载,在切换路由时并不会显示过渡动画
  2. SSG 基础结构遭到破坏,首屏加载闪烁

这些问题的出现是由于 Next.js(13 以前) 的路由设计,再加上我当时对 SSG/SSR 框架理解不足所导致的。 值得一提的是,Next.js 路由模式的设计缺陷导致了 Next.js 用户需要写一堆犄角旮旯的代码以实现想要的效果。

#原始模式

来自官方的案例中,在每个文件加入如下代码

// page component
export default function Page() {}

Page.getLayout = (props) => <Layout {...props}></Layout>;

然后在 _app.tsx 中获取组件

// _app.tsx
export default function MyApp({ Component, pageProps }) {
  const getLayout = Component.getLayout || ((page) => page);
  return getLayout(<Component {...pageProps} />);
}

如果你不想每个文件都手写一次 getLayout,当然可以写一个插件往页面内注入代码。代价是你将失去灵活切换布局的能力,而且可能你还需要花不少的时间学习如果编写插件。

#Hacker 模式

除了编写插件注入代码外,在社区中存在着一种利用动态路由的解决方案,也是 bilibili-evolved-doc 改造记录 - 异步 MarkDown 加载 一文中采用的方案。这种方案更为的灵活,但也有其相应的的代价。

假设现在想在文档页面套用一个相同的布局,那你至少需要这样的一个文件目录结构

pages
├── index.tsx
└── doc
    ├── Content.tsx
    └── [..slug].tsx

然后在 [..slug].tsx

  • 正确的编写 getStaticPaths 以使得 SSG(SSR) 模式正常的运行(这就是导致一开始问题 2 的原因)
  • 可能还需要编写 getStaticProps 以获取你想要的数据

以上两点就是这种布局模式的代价。另外,需要注意的是这两个方法都是在服务端运行的。这意味在这两个函数被调用的时候,也就是网站构建阶段,我们无法获得一些和浏览器相关的 API,比如 documents。这使得我们需要格外的注意某些代码(库),因为它们依赖浏览器的环境。而且,客户端于服务端之间只能互相传递序列化后的数据,这意味着我们不能直接返回一个组件。尽管这样非常的直观易懂,但我们不能这样做,我们必须在服务端处理好(以某种规则进行序列化)这些组件再返回给客户端。

可以发现无论是手写每个文件的布局、通过插件注入代码还是通过改变文件结构+代码实现,都会令这个简单的布局过程变得非常繁琐。

#更合理的路由模式

为什么我说简单(其实并不简单),是因为在 react-router 的世界里根本没有这个问题。

[
  {
    "path": "/docs",
    "element": {}, // <- docs 下的布局
    "children": []
  },
  {
    "path": "/", // <- 根页面
    "element": {}
  }
]

你甚至可以在 docs 下根据某些规则而套用不同的布局。

究其原因,是因为在 Next.js 中,其将一个网站假设成了一棵树,所有的路径都是 / 的子节点或者孙子节点。可现实情况中,网站的目录往往是一个森林,或者说多根树,也就是和例子中一样, //docs 有可能是同级的。正是因为 Next.js 的路由模式中缺失了这种路径组织方式,导致了原始模式下两难的场景,也导致了需要Hacker 模式这样的方案。

#究极的解决方案

当然是重构拉!这并不是在开玩笑,Next.js 13 更新中的重要的新功能就是路由模式和布局结构的改。

FXFX.THEME