Vue3 + TS + Vite 单页应用 SEO 改造最佳实践
在现代前端开发中,Vue3 + TypeScript + Vite 已经成为构建单页应用 (SPA) 的主流技术栈。然而,SPA 原生存在 SEO(搜索引擎优化)短板,因为其内容主要通过 JavaScript 动态生成,爬虫(尤其是国内的百度爬虫)可能无法正确抓取和索引页面内容。
本文将深入探讨针对 Vue3 SPA 项目的 SEO 改造最佳实践,重点介绍低成本、高收益的预渲染 (Prerendering) 方案。
🔍 SPA 的 SEO 痛点
- 内容抓取困难: 爬虫抓取到的 HTML 通常只有一个空的
<div id="app"></div>,内容需要 JS 执行后才渲染。
- 首屏加载慢: 需要下载并解析 JS 后才能显示内容,影响用户体验和搜索引擎评分 (Core Web Vitals)。
- Meta 信息缺失: 每个页面的 Title、Description 通常是固定的,无法根据路由动态变化。
🛠️ 解决方案对比
| 方案 |
描述 |
优点 |
缺点 |
适用场景 |
| SSR (服务端渲染) |
使用 Nuxt.js 或 Vite SSR,在服务端渲染 HTML。 |
SEO 效果最好,首屏极快。 |
开发/部署成本高,需要 Node.js 服务器支持。 |
大型内容型网站,对 SEO 要求极高。 |
| SSG (静态站点生成) |
构建时生成每个路由的静态 HTML。 |
纯静态文件,部署简单 (Nginx/CDN),SEO 好。 |
构建时间随页面数量增加,不适合频繁变动的数据。 |
博客、文档、营销页。 |
| 预渲染 (Prerendering) |
构建后通过无头浏览器 (Puppeteer) 跑一遍页面,保存为 HTML。 |
侵入性小,无需改造代码架构,部署简单。 |
仅适用于静态路由,动态路由配置复杂。 |
中小型 SPA 项目 SEO 改造首选。 |
对于大多数已经开发完成的 SPA 项目,预渲染是性价比最高的改造方案。
🚀 实战方案一:基于 vite-plugin-prerender 的预渲染改造
⚠️ 注意:该插件依赖 Puppeteer (无头 Chrome),安装包体积较大且可能受网络环境影响导致安装失败。构建过程需要启动浏览器,速度相对较慢。适合对构建环境有完全控制权,且代码中包含大量依赖浏览器 API (如 window, document) 的场景。
我们将使用 vite-plugin-prerender 插件来实现预渲染。该插件基于 Puppeteer,在构建阶段启动一个无头浏览器,访问指定路由并将渲染结果保存为静态 HTML 文件。
1. 安装依赖
1 2 3 4 5 6
| npm install vite-plugin-prerender puppeteer -D
pnpm add vite-plugin-prerender puppeteer -D
"vite-plugin-prerender": "^1.0.8", "puppeteer": "^19.7.0",
|
2. 配置 Vite (vite.config.ts)
在 vite.config.ts 中引入并配置插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import path from "path"; import vitePrerender from "vite-plugin-prerender";
export default defineConfig({ plugins: [ vue(), vitePrerender({ staticDir: path.join(__dirname, "dist"), routes: ["/about"], renderer: new vitePrerender.PuppeteerRenderer({ maxConcurrentRoutes: 1, renderAfterTime: 5000, }), postProcess(renderedRoute) { renderedRoute.html = renderedRoute.html .replace(/http:\/\/localhost:\d+/g, "") .replace(/class="loader"/g, 'class="loader" style="display:none"'); return renderedRoute; }, }), ], });
|
3. 改造入口文件 (main.ts)
为了确保页面完全渲染后再进行抓取,我们需要在 Vue 挂载完成后触发一个事件。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createApp } from "vue"; import App from "./App.vue"; import router from "./router";
const app = createApp(App);
app.use(router);
router.isReady().then(() => { app.mount("#app"); document.dispatchEvent(new Event("render-event")); });
|
SEO 的核心之一是每个页面要有独立的 Title 和 Description。我们可以使用 @vueuse/head (或 unhead) 来管理。
安装:
1
| npm install @vueuse/head
|
使用 (main.ts):
1 2 3
| import { createHead } from "@vueuse/head"; const head = createHead(); app.use(head);
|
// 在页面组件中 (Home.vue):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <script setup lang="ts"> import { useHead } from "@vueuse/head";
useHead({ title: "首页 - 我的 Vue3 应用", meta: [ { name: "description", content: "这是 Vue3 + Vite + TS 应用的首页,包含最新的技术分享。", }, { name: "keywords", content: "Vue3, Vite, TypeScript, SEO" }, // Open Graph 社交分享优化 { property: "og:title", content: "首页 - 我的 Vue3 应用" }, { property: "og:description", content: "这是 Vue3 + Vite + TS 应用的首页,包含最新的技术分享。", }, { property: "og:image", content: "https://your-domain.com/share.png" }, ], link: [ // 规范化 URL,防止重复内容 { rel: "canonical", href: "https://your-domain.com/" }, ], }); </script>
### 5. 构建与验证 运行构建命令: ```bash npm run build
|
构建完成后,查看 dist 目录,你会发现除了 index.html,还生成了 about/index.html, contact/index.html 等文件夹。
打开这些 HTML 文件,你会看到 <div id="app"> 内部已经有了完整的 DOM 结构和内容,而不仅仅是空的占位符。这就是预渲染的成果!
🚀 实战方案二:基于 vite-ssg 的 SSG 改造
✅ 推荐:相比于方案一,vite-ssg 基于 Node.js 环境直接渲染,无需启动无头浏览器,构建速度更快,依赖更轻量,是 Vue3 生态中更现代的静态化方案。
如果你希望获得更彻底的静态化体验,或者你的应用主要由静态内容组成(如博客、文档),vite-ssg 是一个非常优秀的 SSG 方案。它在构建时将 Vue 应用渲染为静态 HTML,同时保留了客户端的水合(Hydration)能力。
1. 安装依赖
1 2 3 4 5
| npm install vite-ssg -D
npm install vue-router
npm install @vueuse/head
|
2. 改造入口文件 (main.ts)
vite-ssg 需要导出一个 createApp 函数,而不是直接挂载应用。注意:这与方案一的入口文件改造互斥,请根据选择的方案进行调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { ViteSSG } from "vite-ssg"; import App from "./App.vue"; import routes from "./router/routes"; import "./style.css";
export const createApp = ViteSSG( App, { routes }, ({ app, router, routes, isClient, initialState }) => { } );
|
3. 配置 package.json
修改构建脚本以使用 vite-ssg 进行构建。
1 2 3 4 5 6 7
| { "scripts": { "dev": "vite", "build": "vite-ssg build", "preview": "vite preview" } }
|
4. 局部页面静态化与配置
vite-ssg 默认会抓取所有路由。如果你只想针对特定页面(如首页 / 和关于页 /about)进行 SEO 优化,或者需要处理动态路由,可以在 vite.config.ts 中配置 ssgOptions。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue";
export default defineConfig({ plugins: [vue()], ssgOptions: { script: "async", formatting: "minify", includedRoutes(paths, routes) { return ["/", "/about"]; }, onFinished() { console.log("SSG 构建完成"); }, }, });
|
🌟 进阶优化
1. 生成 Sitemap
使用 vite-plugin-sitemap 自动生成站点地图,方便搜索引擎收录。
1
| npm install vite-plugin-sitemap -D
|
1 2 3 4 5 6 7 8 9 10 11 12
| import Sitemap from "vite-plugin-sitemap";
export default defineConfig({ plugins: [ Sitemap({ hostname: "https://your-domain.com", dynamicRoutes: ["/", "/about", "/contact"], }), ], });
|
2. Nginx 配置
预渲染生成的目录结构通常是 路由/index.html。Nginx 需要配置 try_files 以正确服务这些文件。同时建议开启 Gzip/Brotli 压缩以提升加载速度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html;
gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
location / { try_files $uri $uri/index.html /index.html; } }
|
📝 总结
对于 Vue3 + Vite 的单页应用,预渲染 (Prerendering) 和 SSG (静态站点生成) 是实现 SEO 最快、成本最低的路径。
- 方案一 (vite-plugin-prerender): 适合老旧项目改造,或者强依赖浏览器环境的场景。但构建较慢,依赖 Puppeteer。
- 方案二 (vite-ssg): 推荐首选。适合新项目或博客/文档类应用。构建快,依赖少,体验更接近原生 SSR。
- 适用范围: 官网、营销页、文档站等静态内容较多的场景。
- 局限性: 对于海量动态数据(如电商详情页),建议考虑 Nuxt.js SSR 方案。
通过本文的实践,你可以轻松让你的 Vue3 SPA 项目被搜索引擎友好收录,获取更多自然流量。