> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CSR 项目改造为 SSG（vite-react-ssg）

> 将现有 CSR 项目迁移到 Vite + React SSG：保持 App.tsx 路由源、补齐 ssg-entry 与 getStaticPaths、处理 i18n/主题/Sonner 的 SSR 安全，并确保可构建与稳定 hydration。

# CSR 项目改造为 SSG（vite-react-ssg）

> ⚠️ **已退役 · 禁止新用**：SSG（vite-react-ssg）已不再是任何 SEO 改造选择，新旧项目一律走[请求期 SSR](/superun/skills/seo/ssr)。**已在 vite-react-ssg 的残留项目请按 [SSG→SSR 迁移](/superun/skills/seo/ssg-to-ssr) 迁出**（不论内容静态/动态），不要继续停在 SSG。本指南仅留作历史参考。

你是代码迁移 Agent。你的任务是在当前项目中完成 CSR -> SSG 迁移，并保证可稳定构建与 hydration。

## A. 必须遵守（MUST）

1. 必须保留 `src/App.tsx` 的 JSX 路由声明（`createRoutesFromElements + <Route />`）。
2. 必须导出 `RouteRecord[]` 给 `vite-react-ssg` 使用。
3. 必须保持 `src/App.tsx` 为路由源文件，不引入约定式路由方案（禁止 `root.tsx` / `routes.ts` 重构）。
4. 必须确保构建产物包含静态 HTML 页面（SSG）。
5. 必须使用 `react-router-dom@^6.30.3`（若检测为 v7，先降级）。
6. 必须将仅客户端能力（如 `Sonner`、`next-themes` 主题读取）做“挂载后渲染”保护。
7. 必须对首页默认执行“SEO 可见性优化”：SSG 输出可见最终态，客户端挂载后再接管动画与数据增强。
8. 必须将首页及核心入口的内部路由改造为 SEO 友好形式：静态 HTML 中可解析目标 URL，避免仅依赖运行时事件跳转。
9. 必须保持 `src/App.tsx` 中 `// @side` 注释块结构稳定（注释文本、分组边界、相对顺序不变），仅允许在对应分组内做最小必要改动。
10. 必须遵守 React Router v6 嵌套路由路径规则：顶层路由用绝对路径（带 `/`），子路由用相对路径（不带 `/`），根重定向与兜底路由使用 `index` 或 `path="*"`。

## B. 明确禁止（DO NOT）

1. 不要把路由体系迁移成文件路由或框架路由。
2. 不要在模块顶层执行任何依赖浏览器环境的逻辑（`window`/`document`/`localStorage`/`matchMedia`）。
3. 不要在 SSR 构建阶段保留 `manualChunks` 对 `react`、`react-dom` 的拆分配置。
4. 不要修改与本迁移无关的业务行为。

## C. 执行步骤（按顺序）

### 1) SSR 安全全量预扫描（编码前必须完成）

* 在任何改造前，先对 `src/` 全目录做一次性预扫描并汇总“待修改文件清单”，避免进入“改造 -> 部署失败 -> 补丁 -> 再部署”循环。
* 预扫描至少覆盖以下检查点（基线要求，可按项目继续扩展）：
  * 浏览器 API 直接访问：`window` / `document` / `localStorage` / `sessionStorage` 等仅浏览器环境能力。
  * Hook 初始化函数中的 SSR 同步执行逻辑：重点检查 `useState` / `useMemo` / `useReducer` 初始化阶段是否访问浏览器 API。
  * 非确定性表达式：如时间戳、随机数、随机 ID 等在构建期与 hydration 期可能不一致的值来源。
  * 动画初始隐藏态：尤其是 `framer-motion` 的 `initial` 隐藏配置，避免 SSG 首屏输出不可见内容。
* 以上仅为最低检查基线，不是完整清单；目标是一次性收敛 SSR 风险点，而非机械命中固定语法。
* 仅在清单收敛后再开始编码改造。

### 2) 依赖与脚本

* 确保依赖：
  * `react-router-dom@^6.30.3`
  * `vite-react-ssg@^0.9.0`（devDependency）
* 确保脚本：
  * `dev: vite-react-ssg dev`
  * `build: vite-react-ssg build`
  * `preview: vite preview`

### 3) 路由导出（`src/App.tsx`）

* 保留现有 JSX 路由树。
* 使用 `createRoutesFromElements(...)` 生成路由对象。
* 断言并导出 `RouteRecord[]`。
* 若存在动态路由（如 `:id`），必须给对应 route 挂 `getStaticPaths`，返回完整 URL 列表（必须带父路径）。
* 路径书写规范（React Router v6）：
  * 顶层端路由（挂在无 `path` 的 `RootLayout` 下；若存在 `// @side` 分组则需保持其结构稳定）必须使用绝对路径，如 `path="/website"`、`path="/admin"`、`path="/customer"`。
  * 子路由（嵌套在有 `path` 的父路由下）必须使用相对路径，如 `path="workspace"`、`path="settings"`、`path="customers/:customerId"`；由路由系统自动拼接父前缀。
  * 根路由重定向与通配路由与顶层路由同级，使用 `index` 或 `path="*"`，不写绝对斜杠形式。

### 4) SSG 入口（`src/main.tsx`）

* 使用 `ViteReactSSG` 创建根入口：
  * 传入 `routes`
  * `basename: import.meta.env.BASE_URL`
* 保持样式入口（如 `index.css`）正常导入。

### 5) Vite 配置（`vite.config.ts`）

* 使用 `@vitejs/plugin-react`。
* 若存在 `manualChunks`，在 `isSsrBuild` 为真时必须禁用 `output` 自定义分包，避免 SSR 构建错误。

## D. 客户端能力专项约束（重点）

### D1. Sonner 处理规则

1. `Sonner`（toaster）应保留在全局布局，保证全站可用。
2. 必须增加 `mounted` 保护，仅在客户端挂载后渲染 toaster。
3. 任何 `toast(...)` 调用必须位于事件回调或 `useEffect` 中，禁止模块顶层触发。

判定标准：构建与首屏 hydration 期间不应出现与 toast 相关的环境错误或不一致警告。

### D2. ThemeProvider（next-themes）处理规则

1. `ThemeProvider` 的挂载策略在 SSR 与客户端首帧必须一致，禁止通过 `mounted` 条件重构同一业务树。
2. 主题相关的分支渲染（`useTheme()` 驱动 DOM/class）必须在挂载后执行；未挂载前使用稳定 fallback。
3. `ThemeProvider` 配置固定为 SEO 迁移安全形态：移除 `enableSystem`，并显式设置 `defaultTheme` 与 `themes`。

正确写法（参考）：

`useMounted` 最小实现（参考）：

```tsx theme={null}
function useMounted() {
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  return mounted;
}
```

```tsx theme={null}
function RootLayout() {
  const mounted = useMounted();

  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="light"
      themes={["light", "dark"]}
      disableTransitionOnChange
    >
      <QueryClientProvider client={queryClient}>
        <TooltipProvider>
          {mounted && <Sonner />}
          <Outlet />
          <ScrollRestoration />
        </TooltipProvider>
      </QueryClientProvider>
    </ThemeProvider>
  );
}
```

判定标准：无明显主题闪烁和 hydration warning。

## E. 首页 SEO 可见性优化（SSG 迁移默认执行）

### E1. 适用范围（默认要求）

1. 该模块在进行 SSG 迁移时对首页为默认必做项，不可跳过。
2. 首页所有首屏/核心内容板块（标题、正文、关键指标、主要 CTA）必须在静态 HTML 中可见且可抓取。
3. 目标不是“去掉动画”，而是“SSG 阶段先可见，客户端接管后再播放”。

### E2. 核心原则

SSG 渲染时必须输出“完全可见的最终状态”；客户端挂载后再启用入场动画、视口触发和数据增强。

### E3. 判定决策树（必须按顺序排查）

1. 若组件使用 `framer-motion` 且存在 `initial` 隐藏态（如 `opacity: 0` / 位移）：
   * SSG 阶段使用 `initial={false}` 直出最终可见态；
   * 客户端挂载后再恢复入场动画配置。
2. 若组件使用 `useInView` 控制显示：
   * SSG 阶段默认视为“已进入视口”（可见）；
   * 客户端再按真实视口状态驱动动画。
3. 若数据组件存在 `if (data.length === 0) return null`：
   * 禁止直接返回空；
   * 必须保留语义化板块结构（如 section + heading）并输出占位/兜底内容，确保 HTML 结构完整。
4. 若数据请求依赖客户端环境：
   * SSG 阶段禁用请求或使用静态兜底值；
   * 客户端挂载后再请求并覆盖展示。
5. 若存在浏览器 API（`window`/`document`/`localStorage`/`matchMedia`）：
   * 仅允许在客户端挂载后访问，避免构建失败与 hydration 不一致。

### E4. 首页落地检查项（验收前必查）

1. 查看首页静态 HTML：核心板块文本与标题均存在且默认可见（非透明、非偏移隐藏）。
2. 无数据时首页仍保留完整板块语义结构，不出现整块缺失。
3. 客户端接管后动画正常、无显著 hydration warning。

### E5. 内部路由跳转 SEO 友好改造（首页重点）

1. 首页导航、卡片、Banner、CTA 等核心入口，必须输出可抓取的链接地址（`<a href>` 或 React Router `Link to` 最终渲染为 `href`）。
2. 禁止将“跳转能力”仅放在 `onClick + navigate(...)`、`window.location`、自定义事件中而无可解析 `href`。
3. 对于“整卡可点击”场景，优先使用语义化链接包裹或在卡片内部提供明确链接节点，避免仅绑定点击事件。
4. 链接目标必须对应可访问页面；动态路由目标需与 `getStaticPaths` 覆盖范围一致，避免落到无静态结果页面。
5. 仅用于登录态分流或权限校验的客户端重定向可保留，但不得替代首页核心内容的常规内部链接。

## F. 问题处理细则（编码过程中必须实时执行）

1. 重定向风险点：统一识别重定向页面中的 `Navigate` 用法，改为 `return null` + `useEffect` 客户端重定向。
2. 主题系统风险点（`next-themes`）：`ThemeProvider` 保持可扩展配置，启用 `mounted` 守卫，移除 `enableSystem`；单主题场景固定 `defaultTheme` 与 `themes`。
3. 浏览器 API 风险点：排查 `window` / `document` / `localStorage` / `sessionStorage` 在模块顶层、渲染路径、Hook 初始化函数（`useState` / `useMemo` / `useReducer`）中的访问；SSR 阶段同步执行路径必须迁移到 `useEffect` 或加 `typeof window !== "undefined"` 守卫。
4. 非确定性值风险点：排查时间戳、随机数、随机 ID 等值来源；若出现在 `useState` 默认值、组件顶层变量或 JSX 渲染逻辑中，改为稳定值或移至 `useEffect`，仅在事件回调中保留非确定性计算。
5. 客户端组件挂载风险点：`Sonner` / `Toast` 必须在 `mounted` 后渲染。
6. 重定向页面结构风险点：禁止使用 `Head` 组件。
7. 构建配置风险点：SSG 构建阶段必须禁用 `manualChunks`（通过 `isSsrBuild` 条件判断）。
8. 已知可接受限制 A：根路由重定向页面的 hydration warning 属于架构性限制，可接受；禁止围绕该点反复补丁。
9. 已知可接受限制 B：`framer-motion` 的 `motion.*` 可能产生少量 style 级 hydration 差异，可接受；优先保证“SSG 先可见、客户端再接管动画”，禁止围绕该限制反复补丁。
10. SEO 链接可抓取风险点：首页与核心板块中的内部跳转，禁止仅 `onClick + navigate(...)`，必须输出可解析 `href`。
11. 链接可达性风险点：校验内部链接目标可访问，动态路由需被 `getStaticPaths` 覆盖。

## G. 交付验收（必须全部通过）

1. `pnpm build` 成功。
2. `dist` 产出静态 HTML 页面（含主要路由及动态路由静态结果）。
3. 运行后无 `window/document is not defined` 报错。
4. 运行后无显著 hydration warning（重点检查主题和 Sonner；根路由重定向页的已知 warning 除外）。
5. `App.tsx` 仍为 JSX 路由源文件。
6. 首页静态 HTML 中核心板块默认可见且结构完整（无“透明但存在”或“整块缺失”）。
7. 首页静态 HTML 中可提取到核心内部链接（含主要导航/CTA），且目标路由与静态产物覆盖一致。
8. 目标为单次构建通过：若因遗漏浏览器 API 或非确定性表达式导致二次补丁，必须回溯并补齐“步骤 1 预扫描”清单。

## H. 失败处理优先级（按顺序排查）

1. 先检查 `react-router-dom` 版本是否误为 v7。
2. 再检查动态路由 `getStaticPaths` 是否挂在正确 route 且返回完整路径。
3. 再检查 `manualChunks` 是否在 SSR 构建中被禁用。
4. 最后检查是否遗漏客户端挂载保护（`Sonner` / `useTheme` 分支组件）。
5. 若首页可抓取链接不足，优先排查是否仍存在仅事件驱动的 CSR 跳转入口。
