当搜索引擎的爬虫访问我们的站点时,如果只看到一句冷冰冰的 <div id="app"></div>
,SEO 基本就凉了。Vue SSR(Server-Side Rendering,服务端渲染)正是为了解决这个问题:让首屏 HTML 在服务器上生成,既能被爬虫读懂,又能让用户以最短的时间看到内容。
一、两份入口文件,一个共享的"根"
Vue 项目的传统 SPA 只有一个 main.js
,而 SSR 需要两个入口:
-
app.js
这是"纯粹"的 Vue 根实例工厂,既不挂载 DOM,也不关心运行在哪个环境。它返回一个干净的
new Vue()
,被客户端和服务端共同引用。 -
client-entry.js
拿到
app.js
返回的实例后,直接mount('#app')
,把静态标记激活成可交互的 SPA。 -
server-entry.js
在 Node 环境里执行,职责有三件:
- 调用
app.js
创建根实例; - 根据请求 URL 做路由匹配,找到需要渲染的组件;
- 执行组件暴露的
asyncData
或fetch
,把数据预取到 Vuex store。
- 调用
这样设计让"渲染"与"激活"解耦,同一份业务代码跑在两端。
二、Webpack 打出两份 Bundle
构建时,Webpack 会跑两次:
-
Client Bundle
打包所有客户端代码,输出到
dist/client
,浏览器下载后负责激活静态 HTML。 -
Server Bundle
打包所有服务端代码,输出到
dist/server
,Node 进程通过vue-server-renderer
读取这份 Bundle,生成首屏 HTML。
两份 Bundle 都包含业务组件,但前者带浏览器运行时,后者只保留渲染逻辑,体积更小。
三、服务器收到请求,一条流水线干活
当用户或爬虫发来 GET /article/42
:
- Node 进程加载
server-entry.js
,创建一个新的 Vue 实例。 - 路由匹配到
Article.vue
,触发asyncData
钩子,拉取文章详情并写入 store。 vue-server-renderer
把组件树渲染成字符串,插入到模板中的<!--vue-ssr-outlet-->
占位符里。- 为了让客户端"无缝续命",服务器把 store 状态序列化成一段脚本:
html
<script>window.__INITIAL_STATE__ = {...}</script>
- 最终拼好的 HTML 响应给浏览器,首屏直出完成。
四、浏览器"激活"静态标记
浏览器拿到 HTML 后,做了三件事:
-
解析 DOM 并立即渲染,用户瞬间看到文章标题与正文。
-
加载 Client Bundle,执行
client-entry.js
,创建同构的 Vue 实例。 -
通过
__INITIAL_STATE__
恢复 store 数据,再调用hydrate
而非mount
。hydrate
会对比服务端返回的 DOM 与客户端虚拟 DOM,复用已有节点、绑定事件,把"死"的 HTML 激活成"活"的 SPA。
这一步叫 客户端激活(Client Hydration),只有 DOM 结构与数据完全一致才能成功,否则 Vue 会整段替换,带来性能损耗。
五、交互回归 SPA 常态
激活完成后,所有路由跳转、数据更新都由浏览器接管,退化为普通单页应用。
SSR 只在首屏出场一次,之后不再参与。这样既享受了 SEO 与首屏性能,又保留了 SPA 的流畅体验。
六、总结
VUE SSR 的核心思想是让服务器先跑一次组件的渲染函数,把结果 HTML 交给浏览器,浏览器再用同一份代码激活它。