本文记录 Vue 项目基于 vite-ssg 实现静态预渲染(SSG)的落地经验,适合官网类站点,后续同类官网可直接参考这套实现
一句话先看懂:本文方案基于 vite-ssg 做Vite预渲染,不是Nuxt那种运行时SSR。开发时仍是普通SPA单页应用,仅在打包构建阶段,把指定路由提前生成独立HTML,方便搜索引擎抓取页面标题、描述、正文和meta信息。
1. 构建产物是什么样?
打包后的dist目录是嵌套式HTML结构,Nginx配合目录默认首页规则,浏览器地址栏不会带.html后缀。
| 路由**** | 构建产物**** | 访问地址**** |
| / | dist/index.html | 线上站点根路径 |
| /about | dist/about/index.html | /about |
| /blog/post-detail | dist/blog/post-detail/index.html | /blog/post-detail |
💡原理:打包的时候就提前生成好about/index.html这份完整HTML。 把dist丢服务器作为静态目录,用户访问/about时,Nginx查找逻辑:
- 先找名为about的文件 → 不存在
- 再找about文件夹 → 存在
- 读取文件夹内默认首页index.html返回给浏览器
Nginx核心配置:
location / {
try_files uriuri/ /index.html;
}
- $uri:匹配对应静态文件(js、图片等资源)
- $uri/:匹配目录,返回目录下index.html(SSG页面就是走这条)
- /index.html兜底:上面都匹配不到,回退SPA首页,用于没有预渲染的路由
用户地址栏看不到index.html,这只是服务器内部文件命名。
2. 需要配置哪些内容?
A. 依赖 & package.json脚本
用到两个核心包
- vite-ssg@^28.3.0:静态预渲染构建
- @unhead/vue:Vue内维护title、meta、canonical等head标签,预渲染时直接注入HTML
canonical 读音 /kəˈnɒnɪkəl/,SEO里叫规范URL,用来指定页面权威地址。
"scripts": {
"dev": "vite",
"build": "vite-ssg build",
"build:spa": "vite build",
"build:test": "vite-ssg build --mode development",
"build:prod": "vite-ssg build --mode production"
},
- 本地开发npm run dev:还是普通SPA,不走SSG
- 正式发布必须用vite-ssg build,不能直接vite build
- build:spa作为兜底方案:SSG打包出问题时,可以临时切回普通SPA发布
- npm run build等价build:prod,默认生产环境;build:test对应测试环境
B. 入口 main.js 修改
普通SPA写法:直接createApp然后mount挂载
const app = createApp(App);
app.use(getRouter());
app.mount('#app');
SSG改造:导出app工厂函数,让vite-ssg在Node环境逐个渲染页面
export const createApp = ViteSSG(
App,
{
routes,
scrollBehavior() {
return { top: 0 };
}
}
);
👉 scrollBehavior是VueRouter配置,不是SSG必需配置 作用:站内导航客户端跳转时页面自动滚回顶部;不加的话,切换路由会保留上次滚动位置,体验不好。 页面直接刷新/预渲染页面打开,浏览器本身就从顶部加载,这个配置只作用于站内点击导航。
C. 路由改造
src/router/index.js,必须单独导出routes数组,不要只导出router实例 vite-ssg读取的是路由表,不是运行时router。
- 需要SEO收录的路由,添加meta.seoKey,供usePageSeo使用
- 动态路由/blog/:id可以保留,但预渲染列表里,要展开成一个个真实path
D. vite.config.js 中的ssgOptions(核心配置)
ssgOptions: {
script: 'async',
formatting: 'minify',
dirStyle: 'nested',
includedRoutes() {
return prerenderRoutes; // 需要预渲染的全部路由
},
onFinished() {
writeSeoFiles(path.resolve(__dirname, 'dist'), siteUrl, mode === 'production');
}
},
| 配置项**** | 说明**** | 项目效果**** |
| script: 'async' | html内script模块增加async | 优先展示预渲染页面内容,JS延迟水合,首屏更快 |
| formatting: 'minify' | 压缩HTML代码 | 文件体积更小,dist里html为单行代码 |
| dirStyle: 'nested' | 嵌套目录输出 | /about生成about/index.html,适配Nginx静态托管;默认flat会输出about.html |
| includedRoutes | 预渲染白名单 | 只有写在这里的路径,才生成HTML;动态blog路由需要展开所有id,否则会被丢弃 |
| onFinished | 全部页面渲染完成后的钩子 | 自动生成robots.txt、sitemap.xml,复制处理隐私协议页面 |
关键:includedRoutes + dirStyle 才决定会不会生成独立页面HTML;script、formatting只影响性能与文件格式。
vite-ssg参考资源:
- Github仓库:github.com/antfu-colle...
- npm包:www.npmjs.com/package/vit...
其他可选参数简要说明:
- entry:自定义入口文件路径
- mock:预渲染模拟window/document,尽量少用,优先使用window判断守卫
- concurrency:并发预渲染页面数量,页面量大可调整
- beastiesOptions:内联关键CSS,false代表关闭
✅ 项目规则: 新增页面流程:添加路由 → 加入prerenderRoutes数组 → 需要收录就加到sitemapPaths 隐私页面特殊处理:路由存在,但不加入预渲染名单;onFinished钩子单独拷贝文件。 隐私页、注销账号页面即使预渲染,也不加入sitemap,robots里禁止爬虫抓取。
E. SEO三件套,页面被搜索引擎抓取的关键
- src/const/seo.js:维护每页title、description、keywords、robots规则
- src/composables/usePageSeo.js:封装@unhead/vue,把seo信息注入页面head
- App.vue 全局调用usePageSeo()
环境区分:
- build:prod生产环境:普通页面index,follow;隐私、注销页面noindex
- build:test测试环境:全站noindex,nofollow,防止测试站被搜索引擎收录
📖 noindex / nofollow 通俗解释: 这是给爬虫的指令,不影响用户正常访问页面****
- noindex:不要把这个页面放进搜索结果
- nofollow:不要抓取页面里的链接
常见组合:
- index,follow:正常公开页面(首页、about、博客)
- noindex,nofollow:隐私协议、注销页,禁止收录和爬取内链
区分:meta robots标签是爬虫抓到页面后生效;robots.txt是告诉爬虫尽量不要访问这个路径,两者配合使用。
F. 浏览器API兼容(踩坑重点!)
预渲染是跑在Node环境,不存在window、document,访问就会打包失败。 项目防护方案,新项目直接抄:
- src/utils/adapt.js:增加typeof window === 'undefined'判断,Node环境直接return
- src/utils/gsap.js:仅window存在时才注册ScrollTrigger
- 动画相关逻辑全部放到onMounted内部,不在setup顶层执行
简单一句话:Node预渲染阶段避开浏览器API;要使用window/document,先判断环境,或者放到onMounted里。
页面静态正文数据(博客内容),要打包时就拿到,不要在setup顶层发起接口请求。接口请求放到客户端执行。
3. OG社交分享卡片配置(Facebook/Zalo等平台)
用来控制分享链接时展示的卡片信息
| 标签**** | 说明**** |
| og:title | 卡片标题,复用页面title |
| og:description | 卡片摘要描述,复用页面description |
| og:url | 页面完整绝对地址 |
| og:type | 普通页面website,博客详情article |
| og:image | 分享大图,默认图og-default.png |
| og:image:type | 图片类型,png |
| og:image:width/height | 1200 × 630 |
| og:site_name | 站点名称 |
| og:locale | 语言区域vi_VN |
| og:image:alt | 图片无障碍描述,可选 |
⚠️注意:Facebook会缓存分享卡片。修改图片/文案后,需要到调试工具重新抓取刷新: developers.facebook.com/tools/debug...
图片规范需求:
- 尺寸 1200 × 630(比例1.91:1,Facebook/Zalo/Telegram通用)
- 格式优先JPG,体积尽量<300KB,不超过1MB
- 安全边距:四周预留80~100px,重要文字logo不要贴边,防止平台裁切
- 文字:越南语,字号保证手机预览清晰
- 内容:品牌Logo + 一句slogan;先做全站默认图,博客详情封面后续按需补充
4. 域名规范(重要)
正式域名:xxx.com 隐私协议正文里写www.xxx.com,需要运维配置:www.xxx.com 301重定向到裸域。
canonical规范URL只能保留唯一域名,避免重复收录问题。
总结
SSG静态站点生成,就是打包构建阶段提前把路由渲染成完整HTML。用户打开页面就能直接看到完整内容和SEO标签,不用等待浏览器加载Vue后再渲染。后续同类官网项目做SSG,可参考这套实现。