Server-Side Rendering (SSR) в Vue.js позволяет рендерить компоненты на сервере и отправлять готовый HTML на клиент. Это улучшает производительность, SEO и снижает время до первого отображения страницы (Time to First Paint). В отличие от классического SPA, где весь рендер происходит на клиенте, SSR позволяет генерировать полный HTML на сервере, а затем гидрировать его на клиенте для интерактивности.
Ключевые преимущества SSR:
Проект для SSR обычно разделяется на несколько частей:
Серверная часть (server) Обрабатывает запросы, рендерит компоненты Vue в HTML и отправляет их клиенту.
Клиентская часть (client) Гидрирует серверный HTML и управляет интерактивностью.
Общие компоненты (shared) Компоненты и маршруты, используемые как на сервере, так и на клиенте.
Пример структуры:
project/
│─ server/
│ └─ index.js
│─ client/
│ └─ main.js
│─ shared/
│ └─ App.vue
│ └─ components/
│ └─ Header.vue
│ └─ Footer.vue
│─ package.json
SSR требует двух сборок: серверной и клиентской. Для этого используют webpack или Vite с разными конфигурациями.
Пример конфигурации с Vite:
// vite.config.js
import vue from '@vitejs/plugin-vue';
export default {
plugins: [vue()],
build: {
ssr: process.env.SSR === 'true' ? 'server/main.js' : undefined,
outDir: process.env.SSR === 'true' ? 'dist-server' : 'dist-client'
}
};
Для SSR Vue предоставляет функцию renderToString из
пакета @vue/server-renderer. Серверный код выглядит
так:
import express from 'express';
import { createSSRApp } from 'vue';
import { renderToString } from '@vue/server-renderer';
import App from '../shared/App.vue';
const server = express();
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const html = await renderToString(app);
res.send(`
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Vue SSR</title>
</head>
<body>
<div id="app">${html}</div>
<script type="module" src="/client/main.js"></script>
</body>
</html>
`);
});
server.listen(3000);
Клиентская часть подключает серверный HTML и делает его интерактивным
с помощью createApp и метода mount.
import { createApp } from 'vue';
import App from '../shared/App.vue';
createApp(App).mount('#app');
Гидрация гарантирует, что события и состояние компонентов работают корректно после того, как HTML был сгенерирован на сервере.
Для SSR важно загружать данные до рендера, чтобы HTML содержал
актуальную информацию. Обычно создают функцию asyncData или
используют Vuex.
Пример с asyncData:
export default {
data() {
return { items: [] };
},
async asyncData() {
const response = await fetch('https://api.example.com/items');
return { items: await response.json() };
},
created() {
if (this.$ssrContext) {
Object.assign(this.$data, this.$ssrContext.asyncData);
}
}
};
На сервере вызывают asyncData перед рендером:
const app = createSSRApp(App);
app.$ssrContext = { asyncData: await App.asyncData() };
const html = await renderToString(app);
SSR обычно интегрируется с vue-router. Необходимо
синхронизировать маршрут сервера и клиента:
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from './pages/Home.vue';
export function createAppRouter(ssr = false) {
return createRouter({
history: ssr ? createMemoryHistory() : createWebHistory(),
routes: [{ path: '/', component: Home }]
});
}
На сервере используется createMemoryHistory, на клиенте
— createWebHistory.
SSR требует загрузки данных для конкретного маршрута на сервере.
Обычно создается функция fetchData для компонентов:
const matchedComponents = router.getMatchedComponents(req.url);
await Promise.all(matchedComponents.map(c => c.fetchData?.()));
Это гарантирует, что все данные будут готовы до рендера.
Для состояния приложения на сервере используют отдельный экземпляр Vuex на каждый запрос:
import { createStore } from 'vuex';
export function createAppStore() {
return createStore({
state: () => ({ items: [] }),
mutations: { setItems(state, items) { state.items = items; } },
actions: { async fetchItems({ commit }) {
const data = await fetch('https://api.example.com/items').then(r => r.json());
commit('setItems', data);
}}
});
}
На сервере:
const store = createAppStore();
await store.dispatch('fetchItems');
const app = createSSRApp(App);
app.use(store);
На клиенте состояние гидрируется из
window.__INITIAL_STATE__:
const store = createAppStore();
store.replaceState(window.__INITIAL_STATE__);
createApp(App).use(store).mount('#app');
SSR в Vue.js требует внимательного подхода к состоянию и маршрутизации, но обеспечивает значительное ускорение первых рендеров и улучшение SEO. При правильной архитектуре можно создавать масштабируемые и быстрые веб-приложения, объединяя преимущества серверного и клиентского рендера.