SSR в Vite строится вокруг принципа разделения зависимостей на те,
что исполняются в Node.js как внешние модули, и те, что должны быть
включены в серверный бандл через esbuild/Rollup. Управление этим
поведением осуществляется через две ключевые настройки:
ssr.external и ssr.noExternal. Эти параметры
напрямую влияют на то, как Vite обрабатывает node_modules
при серверной сборке, как разрешаются импорты и какие пакеты оказываются
в итоговом SSR-бандле.
При сборке SSR-приложения Vite не стремится повторить поведение браузерного бандлера. Вместо этого он формирует Node-ориентированную графовую структуру модулей, где каждый импорт классифицируется как:
require()/import в Node.jsПо умолчанию Vite пытается:
Это поведение можно переопределить двумя опциями.
ssr.external определяет список зависимостей, которые
никогда не будут включены в SSR-бандл и всегда
останутся внешними импортами.
export default {
ssr: {
external: ['lodash', 'express']
}
}
Если модуль попадает в ssr.external, Vite:
import как естьНекоторые библиотеки рассчитаны исключительно на Node.js и не должны бандлиться:
fs, path, crypto (встроенные
модули Node)bcrypt, sharp,
sqlite3)Пример:
ssr: {
external: ['sharp', 'bcrypt']
}
Некоторые пакеты сами управляют загрузкой подмодулей:
Бандлинг таких пакетов может ломать динамические импорты.
В pnpm/yarn workspaces зависимости могут находиться вне
node_modules текущего пакета. Пометка их как external
снижает риск двойного резолва.
ssr.external работает только в SSR-контексте. Браузерный
бандл не затрагивается.
Можно использовать строки или паттерны:
ssr: {
external: [
'lodash',
/^@nestjs\//,
'pg'
]
}
Регулярные выражения позволяют исключать целые namespace-пакеты.
Если пометить пакет как external, но он использует ESM-экспорт, Node
может потребовать дополнительной конфигурации
"type": "module" или interop-обёртки. Это часто проявляется
ошибками:
ERR_REQUIRE_ESMCannot use import statement outside a modulessr.noExternal делает противоположное: заставляет Vite
всегда бандлить указанные зависимости, даже если они
обычно считаются внешними.
export default {
ssr: {
noExternal: ['some-esm-lib']
}
}
При обработке графа модулей Vite:
noExternalНекоторые пакеты распространяются как ESM-only и не дружат с CommonJS окружением Node SSR:
export default без CJS fallbackПример:
ssr: {
noExternal: ['unified', 'remark']
}
Если библиотека использует import() внутри и Node
резолвит их некорректно, бандлинг устраняет проблему.
Некоторые библиотеки содержат условный код:
if (typeof window !== 'undefined')
При SSR это может ломать выполнение. Бандлинг позволяет Vite провести tree-shaking и заменить окружение.
Если один и тот же пакет попадает в оба списка:
ssr.noExternalssr: {
noExternal: ['vue', 'vue-router']
}
ssr: {
noExternal: [/^@vue\//]
}
ssr: {
external: ['lodash'],
noExternal: ['vue']
}
Vite SSR использует Node resolution алгоритм, но с дополнительным анализом:
package.json (type,
exports)sideEffects)noExternal заставляет Vite игнорировать часть этой
логики и всегда идти через бандлер.
Обычно:
noExternalexternalssr: {
noExternal: ['vue', 'vue-router', 'pinia']
}
Если SSR используется как серверный рендеринг API + HTML:
ssr: {
external: [
'fs',
'path',
'crypto',
'pg',
'mysql2'
]
}
При workspace структуре:
ssr: {
noExternal: [
/^@shared\//,
/^@ui\//
]
}
Это гарантирует, что внутренние пакеты будут правильно транспилироваться.
Причина:
Решение:
noExternalПричина:
Решение:
external или
noExternalПричина:
Решение:
noExternalПлюсы:
Минусы:
Плюсы:
Минусы:
На практике используется гибридный подход:
Типичная базовая конфигурация:
export default {
ssr: {
noExternal: [
'vue',
'vue-router',
'pinia'
],
external: [
'sharp',
'bcrypt',
'pg'
]
}
}
ssr.external и ssr.noExternal можно
рассматривать как два слоя контроля:
Их комбинация определяет границу между:
При импорте модуля в SSR:
ssr.noExternalssr.externalЭта последовательность критична для понимания неожиданных эффектов в SSR-сборках Vite.