Implementing Server-Side Rendering (SSR) with Vue.js
This article explains how to implement server‑side rendering (SSR) for Vue.js applications, covering the underlying concepts, required code setup, Vuex state handling, webpack configuration, and deployment steps to improve SEO for single‑page applications.
Background: developers often avoid Vue for SEO concerns, but Vue 2.0 introduces server‑side rendering (SSR) to make SPA pages crawlable.
SSR renders the initial HTML on the server, sends a fully populated page to the client, and then hydrates the Vue app on the browser.
Implementation steps:
Create a Vue instance without mounting it to the DOM.
Configure vue‑router with history mode and a base path.
Write view components (e.g., <template> <div class="content"> <course-cover :class-data="classData[0]"></course-cover> <article-items :article-items="articleItems"></article-items> </div> </template> <script> import courseCover from '../components/courseCover.vue'; import articleItems from '../components/articleItems'; export default { computed { classData() { return this.$store.state.courseListItems; }, articleItems() { return this.$store.state.articleItems; } }, components: { courseCover, articleItems }, // 服务端获取数据 fetchServerData ({ state, dispatch, commit }) { let alias = state.route.params.alias; return Promise.all([ dispatch('FETCH_ZT', { alias }), dispatch('FETCH_COURSE_ITEMS'), dispatch('FETCH_ARTICLE_ITEMS') ]) }, // 客户端获取数据 beforeMount() { return this.$store.dispatch('FETCH_COURSE_ITEMS'); } } </script> that expose a fetchServerData method for server‑side data pre‑fetching.
Use Vuex for state management, defining state, actions, and mutations.
Create server-entry.js to render the app to a string and attach the initial Vuex state to the context.
Create client-entry.js to replace the state with window.__INITIAL_STATE__ and mount the app.
Configure separate webpack builds for server and client bundles.
Run the builds with npm scripts and use a controller to render the HTML with vue-server-renderer .
Key code examples:
// app.js
import Vue from 'vue';
import router from './router';
import store from './store';
import App from './components/app';
let app = new Vue({
template: '<app></app>',
base: '/c/',
components: {
App
},
router,
store
});
export {
app,
router,
store
} import Vue from 'vue';
import VueRouter from 'vue-router';
import IndexView from '../views/indexView';
import ArticleItems from '../views/articleItems';
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
base: '/c/',
routes: [
{
path: '/:alias',
component: IndexView
}, {
path: '/:alias/list',
component: ArticleItems
}
]
}); // store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
let apiHost = 'http://localhost:3000';
const store = new Vuex.Store({
state: {
alias: '',
ztData: {},
courseListItems: [],
articleItems: []
},
actions: {
FETCH_ZT: ({ commit, dispatch, state }, { alias }) = {
commit('SET_ALIAS', { alias });
return axios.get(`${apiHost}/api/zt`)
.then(response => {
let data = response.data || {};
commit('SET_ZT_DATA', data);
})
},
FETCH_COURSE_ITEMS: ({ commit, dispatch, state }) => {
return axios.get(`${apiHost}/api/course_items`).then(response => {
let data = response.data;
commit('SET_COURSE_ITEMS', data);
});
},
FETCH_ARTICLE_ITEMS: ({ commit, dispatch, state }) => {
return axios.get(`${apiHost}/api/article_items`)
.then(response => {
let data = response.data;
commit('SET_ARTICLE_ITEMS', data);
})
}
},
mutations: {
SET_COURSE_ITEMS: (state, data) => {
state.courseListItems = data;
},
SET_ALIAS: (state, { alias }) => {
state.alias = alias;
},
SET_ZT_DATA: (state, { ztData }) => {
state.ztData = ztData;
},
SET_ARTICLE_ITEMS: (state, items) => {
state.articleItems = items;
}
}
})
export default store; // server-entry.js
// server-entry.js
import {app, router, store} from './app';
export default context => {
const s = Date.now();
router.push(context.url);
const matchedComponents = router.getMatchedComponents();
if(!matchedComponents) {
return Promise.reject({ code: '404' });
}
return Promise.all(
matchedComponents.map(component => {
if(component.fetchServerData) {
return component.fetchServerData(store);
}
})
).then(() => {
context.initialState = store.state;
return app;
})
} // webpack.server.config.js
// webpack.server.config.js
const base = require('./webpack.base.config'); // webpack 的通用配置
module.exports = Object.assign({}, base, {
target: 'node',
entry: './src/server-entry.js',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
externals: Object.keys(require('../package.json').dependencies),
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.VUE_ENV': '"server"'
})
]
}) "packclient": "webpack --config webpack.client.config.js",
"packserver": "webpack --config webpack.server.config.js" // client-entry.js
import { app, store } from './app';
import './main.scss';
store.replaceState(window.__INITIAL_STATE__);
app.$mount('#app'); // controller.js
const serialize = require('serialize-javascript');
// 因为我们在vue-router 的配置里面使用了 `base: '/c'`,这里需要去掉请求path中的 '/c'
let url = this.url.replace(/\/c/, '');
let context = { url: this.url };
// 创建渲染器
let bundleRenderer = createRenderer(fs.readFileSync(resolve('./dist/server-bundle.js'), 'utf-8'))
let html = yield new Promise((resolve, reject) => {
// 将vue实例编译成一个字符串
bundleRenderer.renderToString(
context, // 传递context 给 server-bundle.js 使用
(err, html) => {
if(err) {
console.error('server render error', err);
resolve('');
}
/**
* 还记得在 server-entry.js 里面 `context.initialState = store.state` 这行代码么?
* 这里就直接把数据发送到浏览器端啦
**/
html += `
`;
resolve(html);
}
)
})
yield this.render('ssr', html);
// 创建渲染器函数
function createRenderer(code) {
return require('vue-server-renderer').createBundleRenderer(code);
} // ssr.html
{% extends 'layout.html' %}
{% block body %}
{{ html | safe }}
{% endblock %}The demo demonstrates Vue + vue‑router + vuex usage, server‑side data fetching, and client‑side hydration, while noting that features like streaming rendering and component caching are not covered.
Choosing Vue SSR provides a viable option for improving SEO without abandoning the Vue ecosystem.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.