我如何在 Vite 多页面应用中添加预渲染而无需 SSR

发布: (2026年5月11日 GMT+8 08:20)
5 分钟阅读
原文: Dev.to

Source: Dev.to

Cover image for How I Added Pre-Rendering to a Vite Multi-Page App Without SSR

我运营 RelahConvert,这是一个拥有 50 多种工具、覆盖 25 种语言的在线文件转换站点。上周,我在 Ahrefs 审核中发现了一个问题:我的 1,449 个页面全部被标记为 “缺少 H1”“字数过少”

这些页面并不是空的。它们都有 H1、描述、FAQ——在浏览时都能正常渲染。但爬虫却看到的是一个空的 body。

设置

该站点是一个 Vite MPA(多页面应用),而不是 SPA。每个工具都有自己的 .html 文件。特定工具的 JavaScript 在运行时通过以下方式注入页面内容:

document.querySelector('#app').innerHTML = template;

这对用户来说效果很好:页面加载后,JS 执行,内容出现。然而,那些不执行 JavaScript 的爬虫(如 Ahrefs、基础机器人、以及 Facebook、X 等社交媒体抓取器)只能看到一个空的 <body>。即使是会运行 JS 的 Google,也会因为需要进行延迟的二次渲染而消耗爬取预算,导致新页面的索引速度变慢。

常规解决方案不适用

解决方案未能奏效的原因
服务器端渲染 (SSR)需要运行时后端;我使用的是 Cloudflare Pages(静态托管)。
静态站点生成 (SSG)需要围绕 Astro 或 Vike 等框架进行完整重构——对已上线站点来说是大幅度改动。
使用 Puppeteer 预渲染为每个路由启动无头浏览器会让构建时间增加约 3‑5 分钟,并引入大量依赖。

我想要更轻量的方案:仅在构建时将 SEO 相关内容(H1、描述、FAQ)注入静态 HTML,而无需在 Node 中重新实现整个 UI。

Source:

方法:通过 Vite 插件实现混合预渲染

我扩展了现有的构建插件,使其读取我的 i18n 字典并将 SEO 内容直接写入每个 HTML 文件。交互式工具 UI(拖拽、画布操作、转换逻辑)仍然由 JS 渲染——只有对爬虫重要的内容会被预先写入。

插件的大致结构

function preRenderPlugin() {
  return {
    name: 'pre-render-seo',
    apply: 'build',
    closeBundle() {
      const tools = ['compress', 'resize', 'merge-pdf', /* … */];
      const langs = ['en', 'fr', 'es', 'de', 'ar', /* … */];

      for (const tool of tools) {
        for (const lang of langs) {
          const i18n = loadI18n(lang);
          const seo = i18n.seo[tool];

          const html = readHTML(`dist/${lang}/${slugFor(tool, lang)}/index.html`);
          const injected = injectSEOContent(html, {
            h1: i18n.nav_short[tool],
            description: i18n.heroDesc,
            body: seo.body,
            faqs: seo.faqs,
          });

          // The content gets injected into the placeholder.
          // When JS runs at runtime, it replaces the contents with the interactive UI —
          // but the pre‑rendered version is what crawlers see.
          writeHTML(path, injected);
        }
      }
    },
  };
}

三个陷阱

  1. Per‑language metadata – My tool pages had hard‑coded English <title> and <meta> tags, even on French URLs. Pre‑rendering exposed the mismatch, so I extended the plugin to resolve title and meta description per language from i18n at build time.

  2. FOUC on per‑language URLs – Initially I derived per‑language URLs from the homepage template. JS at runtime detected the route, wiped the body, and injected the tool, causing a brief flash of French content before it disappeared. The fix was to use each tool’s own template as the base for its language URLs.

  3. Cloudflare bot protection blocking social scrapers – After fixing OG tags, Facebook’s Sharing Debugger returned 403. Cloudflare’s Browser Integrity Check was challenging the Facebook scraper. The solution was a simple Cloudflare dashboard configuration, not a code change.

结果

  • 构建时间从 22.7 秒 提升到 ~23 秒 —— 开销可以忽略不计。
  • 现在每个工具页面在所有 25 种语言的静态 HTML 中都包含了正确的 H1、描述、常见问题解答以及内部链接。
  • 社交预览已正常工作。
  • Ahrefs 和 Google 能在首次爬取时读取内容。

您可以在 image compressor 上查看实时效果——页面的查看源代码显示了预渲染的内容。

要点

如果你在没有框架的 Vite 上工作,并且需要对爬虫友好的 HTML 而不想完全使用 SSG,那么在构建时注入 SEO 内容到现有结构的插件是一个出乎意料的简洁折中方案。

0 浏览
Back to Blog

相关文章

阅读更多 »