SSR-совместимость в Vite опирается на единый плагинный механизм, где один и тот же плагин должен корректно работать в двух режимах выполнения: клиентском (браузерная сборка) и серверном (Node.js окружение для SSR). Основная сложность заключается в том, что SSR-режим предъявляет дополнительные требования к загрузке модулей, обработке зависимостей и отсутствию браузерных API.
SSR-режим в Vite запускается через отдельный пайплайн, где модули
загружаются не как статические бандлы, а как граф зависимостей,
исполняемый через ssrLoadModule. Это означает:
Плагин, не учитывающий эти различия, может корректно работать в dev-режиме браузера, но ломаться при серверной сборке.
Плагины Vite строятся на базе Rollup-подобной системы хуков:
resolveIdloadtransformconfigureServergenerateBundleДля SSR добавляется контекст ssr, который передаётся во
многие хуки через опции:
transform(code, id, options) {
if (options?.ssr) {
// SSR-специфичная трансформация
}
return code
}
Ключевое отличие SSR-плагина заключается в условной логике выполнения, зависящей от режима сборки.
Одним из базовых механизмов является разделение логики по среде выполнения:
if (import.meta.env.SSR) {
// серверная логика
} else {
// клиентская логика
}
Плагины Vite могут автоматически трансформировать такие условия, обеспечивая tree-shaking ненужного кода в каждом из окружений.
Основная проблема SSR-плагинов — использование браузерных глобальных объектов:
windowdocumentlocalStorageSSR-плагин должен либо:
Пример трансформации:
transform(code, id, options) {
if (options?.ssr) {
return code.replace(/window\./g, 'undefined/* window */.')
}
}
На практике используется AST-трансформация через esbuild
или @babel/parser.
В SSR-режиме модульный граф строится иначе. Плагин может влиять на резолвинг:
resolveId(source, importer, options) {
if (options?.ssr) {
if (source === 'some-browser-only-lib') {
return source + '/ssr'
}
}
}
SSR-резолвинг часто направлен на альтернативные entry points библиотек, например:
modulemainexports["node"]Vite учитывает package.json поле exports и
условия import conditions.
Плагины должны учитывать, что SSR-сборка часто внешнеет зависимости:
Плагин может влиять на это через ssr.external и
ssr.noExternal.
export default {
ssr: {
noExternal: ['my-isomorphic-lib']
}
}
SSR-совместимый плагин не должен ломать externalization логику, например, не должен принудительно инлайнить Node-only пакеты.
В dev SSR используется:
server.ssrLoadModule('/src/entry-server.js')
Плагины участвуют в:
Важно учитывать, что SSR-код исполняется сразу после трансформации, без финального бандла.
Порядок выполнения плагинов критичен:
SSR-плагины должны избегать:
ssr флагаПравильная изоляция:
const cache = new WeakMap()
transform(code, id, options) {
const key = options?.ssr ? id + ':ssr' : id
if (cache.has(key)) return cache.get(key)
const result = code.toUpperCase()
cache.set(key, result)
return result
}
Одним из ключевых аспектов является контроль side effects.
SSR-режим требует:
Плагин может помечать модули как side-effect free:
moduleSideEffects(id) {
if (id.includes('pure-lib')) return false
}
SSR-совместимый плагин часто имеет двойную логику:
export default function plugin() {
return {
name: 'example-plugin',
transform(code, id, options) {
if (options?.ssr) {
return transformServer(code)
}
return transformClient(code)
}
}
}
Иногда выделяются отдельные внутренние модули:
В dev-режиме SSR и HMR работают параллельно:
Плагин может участвовать в HMR через:
handleHotUpdate(ctx) {
if (ctx.modules.some(m => m.id.includes('ssr-only'))) {
ctx.server.reload()
}
}
SSR-плагины должны избегать неконсистентного состояния между клиентом и сервером.
Vite предоставляет доступ к графу модулей:
server.moduleGraphSSR-плагин может использовать его для:
handleHotUpdate({ server, file }) {
const mods = server.moduleGraph.getModulesByFile(file)
mods.forEach(m => server.moduleGraph.invalidateModule(m))
}
В production SSR используется Rollup:
buildStartgenerateBundlewriteBundleSSR-плагин должен учитывать, что:
Vite SSR build генерирует manifest, содержащий:
Плагин может модифицировать:
generateBundle(options, bundle) {
if (options.ssr) {
// модификация server bundle
}
}
SSR-плагины часто влияют на hydration процесс:
Ошибки возникают при:
1. Isomorphic transform
transform(code, id, options) {
return options?.ssr
? transformForNode(code)
: transformForBrowser(code)
}
2. Conditional import rewriting
resolveId(source, importer, options) {
if (options?.ssr && source === 'fs/browser') {
return 'fs/node'
}
}
3. Guarded runtime injection
transform(code, id, options) {
if (!options?.ssr) {
return code + '\nconsole.log("client only")'
}
return code
}
Часто встречаются следующие проблемы:
window в transform без проверки
режимаSSR-плагин в Vite должен придерживаться следующих ограничений:
ssr флагуТакая модель позволяет одному плагину стабильно работать как в dev SSR, так и в production SSR без нарушения консистентности между серверным HTML и клиентской гидратацией.