You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js:将new Vue().$mount()改造为可导出结构适配服务端渲染

改造Vue应用为SSR模式的最佳实践

我来帮你梳理下把现有Vue应用改成SSR的合理方案,核心思路是服务端不能复用单例实例(否则会导致跨请求的状态污染),所以我们需要把创建Vue实例、路由、Vuex Store的逻辑都封装成工厂函数,具体步骤如下:

一、先改造路由和Vuex Store为工厂函数

首先得把原来的单例路由和Store改成可复用的工厂函数,避免服务端请求之间的状态串扰:

1.1 重构路由文件(router/index.js)

原来的代码可能是直接创建并导出Router实例,现在改成导出一个创建路由的函数:

import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(Router)

// 每次调用都返回全新的Router实例
export function createRouter() {
  return new Router({
    mode: 'history', // 服务端部署时需要配置对应的history模式支持(比如Nginx转发)
    routes: [
      { path: '/', name: 'Home', component: Home },
      // 其他路由配置...
    ]
  })
}

1.2 重构Vuex Store文件(vuex/index.js)

同理,把单例Store改成工厂函数:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 每次调用都返回全新的Store实例
export function createStore() {
  return new Vuex.Store({
    state: { /* 你的状态定义 */ },
    mutations: { /* 你的mutations */ },
    actions: { /* 你的actions */ },
    modules: { /* 你的模块 */ }
  })
}

二、重构main.js为可复用的createApp工厂函数

现在把原来直接创建Vue实例的代码,改成导出一个创建App的函数,每次调用都会生成全新的实例、路由和Store:

import Vue from 'vue';
import AppLayout from './theme/Layout.vue';
import { createRouter } from '../router';
import { createStore } from './vuex/index';

Vue.config.productionTip = false;

// 导出核心工厂函数,返回包含app、router、store的对象
export function createApp() {
  const router = createRouter();
  const store = createStore();

  // 用render函数渲染根组件,这是SSR推荐的写法(替代原来的...AppLayout)
  const app = new Vue({
    router,
    store,
    render: h => h(AppLayout)
  });

  return { app, router, store };
}

说明:原来的new Vue({ router, ...AppLayout, store })是把AppLayout的选项合并到Vue实例中,虽然客户端可行,但SSR中用render: h => h(AppLayout)更规范,因为.vue文件的默认导出是组件选项,通过h函数渲染更符合Vue的组件化逻辑。

三、编写客户端入口文件(entry-client.js)

把原来main.js中的挂载逻辑移到这个文件,专门处理客户端的初始化:

import { createApp } from './main';

const { app, router, store } = createApp();

// 如果服务端预取了状态,同步到客户端Store
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

// 等待路由就绪后再挂载,避免路由跳转异常
router.onReady(() => {
  app.$mount('#app');
});

四、编写服务端入口文件(server-entry.js)

这个文件是服务端渲染的入口,需要导出一个函数,接收请求上下文并返回Vue实例:

import { createApp } from './main';

// 服务端入口函数,接收请求上下文(包含url、req、res等信息)
export default function(context) {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp();

    // 设置当前请求的路由路径
    router.push(context.url);

    // 等待路由就绪,确保所有异步组件和路由钩子执行完成
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents();
      // 没有匹配到路由时返回404
      if (!matchedComponents.length) {
        return reject({ code: 404 });
      }

      // 调用匹配组件的asyncData方法(如果有的话),预取页面数据
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          });
        }
      })).then(() => {
        // 把预取的状态存入上下文,服务端渲染时会注入到HTML中
        context.state = store.state;
        resolve(app);
      }).catch(reject);
    }, reject);
  });
}

关键注意点

  • 为什么必须用工厂函数?:服务端是多用户并发环境,单例的Vue实例、路由或Store会导致不同请求之间的状态污染,所以必须为每个请求创建全新的实例。
  • 异步数据预取:如果你的页面需要异步数据,要在组件中定义asyncData方法(接收store和route参数),服务端会在渲染前调用这些方法预取数据,然后同步到客户端。

内容的提问来源于stack exchange,提问作者Boris K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:35:43