SSR и SSG — что меняется в компиляции (и что НЕ меняется)
Контекст: 00-overview. Главный вопрос — «как компилируется приложение для SSR/SSG».
Сразу главное
Компиляция компонентов/шаблонов при SSR и SSG — точно такая же, как для обычного
браузерного приложения: full AOT Ivy → ɵɵdefineComponent (см. 01-full-pipeline).
Те же template-функции, те же инструкции ɵɵdomElementStart/ɵɵtextInterpolate/…
SSR/SSG не трогают компилятор шаблонов. Они добавляют:
- второй build target — серверный бандл (кроме браузерного);
- другую платформу в рантайме —
platform-serverвместоplatform-browser; - шаг рендера в HTML — на запрос (SSR) или на этапе сборки (SSG/prerender);
- hydration — чтобы браузер не перерисовывал готовый серверный DOM.
То есть разница не в «как компилируется компонент», а в «куда и когда выполняется уже скомпилированный код».
CSR vs SSR vs SSG
| CSR (обычно) | SSR | SSG (prerender) | |
|---|---|---|---|
| Где строится HTML | в браузере, в рантайме | на сервере (Node), на каждый запрос | на этапе сборки, заранее |
| Когда | при загрузке у юзера | при HTTP-запросе | при ng build |
| Что отдаётся | пустой index.html + JS |
готовый HTML + JS | готовые статические .html + JS |
| Платформа рендера | platform-browser |
platform-server |
platform-server (во время билда) |
| Плюс | просто | свежие данные + быстрый first paint + SEO | мгновенная отдача (CDN), без сервера |
| Минус | пустой экран/SEO | нужен работающий Node-сервер | контент «замораживается» на момент билда |
Компиляция Ivy во всех трёх — одинаковая. Отличается только платформа и момент выполнения.
Что добавляет сборка: ДВА бандла
ng build с включённым SSR (@angular/build:application с опциями server/ssr/
prerender) выдаёт два выходных набора из одного и того же скомпилированного кода:
скомпилированный Ivy-код (один и тот же)
├─ browser bundle → dist/.../browser/ (грузится в браузере, как обычно)
└─ server bundle → dist/.../server/ (запускается на Node)
точка входа: src/main.server.ts / server.ts
bootstrap через provideServerRendering (а не bootstrapApplication в браузере)
Серверный бандл подключает @angular/platform-server. У него нет настоящего DOM —
вместо браузерного DOM используется domino (серверная реализация DOM на JS,
platform-server/src/domino_adapter.ts). Поэтому те же инструкции ɵɵdomElement*
создают узлы не в реальном браузере, а в domino-дереве, которое потом сериализуется в
строку HTML.
SSR — поток на каждый запрос
HTTP-запрос на Node-сервер
└─ renderApplication() / handler из @angular/ssr (platform-server/src/utils.ts)
├─ createServerPlatform() → platformServer([...]) // platform-server, domino DOM
├─ bootstrap приложения → create+update проходы строят DOM в domino
├─ дождаться стабилизации (initializers, pending tasks)
├─ annotateForHydration() — расставить маркеры hydration (атрибуты `ngh`)
└─ сериализовать domino-DOM → строка HTML
→ отдать HTML клиенту (быстрый first paint, готовый для SEO)
→ в браузере подхватывает browser bundle + provideClientHydration (см. ниже)
annotateForHydration (platform-server/src/utils.ts) и transfer state добавляют в
HTML служебную разметку, чтобы клиент мог «переиспользовать» этот DOM, а не строить с нуля.
SSG / prerender — на этапе сборки
SSG — это тот же серверный рендер, но выполненный во время ng build, а не на
запрос. Билдер:
- берёт серверный бандл;
- прогоняет его по списку маршрутов (роуты, которые умеет перечислить роутер, или заданные вручную);
- для каждого маршрута получает HTML-строку и пишет статический
.htmlвdist.
Результат можно положить на CDN/статический хостинг — Node-сервер в рантайме не нужен. Минус: данные «вмёрзли» на момент сборки (для динамики — SSR или ISR-подходы).
Компиляция тут снова та же — просто скомпилированный код один раз исполняется при билде для генерации страниц.
Hydration — чтобы клиент не перерисовывал серверный DOM
Без hydration браузер выкинул бы серверный HTML и построил DOM заново (мелькание,
лишняя работа). provideClientHydration() (platform-browser/src/hydration.ts)
включает переиспользование DOM: Angular на клиенте обходит существующие узлы
(по маркерам ngh из серверного рендера) и «оживляет» их — навешивает листенеры,
связывает с LView, не пересоздавая элементы.
Связанные механизмы:
- TransferState (
core/src/transfer_state.ts) — данные, полученные на сервере (напр. HTTP-ответы), кладутся в HTML и переиспользуются на клиенте, чтобы не делать тот же запрос дважды. - Event replay — события, случившиеся до загрузки JS, воспроизводятся после
hydration (
platform-server/test/event_replay_spec.ts). - Incremental hydration (v17+) —
@defer (hydrate on …)гидрирует куски лениво, по триггеру (platform-server/test/incremental_hydration_spec.ts).
Hydration — это рантайм-механизм, к компиляции компонентов отношения не имеет
(кроме того, что @defer блоки компилируются отдельными dehydrated-видами).
Что в компиляции НЕ меняется
ngtscтак же генеритɵfac/ɵcmp, та же template-функция(rf, ctx).- Те же стадии HtmlParser → parseTemplate → ingest → фазы → reify → emit.
- Библиотеки так же partial + linker (02-partial-ivy-and-linker).
- Один и тот же скомпилированный код едет и в browser-, и в server-бандл.
Меняется окружение исполнения (platform-server + domino), момент (запрос vs билд) и постобработка (сериализация в HTML + hydration-маркеры).
Привязка к исходникам
packages/platform-server/src/utils.ts—renderApplication/serialize +annotateForHydrationpackages/platform-server/src/provide_server.ts—provideServerRenderingpackages/platform-server/src/domino_adapter.ts— серверный DOM (domino)packages/platform-browser/src/hydration.ts—provideClientHydrationpackages/core/src/hydration/— DOM reuse, incremental hydrationpackages/core/src/transfer_state.ts— TransferState@angular/ssr(отдельный пакет) — handler/Express-обвязка для SSR-сервера