Как происходит SSR (server-side rendering) во Vue?

Server-Side Rendering (SSR) во Vue — это процесс генерации HTML на сервере, а не в браузере клиента. SSR используется для ускорения первой отрисовки, улучшения SEO и повышения производительности на слабых устройствах. В Vue 3 серверный рендеринг реализуется через официальную библиотеку @vue/server-renderer и часто интегрируется с такими фреймворками, как Nuxt.

1. Принцип SSR во Vue

SSR работает следующим образом:

  1. Сервер получает HTTP-запрос.

  2. Создаётся экземпляр приложения Vue на основе маршрута и состояния.

  3. Этот экземпляр рендерится в HTML-строку с помощью renderToString().

  4. Сервер отправляет сгенерированный HTML клиенту.

  5. Клиент загружает JavaScript-бандл и "гидратирует" уже отрендеренный HTML, делая его интерактивным.

2. Установка и зависимости

Для ручной настройки SSR во Vue 3 нужно установить:

npm install vue @vue/server-renderer express

3. Пример базовой SSR-реализации на Vue 3

Структура проекта:

/src
├── app.js # создаёт Vue-приложение
├── entry-client.js
└── entry-server.js
server.js # express-сервер

/src/app.js

import { createSSRApp } from 'vue';
import App from './App.vue';
export function createApp() {
const app = createSSRApp(App);
return app;
}

/entry-server.js

import { renderToString } from '@vue/server-renderer';
import { createApp } from './src/app';
export async function render() {
const app = createApp();
const appContent = await renderToString(app);
return \`
<!DOCTYPE html>
<html>
<head><title>SSR</title></head>
<body>
<div id="app">${appContent}</div>
<script type="module" src="/entry-client.js"></script>
</body>
</html>
\`;
}

/entry-client.js

import { createApp } from './src/app';
createApp().mount('#app');

server.js

import express from 'express';
import { render } from './entry-server.js';
const app = express();
app.use(express.static('.'));
app.get('\*', async (req, res) => {
const html = await render();
res.send(html);
});
app.listen(3000, () => {
console.log('SSR server running at http://localhost:3000');
});

4. Гидратация

После того как сервер отрендерил HTML, Vue на клиенте должен повторно "привязать" Virtual DOM к уже существующему HTML. Этот процесс называется гидратацией и выполняется автоматически при вызове app.mount('#app'), если элемент уже содержит серверный HTML.

Во время гидратации Vue не перерисовывает DOM, а восстанавливает внутренние структуры и обработчики событий.

5. Обработка маршрутов

SSR-приложения часто используют маршрутизацию. В Vue 3 для этого применяют vue-router@4. На сервере необходимо создать новый экземпляр маршрутизатора для каждого запроса, установить маршрут и дождаться завершения всех асинхронных задач (например, получения данных).

router.push(req.url);
await router.isReady();

6. Асинхронные данные и SSR

Во Vue SSR нельзя использовать onMounted() для загрузки данных, так как этот хук не вызывается на сервере. Вместо него используют async setup() или кастомную логику с await внутри setup().

Пример:

<script setup>
import { ref } from 'vue';
const data = ref(null);
const res = await fetch('https://api.example.com/data');
data.value = await res.json();
</script>

7. Передача состояния с сервера на клиент

Чтобы избежать повторного запроса данных на клиенте, нужно сериализовать состояние на сервере и внедрить его в HTML, а затем использовать на клиенте при создании приложения.

На сервере:

const state = { user: 'Vue SSR' };
const serialized = JSON.stringify(state).replace(/</g, '\\\\u003c');
html = \`
&lt;div id="app"&gt;${appContent}&lt;/div&gt;
&lt;script&gt;window.\__INITIAL_STATE__ = ${serialized}&lt;/script&gt;
&lt;script src="/entry-client.js"&gt;&lt;/script&gt;
\`;

На клиенте:

const initialState = window.\__INITIAL_STATE_\_;

8. Кэширование

SSR может быть ресурсоёмким. Для повышения производительности применяют кэш HTML по URL или кэш промежуточных данных.

Иногда используют промежуточные шаблоны, которые вставляют HTML приложения в общий layout:

&lt;!-- шаблон layout.html --&gt;
&lt;html&gt;
&lt;body&gt;
&lt;!--vue-ssr-outlet--&gt;
&lt;/body&gt;
&lt;/html&gt;

Это реализуется, например, в Nuxt или Vite SSR-плагинах.

9. Vue + Vite SSR

Vue официально поддерживает SSR через Vite с ручной настройкой. Пример:

import { createServer } from 'vite';
const vite = await createServer({
server: { middlewareMode: true },
appType: 'custom'
});

Vite обрабатывает как серверный, так и клиентский бандлы, и облегчает настройку SSR без Webpack.

10. SSR и Nuxt

Nuxt 3 — это фреймворк на основе Vue 3 с полноценной SSR-поддержкой. Он инкапсулирует всю серверную логику, маршруты, загрузку данных, гидратацию и кэширование.

Пример:

npx nuxi init my-app
cd my-app
npm install
npm run dev

Nuxt автоматически обрабатывает серверный рендеринг, создание страницы, вызов API и обновление состояния.

11. Ограничения SSR

  • Не поддерживается DOM-специфичный API на сервере (window, document, localStorage и т. д.).

  • Хуки onMounted, onUpdated, watch не работают на сервере.

  • Задержка первой загрузки увеличивается из-за сетевых запросов и рендеринга на сервере.

  • Необходимо обеспечить идентичность серверного и клиентского HTML (иначе возникает ошибка hydration mismatch).

12. SSR и безопасность

Серверная сериализация состояния требует XSS-защиты. Vue автоматически экранирует данные, но при ручной вставке нужно избегать </script> и символов < в JSON:

JSON.stringify(state).replace(/</g, '\\\\u003c');

Также следует избегать прямой вставки пользовательского HTML через v-html в SSR.

13. Dev и Prod сборка

В production SSR требует сборки двух отдельных бандлов:

  1. Client bundle — JavaScript-код для браузера.

  2. Server bundle — код, который будет исполняться на Node.js.

Инструменты, как Vite, Webpack или Nuxt, обеспечивают такую сборку через разные entry-файлы и соответствующие конфигурации.