Динамические маршруты Angular v21 SSR standalone для мобильных/десктоп
Как предоставить динамически загружаемые маршруты в Angular v21 с SSR и standalone-архитектурой для мобильной и десктопной версий на корневом пути ''. Настройка provideRouter, обработка ресайза, lazy loading и лучшие практики роутинга.
Как в Angular v21 с SSR и standalone-архитектурой предоставить динамически загружаемые маршруты для мобильной и десктопной версий приложения, использующих один корневой путь (‘’)?
В app.ts динамически загружается мобильная или десктопная версия в зависимости от ширины окна:
protected readonly child = signal<Type<any> | null>(null);
ngOnInit() {
if (isPlatformBrowser(this.platformId) && window.innerWidth < 768) {
import('./mobile-version/mobile-version').then(c => this.child.set(c.MobileVersion));
} else {
import('./desktop-version/desktop-version').then(c => this.child.set(c.DesktopVersion));
}
}
Мобильная версия имеет свои дочерние маршруты в mobile-version.routes.ts:
import { Routes } from '@angular/router';
import { MobileVersion } from './mobile-version';
export const MOBILE_ROUTES: Routes = [
{ path: '', component: MobileVersion },
{ path: 'imprint', loadComponent: () => import('./imprint/imprint').then(c => c.Imprint) },
{ path: 'policy', loadComponent: () => import('./privacy/privacy').then(c => c.Privacy) },
];
- Как предоставить маршруты мобильной версии при загрузке мобильного компонента?
- Можно ли определить
MOBILE_ROUTESвнутри самогоMobileVersionили они должны предоставляться из родительского компонента? - Продолжит ли роутинг работать корректно при изменении размера окна в процессе использования приложения (если десктопные маршруты загружаются аналогично и используют тот же корневой путь)?
- Какова лучшая практика для реализации такого сценария?
В Angular v21 с SSR и standalone-архитектурой динамически загружаемые маршруты для мобильной и десктопной версий на одном корневом пути (‘’) предоставляются через provideRouter с функцией, проверяющей ширину окна в bootstrapApplication. Это позволяет избежать багов с ROUTES в lazy-loaded standalone компонентах, обеспечивая корректный angular router при ресайзе. Лучшая практика — определять MOBILE_ROUTES в отдельном файле и предоставлять их условно через higher-level API, с обработчиком ресайза для перезагрузки роутера.
Содержание
- Введение в angular router и standalone angular в Angular v21
- Настройка динамической загрузки компонентов с angular ssr
- Предоставление дочерних маршрутов в angular standalone component
- Обработка lazy loading components angular для мобильной и десктопной версий
- Роутинг при изменении размера окна и angular router navigate
- Лучшие практики и распространенные проблемы в angular lazy load с SSR
- Источники
- Заключение
Введение в angular router и standalone angular в Angular v21
Angular router в версии 21 идеально вписывается в standalone-архитектуру, где нет модулей, а всё строится на компонентах и провайдерах. Представьте: одно приложение, один корневой путь ‘’, но разные интерфейсы — мобильный и десктопный, загружаемые динамически по ширине экрана. Это не просто трюк, а реальный способ оптимизировать SSR для производительности: сервер рендерит базовый shell, клиент дорисовывает нужный вариант.
В вашем коде сигнал child в app.ts — хороший старт для lazy loading components angular. Но роутинг добавляет сложность: как передать MOBILE_ROUTES в загруженный MobileVersion без перезагрузки страницы? Официальная документация Angular подчёркивает, что router управляет навигацией через outlets и links, обновляя контент без фулл-рилоада. А в standalone всё упрощается provideRouter вместо RouterModule.
Почему это важно именно с angular ssr? Сервер не знает ширину клиента, так что изначально рендерит пустой outlet, а клиент подхватывает. Давайте разберём по шагам, чтобы избежать типичных ловушек вроде “роуты не срабатывают после ресайза”.
Настройка динамической загрузки компонентов с angular ssr
Сначала bootstrapApplication в main.ts. Здесь задаём provideRouter с пустым маршрутом ‘’, где outlet рендерит сигнал child из AppComponent. SSR совместимо, потому что isPlatformBrowser защищает от window на сервере.
Вот базовый setup:
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app';
bootstrapApplication(AppComponent, {
providers: [
provideRouter([
{ path: '', outlet: 'child-outlet', loadComponent: () => import('./app/app').then(c => c.AppComponent) }
])
]
});
В AppComponent добавьте
Альтернатива: сделайте loadComponent функцией, возвращающей нужный компонент по ширине. Но для роутов лучше глобальный провайдер — об этом дальше. На сервере это работает стабильно, клиент гидратирует без мерцания.
Предоставление дочерних маршрутов в angular standalone component
Можно ли определить MOBILE_ROUTES прямо в MobileVersion? Теоретически да, через providers: [ROUTES, MOBILE_ROUTES]. Но на практике в standalone lazy-loaded компонентах это не срабатывает — фабрика не вызывается, роуты игнорируются. Это известный баг, обсуждаемый в GitHub issue Angular.
Правильный путь: предоставлять роуты из родителя или глобально. В app.ts inject Router и используйте setChildRoutes или higher-level API. Лучше — условный provideRouter в bootstrap:
// Функция для роутов
function getRoutes() {
if (typeof window !== 'undefined' && window.innerWidth < 768) {
return MOBILE_ROUTES;
}
return DESKTOP_ROUTES; // аналогично
}
// main.ts
provideRouter([{ path: '', children: getRoutes() }]) // Но это статично!
Проблема: getRoutes вызывается раз при bootstrap. Для динамики нужен сервис с сигналом ширины и router.resetConfig. Родительский компонент (App) может inject ROUTER_PROVIDERS и предоставить child роуты через DI.
Коротко: определяйте MOBILE_ROUTES вне компонента (в .routes.ts), импортируйте и предоставляйте через provideRouter в providers AppComponent или bootstrap. Внутри MobileVersion — не рекомендуется из-за бага.
Обработка lazy loading components angular для мобильной и десктопной версий
Lazy loading components angular здесь ключ: import(‘./mobile-version/mobile-version’) — это loadComponent в роуте. Для дочерних: в MOBILE_ROUTES используйте loadComponent для imprint/policy.
В AppComponent шаблон:
<ng-container *ngComponentOutlet="child(); selector: ''">
<router-outlet></router-outlet>
</ng-container>
Selector ‘’ позволяет рендерить child без тега. Роуты загружаются лениво: при навигации на ‘imprint’ подхватывается loadComponent.
С SSR: сервер видит пустой outlet (child null), клиент заполняет. ANGULARarchitects рекомендует именно loadComponent/loadChildren для standalone. Ваш код почти готов — добавьте provideChildRouter в App providers.
Десктоп аналогично: DESKTOP_ROUTES с теми же путями ‘’, ‘imprint’ и т.д. Один корень ‘’ делит роуты nested.
Роутинг при изменении размера окна и angular router navigate
А что при ресайзе? Роутинг сломается: если был мобильный ‘imprint’, а окно расширили — DesktopVersion загрузится, но роуты не обновятся, навигация зависнет.
Решение: слушайте resize в сервисе, обновляйте сигнал child и router.config.
// route-service.ts
export class RouteService {
width = signal(window.innerWidth);
constructor() {
window.addEventListener('resize', () => this.width.set(window.innerWidth));
}
}
// app.ts
constructor(private routeService: RouteService, private router: Router) {}
ngOnInit() {
effect(() => {
const w = this.routeService.width();
if (w < 768) {
import('./mobile-version/mobile-version').then(c => {
this.child.set(c.MobileVersion);
this.router.resetConfig(MOBILE_ROUTES); // Или navigate root
});
} else {
// Аналогично для desktop
}
});
}
Router.resetConfig(MOBILE_ROUTES) перезагружает роуты. Для angular router navigate используйте { skipLocationChange: true } при смене. Guards (canMatch) проверяют ширину перед активацией роута — идеально для предотвращения багов.
Без этого роутинг “запомнит” старые роуты. Тестируйте: ресайзьте, кликните link — должно перезагружать child роуты.
Лучшие практики и распространенные проблемы в angular lazy load с SSR
Лучшая практика: центральный RouteService с сигналом ширины, provideRouter в bootstrap с динамическими роутами через resolver или factory. Избегайте ROUTES — используйте provideRouter/loadChildren.
Проблемы:
- Баг с providers в lazy standalone: фикс — higher-level API.
- SSR hydration mismatch: используйте isPlatformBrowser везде.
- Bundle size: tree-shake unused роуты.
- Performance: debounce resize (lodash или RxJS).
Пример полного bootstrap:
provideRouter([
{
path: '',
loadChildren: () => width < 768 ? import('./mobile.routes') : import('./desktop.routes')
}
])
Это лениво грузи роуты целиком. Guards для роутов: canMatch(fn(width)).
В проде с angular ssr: ng serve --ssr=false для теста, build с --ssr. Документация подтверждает: nested lazy с standalone — стандарт.
Ресайз без фликера? Signal + ngComponentOutlet магия. Попробуйте — и увидите, как приложение “адаптируется” на лету.
Источники
- ROUTES — InjectionToken для низкоуровневой конфигурации angular router в standalone: https://angular.dev/api/router/ROUTES
- Routing — Руководство по роутингу, outlets и lazy loading в Angular: https://angular.dev/guide/routing
- Routing and Lazy-Loading with Standalone Components — Практика роутинга и lazy load в standalone angular: https://www.angulararchitects.io/en/blog/routing-and-lazy-loading-with-standalone-components/
- Standalone lazy-loaded ROUTES providers not working — Обсуждение бага с ROUTES в lazy компонентах: https://github.com/angular/angular/issues/54518
Заключение
В Angular v21 с SSR динамические роуты для мобильной/десктопной версий на ‘’ реализуются через условный provideRouter и RouteService с сигналом ширины — это решает все под вопросы: предоставление MOBILE_ROUTES извне, ресайз без сбоев и lazy loading. Забудьте ROUTES из-за багов, полагайтесь на loadComponent и guards. В итоге приложение лёгкое, адаптивное и SSR-ready. Начните с сервиса — и роутинг заработает как часы, даже при безумных ресайзах.
ROUTES — это InjectionToken для низкоуровневой конфигурации angular router через dependency injection в standalone angular. Рекомендуется использовать высокоуровневые API, такие как provideRouter или RouterModule.forRoot(), вместо прямого ROUTES для настройки маршрутов с angular ssr. Это упрощает lazy loading components angular и обеспечивает совместимость с SSR в Angular v21.
- Подходит для динамических child-маршрутов в мобильных/десктопных версиях.
- Избегайте прямого использования из-за потенциальных багов в lazy-loaded компонентах.
Angular Router управляет навигацией в SPA, определяя компоненты по URL с использованием outlets для рендеринга и links для angular router navigate без перезагрузки страницы. Поддерживает nested routes, guards, params и lazy loading в standalone angular. В angular ssr роутинг обновляет контент на клиенте после гидратации index.html, идеально для динамических мобильных/десктопных версий на одном корневом пути ‘’.
В standalone angular используйте provideRouter с loadComponent или loadChildren для angular lazy load маршрутов. Для динамических мобильных/десктопных версий создайте функцию, проверяющую window.innerWidth (с isPlatformBrowser для angular ssr), и передайте условные MOBILE_ROUTES в bootstrapApplication. Маршруты можно определять внутри MobileVersion или импортировать; при ресайзе обновляйте Router с guards для корректного angular router navigate.
- Лучшая практика: сервис для детекции размера + динамические провайдеры.
В standalone lazy-loaded компонентах провайдер ROUTES не срабатывает: фабрика не вызывается, маршруты не предоставляются, даже с inject() в loadChildren. Это известный баг в angular router (Angular 15+), где ROUTES в providers компонента или маршрутов игнорируется при lazy loading components angular. Рекомендуется использовать higher-level API вроде provideRouter для динамических child-маршрутов в standalone angular с SSR.
