SSR-точка входа в приложении на Vite определяет серверную часть рендеринга, отвечающую за генерацию HTML на стороне сервера перед передачей его клиенту. В отличие от классического SPA, где браузер получает пустой HTML и самостоятельно строит интерфейс, SSR формирует готовую разметку заранее, обеспечивая более быстрый первый рендер и улучшенную индексацию.
В контексте Vite SSR разделение ответственности становится строгим: клиентская точка входа отвечает за гидратацию и дальнейшую интерактивность, серверная — за первичную генерацию HTML и подготовку состояния приложения.
Типичная SSR-архитектура в Vite включает несколько ключевых файлов:
Серверная точка входа формирует HTML-строку и возвращает её в HTTP-ответе, а клиентская подхватывает уже готовую разметку.
Разделение особенно важно, поскольку код, выполняемый на сервере, не
имеет доступа к браузерным API (window,
document, localStorage), а клиентский код не
должен содержать серверной логики.
Серверная точка входа представляет собой модуль, экспортирующий функцию рендеринга приложения. Эта функция принимает контекст запроса и возвращает HTML.
Минимальная структура:
export function render(url, manifest) {
return {
html: '',
state: {}
}
}
В реальных приложениях используется более сложная модель, включающая создание экземпляра приложения, роутинг и асинхронную загрузку данных.
Ключевая особенность SSR в Vite заключается в том, что серверная точка входа загружается через специальный API:
vite.ssrLoadModule('/src/entry-server.js')
Это позволяет использовать ES-модули напрямую без предварительной сборки.
Механизм ssrLoadModule является центральным элементом
SSR в Vite. Он выполняет загрузку модулей в Node.js с учётом
трансформаций Vite, включая:
Серверная точка входа должна быть совместима с этим механизмом, то есть не должна использовать Node-специфичные require-конструкции, если проект ориентирован на ESM.
Пример интеграции:
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
После загрузки функция render используется для генерации
HTML.
Серверный entry-модуль обычно создаёт экземпляр приложения без привязки к DOM:
import { createApp } from './app'
export function render(url) {
const { app, router } = createApp()
router.push(url)
await router.isReady()
const html = await renderToString(app)
return {
html
}
}
В этом коде важен порядок:
Функция renderToString может быть из
@vue/server-renderer, React DOM Server или аналогичного
SSR-движка.
Клиентская точка входа выполняет гидратацию HTML, сгенерированного сервером.
import { createApp } from './app'
createApp().mount('#app')
Важно, что клиентская часть не выполняет повторный рендер, а только активирует уже существующую DOM-структуру.
SSR-архитектура требует синхронизации состояния между сервером и клиентом. Обычно используется передача состояния через глобальный объект:
window.__INITIAL_STATE__
Сервер записывает состояние, клиент его считывает.
HTML-шаблон Vite играет роль контейнера для SSR-результата. Обычно он содержит:
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>
Сервер заменяет комментарий на сгенерированный HTML:
template.replace('<!--ssr-outlet-->', html)
Также может добавляться сериализованное состояние:
<script>
window.__INITIAL_STATE__ = {...}
</script>
Vite предоставляет middleware-режим для SSR, позволяющий интегрировать dev-сервер с Node.js сервером (например, Express или Koa).
import express from 'express'
import { createServer as createViteServer } from 'vite'
const app = express()
const vite = await createViteServer({
server: { middlewareMode: true }
})
app.use(vite.middlewares)
В этом режиме SSR-точка входа загружается динамически при каждом запросе, что обеспечивает мгновенное отражение изменений кода.
Серверная точка входа должна учитывать URL запроса, так как именно он определяет содержимое страницы.
Типичный поток:
Критически важно дождаться завершения всех асинхронных операций перед рендерингом.
Ошибка в этом месте приводит к частично отрендеренному HTML и рассинхронизации гидратации.
SSR требует предварительной загрузки данных до генерации HTML. Это решается через специальные хуки:
await Promise.all(routeComponents.map(loadData))
Каждый компонент может иметь метод загрузки:
Component.asyncData = async (store, route) => {
await store.fetch(route.params.id)
}
Серверная точка входа агрегирует эти вызовы.
Vite поддерживает отдельную сборку для серверной части:
vite build --ssr src/entry-server.js
Результат представляет собой Node.js-совместимый модуль, оптимизированный для выполнения на сервере.
Особенности SSR-сборки:
Manifest используется для связывания клиентских ассетов с серверным рендерингом.
Он содержит:
Серверная точка входа использует manifest для вставки правильных
<script> и <link> тегов:
const scripts = manifest['src/entry-client.js'].file
Это обеспечивает корректную загрузку клиентского бандла.
Распространённые проблемы:
Каждый запрос должен создавать новый экземпляр приложения.
SSR требует строгой изоляции:
export function createApp() {
const app = new App()
const router = new Router()
return { app, router }
}
Каждый вызов SSR entry должен формировать новый контекст, исключая shared state.
Полный процесс включает последовательные шаги:
ssrLoadModuleКаждый этап критичен для корректной работы SSR.
SSR entry может быть расширена дополнительными слоями:
Streaming SSR особенно важен для крупных приложений, где полный рендер занимает значительное время.
При использовании потокового рендеринга HTML формируется по частям:
const stream = renderToNodeStream(app)
stream.pipe(res)
SSR entry в этом случае возвращает не строку, а поток, что снижает время до первого байта.
SSR-точка входа становится центральным элементом серверной архитектуры. От её структуры зависит:
В Vite SSR entry не является просто файлом, а полноценным слоем исполнения приложения в серверной среде, связывающим систему модулей Vite и runtime Node.js.