Как реализовать универсальное (SSR) приложение с Angular Universal?
Angular Universal — это технология серверного рендеринга (SSR) в Angular, позволяющая рендерить приложение на сервере перед отправкой HTML-контента клиенту. Это ускоряет первую отрисовку, улучшает SEO и повышает производительность на слабых устройствах. SSR особенно полезен для приложений, которые должны быть индексируемыми поисковыми системами или быстро загружаться при медленном соединении.
1. Принцип работы Angular Universal
-
Пользователь делает запрос к серверу (например, GET /home).
-
Сервер запускает Node.js-приложение с Angular Universal.
-
Приложение рендерится в HTML на сервере.
-
Готовый HTML-контент отправляется пользователю.
-
После загрузки на клиенте Angular "оживляет" приложение (hydration), подключая обработчики событий и состояния.
2. Установка Angular Universal
Для преобразования существующего Angular-приложения в SSR-версию используется CLI:
ng add @nguniversal/express-engine
Команда:
-
добавит зависимости (@nguniversal/express-engine, ts-loader, express);
-
создаст необходимые файлы (server.ts, main.server.ts, app.server.module.ts);
-
обновит angular.json и package.json для поддержки SSR.
3. Основные файлы SSR-структуры
main.server.ts — entry point серверной сборки:
```python
export { AppServerModule } from './app/app.server.module';
**app.server.module.ts** — серверный модуль:
<br/>```python
@NgModule({
imports: \[
AppModule,
ServerModule,
\],
bootstrap: \[AppComponent\],
})
export class AppServerModule {}
server.ts — Node.js сервер на Express:
import 'zone.js/node';
import express from 'express';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { AppServerModule } from './src/main.server';
const app = express();
app.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
app.set('view engine', 'html');
app.set('views', 'dist/my-app/browser');
app.get('\*.\*', express.static('dist/my-app/browser'));
app.get('\*', (req, res) => {
res.render('index', { req });
});
app.listen(4000, () => console.log('Server running on http://localhost:4000'));
4. Сборка SSR-приложения
Создаются два бандла:
Browser (SPA, как обычно):
```python
ng build --prod
**Server** (Node.js):
<br/>```python
ng run my-app:server:production
Можно использовать скрипт:
"scripts": {
"build:ssr": "ng build --prod && ng run my-app:server:production",
"serve:ssr": "node dist/my-app/server/main.js"
}
5. Использование API браузера
Поскольку SSR выполняется в среде Node.js, нельзя напрямую использовать API браузера (window, document, navigator). Необходимо оборачивать такие вызовы:
if (typeof window !== 'undefined') {
// безопасный доступ к window
}
Или через Angular PLATFORM_ID:
import { isPlatformBrowser } from '@angular/common';
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
// код выполняется только в браузере
}
}
6. Обработка асинхронных данных при SSR
Angular Universal дожидается выполнения всех асинхронных задач в шаблоне перед рендерингом. Например, Observable в *ngIf с async pipe будет автоматически разрешён на сервере.
Однако асинхронные операции вне шаблона (например, в ngOnInit) должны быть синхронизированы через APP_INITIALIZER, TransferState или resolve в маршрутах.
7. Кэширование HTML (предварительный рендеринг)
Angular Universal поддерживает предрендеринг (static prerender):
ng run my-app:prerender
Это генерирует статические HTML-файлы, которые можно отдавать как обычные страницы. Подходит для сайтов с фиксированным маршрутом.
8. TransferState API
Позволяет передать данные с сервера на клиент, чтобы не выполнять один и тот же HTTP-запрос дважды (на сервере и в браузере).
import { TransferState, makeStateKey } from '@angular/platform-browser';
const STATE_KEY = makeStateKey<any>('some-data');
constructor(private transferState: TransferState) {}
ngOnInit() {
if (this.transferState.hasKey(STATE_KEY)) {
this.data = this.transferState.get(STATE_KEY, null);
} else {
this.http.get('/api/data').subscribe(data => {
this.data = data;
this.transferState.set(STATE_KEY, data);
});
}
}
9. Angular CLI и Angular.json
После добавления Universal Angular CLI создаёт 2 цели сборки:
-
"browser" — обычная SPA;
-
"server" — серверная сборка;
-
"ssr" — сборка и запуск SSR;
-
"prerender" — статический рендер.
10. SSR и маршрутизация
SSR поддерживает RouterModule, включая resolve, lazy-loading, guards. Однако нужно учитывать:
-
Ленивая загрузка требует предварительной инициализации модулей.
-
CanActivate и CanLoad могут влиять на рендер.
11. SEO преимущества
Angular Universal позволяет:
-
Индексацию страниц поисковыми системами;
-
Быструю первую отрисовку (First Contentful Paint);
-
Создание мета-тегов на сервере (Title, Meta, Link).
constructor(private title: Title, private meta: Meta) {}
ngOnInit() {
this.title.setTitle('My Page Title');
this.meta.updateTag({ name: 'description', content: 'Page description' });
}
12. Производственные особенности
-
SSR требует собственного Node.js-сервера.
-
Можно использовать Express, NestJS, Firebase Functions, Vercel, Cloudflare Workers и другие среды.
-
Возможна интеграция с CDN, кэшами, Redis, и промежуточным рендерингом (islands architecture).
13. Ошибки SSR
Наиболее частые ошибки:
-
Использование window, document, localStorage без проверки окружения.
-
Отсутствие TransferState, приводящее к дублирующимся запросам.
-
Неправильные пути в Express или static middleware.
-
Неожиданные асинхронные операции, вызывающие пустой HTML.
14. Инструменты и отладка
-
Логируйте запросы и рендер в server.ts.
-
Используйте DevTools Lighthouse для замеров FCP, TTI, SEO.
-
Проверяйте результат рендеринга в HTML-ответе (view-source:).
-
Добавляйте логирование в main.server.ts, AppServerModule, ngExpressEngine.
Таким образом, Angular Universal предоставляет полную поддержку SSR в Angular-приложениях, включая рендеринг на сервере, передачу состояния, SEO-оптимизацию и динамическую маршрутизацию.