在单页应用(SPA)开发中,“共享布局”几乎是每个项目都会遇到的刚需——导航栏、页脚、侧边栏等组件需要在多个页面间复用。而更进一步的诉求是:能否在构建时(build time)就提前生成这些带有共享布局的静态页面,从而提升首屏加载速度并利于 SEO?近日,React Router 团队与开源社区联合给出了一套轻量级的实现思路,无需依赖 Next.js 或 Gatsby 等重型框架,仅凭 React Router v6 的核心特性即可完成。

核心痛点:客户端路由 vs. 构建时预渲染

传统的 React Router 是纯客户端路由:浏览器下载完整的 JS bundle 后,根据 URL 动态挂载组件。这种方式虽然灵活,但存在两个明显的短板: - 首屏白屏:用户必须等待 JS 解析完毕才能看到布局与内容; - SEO 不友好:搜索引擎爬虫无法抓取客户端渲染生成的页面结构。

如果能在构建时就将每个路由对应的页面(包含公共布局)提前渲染成 HTML 文件,部署到 CDN 上,就能同时解决上述两个问题。这正是“构建时共享布局”要攻克的技术关键。

官方推举方案:布局路由 + 静态渲染

React Router v6 引入了“布局路由”(Layout Routes)的概念。开发者可以在路由配置中定义一个父级路由,其 element 属性指向共享布局组件,内部通过 <Outlet /> 插槽渲染子路由的内容。例如:

const router = createBrowserRouter([
  {
    element: <RootLayout />,   // 共享布局
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/about", element: <AboutPage /> },
      { path: "/contact", element: <ContactPage /> },
    ],
  },
]);

在构建时,只需借助 Node.js 脚本遍历路由树,利用 renderToStaticMarkuprenderToString 将每个路由下的完整页面(包括布局)输出为独立的 HTML 文件。以 Vite + React 项目为例,社区贡献的 react-router-static 工具包可以自动完成这一流程:解析路由配置、生成路由映射、并行渲染静态页面,并在每个文件中保留 <Outlet /> 渲染后的子组件内容。

关键一步:静态路由适配

要在 Node.js 环境中执行渲染,需要将 createBrowserRouter 替换为 createStaticRouter。React Router v6 提供了 @remix-run/router 包中的静态路由创建函数,它不依赖 window.location,适合在服务端或构建脚本中使用。典型实现如下:

import { createStaticRouter, createStaticHandler } from '@remix-run/router';
import { renderToStaticMarkup } from 'react-dom/server';

const routes = [
  {
    element: <RootLayout />,
    children: [
      { path: "/", element: <HomePage /> },
      // ... 其他子路由
    ],
  },
];

const { query, dataRoutes } = createStaticHandler(routes);

async function buildPage(url) {
  const context = await query(new Request(`http://localhost${url}`));
  const router = createStaticRouter(dataRoutes, context);
  return renderToStaticMarkup(
    <StaticRouterProvider router={router} context={context} />
  );
}

随后,遍历所有路由路径,调用 buildPage 生成 HTML 并写入 dist 目录。每个页面不仅包含共同的 <nav><footer>,还嵌入了该路由独有的内容,且 <link><meta> 等标签可通过路由级 loader 动态注入。

社区反响:轻量、可定制、无侵入

该方案在社交媒体上引起了前端工程师的热议。不少开发者表示,相比于引入整个 SSG 框架,React Router 原生的布局路由 + 自定义构建脚本的方式更加“克制”——项目不需要重构,仅需在构建流程中添加一个 Node 脚本即可。而且,由于渲染结果仍是标准的 React 应用,后续切换到客户端路由(如用户交互频繁的仪表盘)时也无需改动已有组件。

当然,也有批评者指出,该方法需要手动处理动态路由(如 /post/:id)的预渲染列表,以及处理 useEffect 等客户端副作用。对此,React Router 核心贡献者回应称,布局路由的构建时渲染最佳实践是配合 Suspense数据加载(loader)使用:在构建时只渲染不依赖浏览器 API 的静态内容,而需要实时数据的区域则保留为动态挂载。通过“静态骨架 + 动态插槽”的组合,可以兼顾速度与灵活性。

结语:SSR 之外的另一个选择

构建时预渲染(Static Site Generation)早已不是新鲜事,但过去一直由 Next.js、Gatsby 等框架主导。React Router 团队此次提供的方案,等于给了纯 React 开发者一把“轻量级的 SSG 钥匙”——无需框架迁移,仅靠路由库本身的升级,就能在构建时获得共享布局的静态页面。对于想逐步优化 SEO 和首屏性能的中小型项目而言,这无疑是一条低成本的实践路径。未来,随着 React Router 对 @remix-run/router 的进一步整合,构建时渲染的门槛还将继续降低。