Skip to content

vitepress-rendering-strategies

VitePress 引入跨框架组件渲染能力(当前内置 React),在不改变 VitePressSSG 架构前提下提供更细粒度的渲染与 Hydration 策略,灵感参考自 AstroIslands Architecture

  • 跨框架渲染: 先支持 React,规划扩展到 Solid/Svelte/Preact/Angular 等。
  • 四种客户端指令: client:onlyclient:loadclient:visiblessr:only(默认)。
  • SPA 同步渲染优化: spa:sync-render/spa:sr,在路由切换时同步注入关键组件的预渲染输出,避免页面闪烁。
  • 一致的 Dev/Prod 体验: Dev 提供 HMRProd 生成页面级 metafilemodulepreload 以优化切页性能。
  • MPA 模式兼容: 兼容 VitePress MPA 模式。

更完整的策略设计、动机与示例,见文档 VitePress Cross-Framework Rendering Strategy

要求

  • Node.js: ^20.19.0 或 >=22.12.0
  • VitePress: ^1.6.3
  • React/ReactDOM: ^18.2.0
  • @vitejs/plugin-react-swc: ^3.9.0

安装

bash
pnpm add -D vitepress-rendering-strategies @vitejs/plugin-react-swc
pnpm add react react-dom

快速开始

  1. VitePress 配置中启用 React 渲染策略插件:

    ts
    // .vitepress/config.ts
    import { defineConfig } from 'vitepress';
    import vitepressReactRenderingStrategies from 'vitepress-rendering-strategies/react';
    
    const vitePressConfig = defineConfig({
      // 你的 VitePress 配置...
    });
    
    // 向 Vite 配置注入 React 渲染支持与构建期优化
    vitepressReactRenderingStrategies(vitePressConfig);
    
    export default vitePressConfig;
  2. 在主题增强中安装客户端运行时:

    ts
    // .vitepress/theme/index.ts
    import DefaultTheme from 'vitepress/theme';
    import reactClientIntegration from 'vitepress-rendering-strategies/react/client';
    import type { Theme } from 'vitepress';
    
    const theme: Theme = {
      extends: DefaultTheme,
      async enhanceApp() {
        await reactClientIntegration();
      }
    };
    
    export default theme;
  3. Markdown 中导入 React 组件并使用渲染指令:

    md
    <script lang="react">
      import Landing from '../components/Landing';
    </script>
    
    <Landing ssr:only spa:sr title="Hello" />

注意事项

  1. 组件标签命名

    • 必须以大写字母开头(React 风格),例如 MyComp
    • 标签名必须与同一 .md 文件内 <script lang="react"> 块中的本地导入名完全一致。如果使用了别名导入(如 import { Landing as HomeLanding } from '...';),则标签必须写为 <HomeLanding ... />
    • 任何不匹配情况都会在编译时跳过,并输出一条告警。
  2. 仅支持自闭合标签

    • Markdown 中的 React 组件必须写成自闭合形式:<Comp ... />
    • 非自闭合形式(如 <Comp>...</Comp>)会被跳过并输出一条告警。
  3. 位置与导入

    • 组件必须在同一 Markdown 页面内的 <script lang="react"> 块中完成导入,未导入的组件会被忽略。
    • 组件可以在 Vue 的插槽/模板中使用(例如在 <template #default>...</template> 内部),也会被正确发现并转换。
  4. Props 传递(一次性)

    • 标签上的所有非策略属性会作为字符串 props 传递给 React 组件。Vue 绑定(如 :page-title="page.title")会先由 Vue 求值为 DOM 属性,再在 React 渲染/水合时转发为 props。这是一次性数据传递,非响应式。
    • 不要通过属性传递函数或事件处理(如 onClick);当前不支持跨框架桥接可调用的 props/事件。
  5. 支持的指令

    • client:onlyclient:loadclient:visiblessr:only(默认)。
    • spa:sync-render(即 spa:sr)对 client:* 默认关闭,对 ssr:only 默认开启(除非显式 spa:sync-render:disable/spa:sr:disable)。
  6. 忽略区域

    • 围栏代码块中的组件标签不会被解析(设计如此)。

组件编写规范(TypeScript)

tsx
// components/Landing.tsx
import { useState } from 'react';

export interface LandingProps {
  title: string;
}

export default function Landing(props: LandingProps) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h2>{props.title}</h2>
      <button type="button" onClick={() => setCount(c => c + 1)}>
        Click
      </button>
      <span>{count}</span>
    </div>
  );
}

约束:<script lang="react"> 里仅支持静态 ESM import。初始渲染时的 props 为一次性快照,非响应式双向绑定(通过父级 Vue 传入的数据只用于初始化)。

渲染指令与行为

指令一览

  • ssr:only(默认)
    • 构建期完成预渲染,仅输出静态 HTML,不执行客户端 Hydration
    • 适合静态内容、SEO 关键内容,最利于 FCP/LCP/SEO,并避免增加客户端 JS 体积。
  • client:load
    • 先预渲染 HTML,随后在客户端立即 Hydration 接管交互。
    • 适合首屏关键且需要交互的组件;对 TTI 有一定压力。
  • client:visible
    • 预渲染 HTML,组件进入视口后再 Hydration
    • 适合非首屏交互组件(评论区、图表等);脚本默认预加载,非纯惰性。
  • client:only
    • 仅客户端渲染,无 SSR/SSG 预渲染。
    • 适合强宿主依赖或非关键、轻量组件。

SPA 同步渲染(spa:sync-render / spa:sr

VitePressSPA 路由切换时,Vue 内容同步更新;非 Vue 组件(如 React)的预渲染 HTML 与客户端脚本加载是异步的,可能造成“闪烁”。spa:sr 通过将目标页面中使用该指令的组件的预渲染输出合并到 Vue 的客户端脚本,同步注入以消除闪烁。

默认规则:

  • 使用 client:* 指令的组件默认不开启 spa:sr,除非显式标注 spa:sr/spa:sync-render
  • 使用 ssr:only 的组件(以及无指令组件)默认开启 spa:sr,除非显式 spa:sr:disable/spa:sync-render:disable

权衡:spa:sr 改善切页体验,但会增大切页加载的客户端脚本体积,建议对 关键渲染组件 启用。

示例:

md
<Landing client:load spa:sr title="Home" />
<Hero ssr:only />
<Chart client:visible />
<Widget client:only />

架构与运行时

  • 编译期(transformHtml + Rollup)
    • 扫描 .md 中的 <script lang="react">,静态解析 import,提取组件映射。
    • 将形如 <MyComp ...> 的自定义标签替换为占位 <div>,注入内部属性:渲染指令、renderId、组件名、spa:sr 标记等。
    • 按页面聚合 React 依赖,分别产出:
      • 组件浏览器端 loader 脚本与对应的 modulepreloadCSS 依赖;
      • SSR 产物与注入脚本(用于切页时补丁预渲染 HTML);
      • React/ReactDOM 的稳定 chunkassets/chunks/react@x.jsclient@x.js)。
  • 开发期(SPA + HMR)
    • 首屏与切页均在客户端根据占位节点信息进行渲染/Hydration
    • client:load/client:visible/ssr:only 组件,在路由变更时先通过服务端 SSR 返回 HTML 片段,再完成客户端渲染/Hydration,避免状态丢失与闪烁。
  • 生产期(SSG + SPA 切页)
    • 构建时生成全站 vrite-page-metafile.json,记录每个页面 React 组件的 loader 脚本、modulepreload 与可选 SSR 注入脚本。
    • 初次进入页面先注入当前页必要脚本,渲染/水合后再按需预加载其他页面的入口脚本以优化后续切页。
  • MPA 模式
    • 单页预览互不跳转,无需全局 metafile;每页暴露自身所需脚本即可。

错误处理:Hydration 失败会自动回退到客户端渲染,保证用户体验不中断。

使用建议与限制

  • 默认以 ssr:only 渲染,建议仅对需要交互的关键组件使用 client:load;非首屏交互组件用 client:visibleclient:only 适合强宿主依赖或降级。
  • 对开启 spa:sr 的组件保持克制,避免过多增大切页脚本负担。
  • 目录结构需与路由保持一致,否则可能导致资源定位失败。

许可证

MIT

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (eebb97a)