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

我运营 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);
}
}
},
};
}
三个陷阱
-
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. -
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.
-
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 内容到现有结构的插件是一个出乎意料的简洁折中方案。