Как происходит SSR (server-side rendering) во Vue?
Server-Side Rendering (SSR) во Vue — это процесс генерации HTML на сервере, а не в браузере клиента. SSR используется для ускорения первой отрисовки, улучшения SEO и повышения производительности на слабых устройствах. В Vue 3 серверный рендеринг реализуется через официальную библиотеку @vue/server-renderer и часто интегрируется с такими фреймворками, как Nuxt.
1. Принцип SSR во Vue
SSR работает следующим образом:
-
Сервер получает HTTP-запрос.
-
Создаётся экземпляр приложения Vue на основе маршрута и состояния.
-
Этот экземпляр рендерится в HTML-строку с помощью renderToString().
-
Сервер отправляет сгенерированный HTML клиенту.
-
Клиент загружает 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 = \`
<div id="app">${appContent}</div>
<script>window.\__INITIAL_STATE__ = ${serialized}</script>
<script src="/entry-client.js"></script>
\`;
На клиенте:
const initialState = window.\__INITIAL_STATE_\_;
8. Кэширование
SSR может быть ресурсоёмким. Для повышения производительности применяют кэш HTML по URL или кэш промежуточных данных.
Иногда используют промежуточные шаблоны, которые вставляют HTML приложения в общий layout:
<!-- шаблон layout.html -->
<html>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
Это реализуется, например, в 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 требует сборки двух отдельных бандлов:
-
Client bundle — JavaScript-код для браузера.
-
Server bundle — код, который будет исполняться на Node.js.
Инструменты, как Vite, Webpack или Nuxt, обеспечивают такую сборку через разные entry-файлы и соответствующие конфигурации.