Серверный и гибридный рендеринг
По умолчанию Angular поставляет все приложения с клиентским рендерингом (CSR). Хотя такой подход обеспечивает небольшую начальную полезную нагрузку, у него есть и недостатки: более длительная загрузка, ухудшение показателей производительности и повышенные требования к ресурсам, поскольку большую часть вычислений выполняет устройство пользователя. Поэтому многие приложения значительно повышают производительность, интегрируя серверный рендеринг (SSR) в стратегию гибридного рендеринга.
Что такое гибридный рендеринг?
Гибридный рендеринг позволяет разработчикам использовать преимущества серверного рендеринга (SSR), предварительного рендеринга (также известного как «генерация статических сайтов», или SSG) и клиентского рендеринга (CSR) для оптимизации приложения Angular. Он дает точный контроль над тем, как отображаются разные части приложения, чтобы обеспечить пользователям наилучший возможный опыт.
Настройка гибридного рендеринга
Вы можете создать новый проект с гибридным рендерингом, используя флаг серверного рендеринга (то есть --ssr) с командой Angular CLI ng new:
ng new --ssr
Вы также можете включить гибридный рендеринг, добавив серверный рендеринг в существующий проект с помощью команды ng add:
ng add @angular/ssr
ПРИМЕЧАНИЕ: По умолчанию Angular предварительно отображает всё приложение и создает серверный файл. Чтобы отключить это и создать полностью статическое приложение, задайте для outputMode значение static. Чтобы включить SSR, обновите маршруты сервера, указав RenderMode.Server. Подробнее см. в разделах Server routing и Generate a fully static application.
Маршрутизация на сервере
Настройка маршрутов сервера
Вы можете создать конфигурацию маршрутов сервера, объявив массив объектов ServerRoute. Обычно эта конфигурация находится в файле с именем app.routes.server.ts.
// app.routes.server.ts
import {RenderMode, ServerRoute} from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '', // This renders the "/" route on the client (CSR)
renderMode: RenderMode.Client,
},
{
path: 'about', // This page is static, so we prerender it (SSG)
renderMode: RenderMode.Prerender,
},
{
path: 'profile', // This page requires user-specific data, so we use SSR
renderMode: RenderMode.Server,
},
{
path: '**', // All other routes will be rendered on the server (SSR)
renderMode: RenderMode.Server,
},
];
Добавить эту конфигурацию в приложение можно с помощью provideServerRendering и функции withRoutes:
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';
// app.config.server.ts
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(withRoutes(serverRoutes)),
// ... other providers ...
],
};
При использовании шаблона оболочки приложения необходимо указать компонент, который будет использоваться в качестве оболочки приложения для маршрутов с клиентским рендерингом. Для этого используйте функцию withAppShell:
import {provideServerRendering, withRoutes, withAppShell} from '@angular/ssr';
import {AppShell} from './app-shell';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShell)),
// ... other providers ...
],
}; Режимы рендеринга
Конфигурация маршрутизации сервера позволяет указать способ отображения каждого маршрута приложения, задав RenderMode:
| Режим рендеринга | Описание |
|---|---|
| Серверный (SSR) | Отображает приложение на сервере для каждого запроса и отправляет браузеру полностью заполненную HTML-страницу. |
| Клиентский (CSR) | Отображает приложение в браузере. Это поведение Angular по умолчанию. |
| Предварительный рендеринг (SSG) | Предварительно отображает приложение во время сборки и создает статические HTML-файлы для каждого маршрута. |
Выбор режима рендеринга
У каждого режима рендеринга есть свои преимущества и недостатки. Вы можете выбирать режимы рендеринга в соответствии с конкретными потребностями приложения.
Клиентский рендеринг (CSR)
Клиентский рендеринг обеспечивает самую простую модель разработки, поскольку можно писать код, предполагая, что он всегда выполняется в веб-браузере. Это позволяет использовать широкий спектр клиентских библиотек, которые также предполагают работу в браузере.
Как правило, производительность клиентского рендеринга хуже, чем у других режимов, поскольку перед отображением контента необходимо загрузить, разобрать и выполнить JavaScript страницы. Если при отображении страница запрашивает у сервера дополнительные данные, пользователям также приходится ждать завершения этих запросов, прежде чем они увидят весь контент.
Если поисковые роботы индексируют страницу, клиентский рендеринг может негативно повлиять на поисковую оптимизацию (SEO), поскольку поисковые роботы ограничены в объеме JavaScript, который они выполняют при индексировании страницы.
При клиентском рендеринге серверу не нужно выполнять никаких действий для отображения страницы, кроме предоставления статических ресурсов JavaScript. Это может быть важно, если стоимость работы сервера имеет значение.
Приложения, поддерживающие устанавливаемую работу и работу офлайн с помощью сервис-воркеров, могут использовать клиентский рендеринг без необходимости связываться с сервером.
Серверный рендеринг (SSR)
Серверный рендеринг обеспечивает более быструю загрузку страниц, чем клиентский. Вместо того чтобы ждать загрузки и выполнения JavaScript, сервер сразу создает HTML-документ после получения запроса от браузера. Пользователь ждет только столько, сколько необходимо серверу для получения данных и отображения запрошенной страницы. Этот режим также устраняет необходимость в дополнительных сетевых запросах из браузера, поскольку код может получать данные на сервере во время рендеринга.
Серверный рендеринг обычно обеспечивает отличную поисковую оптимизацию (SEO), поскольку поисковые роботы получают полностью отрисованный HTML-документ.
При серверном рендеринге необходимо писать код, который не зависит напрямую от API браузера. Кроме того, выбор JavaScript-библиотек, предполагающих работу в браузере, будет ограничен.
При серверном рендеринге сервер запускает Angular, чтобы сформировать HTML-ответ на каждый запрос, что может повысить стоимость хостинга сервера.
Предварительный рендеринг во время сборки
Предварительный рендеринг обеспечивает более быструю загрузку страниц, чем клиентский и серверный рендеринг. Поскольку HTML-документы создаются во время сборки, сервер может напрямую отвечать на запросы статическим HTML-документом, не выполняя никаких дополнительных действий.
Для предварительного рендеринга необходимо, чтобы вся информация, требуемая для отображения страницы, была доступна во время сборки. Это означает, что предварительно отрендеренные страницы не могут содержать данные, относящиеся к конкретному пользователю, загрузившему страницу. Предварительный рендеринг особенно полезен для страниц, одинаковых для всех пользователей приложения.
Поскольку предварительный рендеринг выполняется во время сборки, он может значительно увеличить продолжительность сборки для выпуска в production. Использование getPrerenderParams для создания большого количества HTML-документов может увеличить общий размер файлов развертывания и тем самым замедлить развертывание.
Предварительный рендеринг обычно обеспечивает отличную поисковую оптимизацию (SEO), поскольку поисковые роботы получают полностью отрисованный HTML-документ.
При предварительном рендеринге необходимо писать код, который не зависит напрямую от API браузера. Кроме того, выбор JavaScript-библиотек, предполагающих работу в браузере, будет ограничен.
Предварительный рендеринг создает очень небольшую нагрузку на сервер для каждого запроса, поскольку сервер отвечает статическими HTML-документами. Статические файлы также легко кэшируются сетями доставки контента (CDN), браузерами и промежуточными уровнями кэширования, что еще больше ускоряет последующие загрузки страниц. Полностью статические сайты можно развертывать только через CDN или сервер статических файлов, не поддерживая собственную серверную среду выполнения приложения. Это повышает масштабируемость, снимая нагрузку с сервера приложения, и особенно полезно для приложений с высокой посещаемостью.
ПРИМЕЧАНИЕ: При использовании сервис-воркера Angular первый запрос обрабатывается сервером, а все последующие запросы обрабатываются сервис-воркером и отображаются на клиенте.
Настройка заголовков и кодов состояния
Для отдельных маршрутов сервера можно задать пользовательские заголовки и коды состояния с помощью свойств headers и status в конфигурации ServerRoute.
// app.routes.server.ts
import {RenderMode, ServerRoute} from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: 'profile',
renderMode: RenderMode.Server,
headers: {
'X-My-Custom-Header': 'some-value',
},
status: 201,
},
// ... other routes
]; Перенаправления
Angular обрабатывает перенаправления, заданные свойством redirectTo в конфигурациях маршрутов, иначе на стороне сервера.
Серверный рендеринг (SSR) Перенаправления выполняются с помощью стандартных перенаправлений HTTP (например, 301, 302) в процессе серверного рендеринга.
Предварительный рендеринг (SSG) Перенаправления реализуются как «мягкие перенаправления» с помощью тегов <meta http-equiv="refresh"> в предварительно отрендеренном HTML.
Настройка предварительного рендеринга во время сборки (SSG)
При использовании RenderMode.Prerender можно указать несколько параметров конфигурации, чтобы настроить процесс предварительного рендеринга и обработки запросов.
Маршруты с параметрами
Для каждого маршрута с RenderMode.Prerender можно указать функцию getPrerenderParams. Эта функция позволяет определить, для каких именно параметров будут созданы отдельные предварительно отрендеренные документы.
Функция getPrerenderParams возвращает Promise, который разрешается в массив объектов. Каждый объект представляет собой карту «ключ — значение», где имя параметра маршрута сопоставлено со значением. Например, если определить маршрут вроде post/:id, то getPrerenderParams может вернуть массив [{id: 123}, {id: 456}] и, таким образом, создать отдельные документы для post/123 и post/456.
В теле функции getPrerenderParams можно использовать функцию Angular inject для внедрения зависимостей и выполнения любых действий, необходимых для определения маршрутов, подлежащих предварительному рендерингу. Обычно это включает отправку запросов для получения данных, используемых при формировании массива значений параметров.
Эту функцию также можно использовать с маршрутами, охватывающими все пути (например, /**), где имя параметра будет "**", а возвращаемое значение будет содержать сегменты пути, например foo/bar. Их можно комбинировать с другими параметрами (например, /post/:id/**) для обработки более сложной конфигурации маршрутов.
// app.routes.server.ts
import {RenderMode, ServerRoute} from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: 'post/:id',
renderMode: RenderMode.Prerender,
async getPrerenderParams() {
const dataService = inject(PostService);
const ids = await dataService.getIds(); // Assuming this returns ['1', '2', '3']
return ids.map((id) => ({id})); // Generates paths like: /post/1, /post/2, /post/3
},
},
{
path: 'post/:id/**',
renderMode: RenderMode.Prerender,
async getPrerenderParams() {
return [
{id: '1', '**': 'foo/3'},
{id: '2', '**': 'bar/4'},
]; // Generates paths like: /post/1/foo/3, /post/2/bar/4
},
},
];
Поскольку getPrerenderParams применяется только к RenderMode.Prerender, эта функция всегда выполняется во время сборки. getPrerenderParams не должен зависеть от API, доступных только в браузере или только на сервере, для получения данных.
ВАЖНО: При использовании inject внутри getPrerenderParams помните, что inject необходимо использовать синхронно. Его нельзя вызывать внутри асинхронных обратных вызовов или после операторов await. Дополнительную информацию см. в разделе runInInjectionContext.
Стратегии резервного режима
При использовании режима RenderMode.Prerender можно задать стратегию резервного режима для обработки запросов к путям, которые не были предварительно отрендерены.
Доступны следующие стратегии резервного режима:
-
Сервер: Используется серверный рендеринг. Это поведение по умолчанию, если свойство
fallbackне задано. - Клиент: Используется клиентский рендеринг.
- Нет: Резервный режим не используется. Angular не будет обрабатывать запросы к путям, которые не были предварительно отрендерены.
// app.routes.server.ts
import {RenderMode, PrerenderFallback, ServerRoute} from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: 'post/:id',
renderMode: RenderMode.Prerender,
fallback: PrerenderFallback.Client, // Fallback to CSR if not prerendered
async getPrerenderParams() {
// This function returns an array of objects representing prerendered posts at the paths:
// `/post/1`, `/post/2`, and `/post/3`.
// The path `/post/4` will utilize the fallback behavior if it's requested.
return [{id: 1}, {id: 2}, {id: 3}];
},
},
]; Создание совместимых с сервером компонентов
Некоторые распространенные API и возможности браузера могут быть недоступны на сервере. Приложения не могут использовать глобальные объекты, специфичные для браузера, такие как window, document, navigator или location, а также некоторые свойства HTMLElement.
Как правило, код, использующий символы, специфичные для браузера, должен выполняться только в браузере, а не на сервере. Этого можно добиться с помощью хуков жизненного цикла afterEveryRender и afterNextRender. Они выполняются только в браузере и пропускаются на сервере.
import {Component, viewChild, afterNextRender} from '@angular/core';
@Component({
selector: 'my-cmp',
template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
contentRef = viewChild.required<ElementRef>('content');
constructor() {
afterNextRender(() => {
// Safe to check `scrollHeight` because this will only run in the browser, not the server.
console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
});
}
}
ПРИМЕЧАНИЕ: Предпочитайте провайдеры для конкретной платформы проверкам во время выполнения с помощью isPlatformBrowser или isPlatformServer.
ВАЖНО: Не используйте isPlatformBrowser в шаблонах с @if или другими условными конструкциями для отображения разного содержимого на сервере и клиенте. Это приводит к несоответствиям при гидратации и сдвигам макета, что негативно сказывается на пользовательском опыте и показателях Core Web Vitals. Вместо этого используйте afterNextRender для инициализации, специфичной для браузера, и сохраняйте единообразие отображаемого содержимого на разных платформах.
Настройка провайдеров на сервере
На стороне сервера значения провайдеров верхнего уровня задаются один раз при первоначальном разборе и вычислении кода приложения. Это означает, что провайдеры, настроенные с помощью useValue, сохраняют свое значение для нескольких запросов до перезапуска серверного приложения.
Чтобы генерировать новое значение для каждого запроса, используйте фабричный провайдер с useFactory. Фабричная функция будет выполняться для каждого входящего запроса, создавая новое значение и присваивая его токену.
Предоставление реализаций для конкретной платформы
Если приложению требуется разное поведение в браузере и на сервере, предоставьте отдельные реализации сервиса для каждой платформы. Такой подход позволяет централизовать платформенную логику в специализированных сервисах.
export abstract class AnalyticsService {
abstract trackEvent(name: string): void;
}
Создайте реализацию для браузера:
@Injectable()
export class BrowserAnalyticsService implements AnalyticsService {
trackEvent(name: string): void {
// Sends the event to the browser-based third-party analytics provider
}
}
Создайте реализацию для сервера:
@Injectable()
export class ServerAnalyticsService implements AnalyticsService {
trackEvent(name: string): void {
// Records the event on the server
}
}
Зарегистрируйте реализацию для браузера в основной конфигурации приложения:
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [{provide: AnalyticsService, useClass: BrowserAnalyticsService}],
};
Переопределите ее реализацией для сервера в конфигурации сервера:
// app.config.server.ts
const serverConfig: ApplicationConfig = {
providers: [{provide: AnalyticsService, useClass: ServerAnalyticsService}],
};
Внедрите сервис в компоненты и используйте его:
@Component({
/*...*/
})
export class Checkout {
private analytics = inject(AnalyticsService);
onAction() {
this.analytics.trackEvent('action');
}
} Доступ к Document через DI
При работе с серверным рендерингом не следует напрямую обращаться к глобальным объектам, специфичным для браузера, например document. Вместо этого используйте токен DOCUMENT, чтобы получить доступ к объекту документа независимо от платформы.
import {inject, DOCUMENT, Service} from '@angular/core';
@Service()
export class CanonicalLinkService {
private readonly document = inject(DOCUMENT);
// During server rendering, inject a <link rel="canonical"> tag
// so the generated HTML includes the correct canonical URL
setCanonical(href: string): void {
const link = this.document.createElement('link');
link.rel = 'canonical';
link.href = href;
this.document.head.appendChild(link);
}
}
ПОЛЕЗНО: Для работы с метатегами Angular предоставляет сервис Meta.
Доступ к Request и Response через DI
Пакет @angular/core предоставляет несколько токенов для взаимодействия со средой серверного рендеринга. Эти токены дают доступ к важной информации и объектам приложения Angular во время SSR.
-
REQUEST: Предоставляет доступ к текущему объекту запроса типаRequestиз Web API. Это позволяет получать доступ к заголовкам, файлам cookie и другой информации о запросе. -
RESPONSE_INIT: Предоставляет доступ к параметрам инициализации ответа типаResponseInitиз Web API. Это позволяет динамически задавать заголовки и код состояния ответа. Используйте этот токен для настройки заголовков или кодов состояния, которые должны определяться во время выполнения. -
REQUEST_CONTEXT: Предоставляет доступ к дополнительному контексту, связанному с текущим запросом. Этот контекст можно передать вторым параметром функцииhandle. Обычно он используется для передачи дополнительной информации о запросе, которая не входит в стандартный Web API.
import {inject, REQUEST} from '@angular/core';
@Component({
selector: 'app-my-component',
template: `<h1>My Component</h1>`,
})
export class MyComponent {
constructor() {
const request = inject(REQUEST);
console.log(request?.url);
}
}
ВАЖНО: Перечисленные выше токены будут null в следующих случаях:
- Во время процессов сборки.
- При отображении приложения в браузере (CSR).
- При генерации статического сайта (SSG).
- Во время извлечения маршрутов в режиме разработки (в момент запроса).
Создание полностью статического приложения
По умолчанию Angular предварительно отображает всё приложение и создает серверный файл для обработки запросов. Это позволяет приложению предоставлять пользователям предварительно отрендеренное содержимое. Однако, если вы предпочитаете полностью статический сайт без сервера, можно отключить такое поведение, задав для outputMode значение static в файле конфигурации angular.json.
Если для outputMode задано значение static, Angular создает предварительно отрендеренные HTML-файлы для каждого маршрута во время сборки, но не создает серверный файл и не требует сервера Node.js для обслуживания приложения. Это удобно при развертывании на платформах статического хостинга, которым не нужен сервер backend.
Чтобы настроить это, обновите файл angular.json следующим образом:
{
"projects": {
"your-app": {
"architect": {
"build": {
"options": {
"outputMode": "static"
}
}
}
}
}
} Кэширование данных при использовании HttpClient
HttpClient кэширует исходящие сетевые запросы при работе на сервере. Эта информация сериализуется и передается браузеру как часть начального HTML, отправленного сервером. В браузере HttpClient проверяет наличие данных в кэше и, если они там есть, использует их повторно вместо отправки нового HTTP-запроса при первоначальном отображении приложения. HttpClient прекращает использовать кэш, когда приложение становится стабильным в браузере.
Настройка параметров кэширования
Вы можете настроить кэширование HTTP-ответов Angular при серверном рендеринге (SSR) и их повторное использование во время гидратации, задав параметры HttpTransferCacheOptions.
Эта конфигурация задается глобально с помощью withHttpTransferCacheOptions внутри provideClientHydration().
По умолчанию HttpClient кэширует все запросы HEAD и GET, которые не содержат заголовки Authorization, Proxy-Authorization или Cookie и не отправляются с withCredentials. Эти настройки можно переопределить, добавив withHttpTransferCacheOptions в конфигурацию гидратации.
import {bootstrapApplication} from '@angular/platform-browser';
import {provideClientHydration, withHttpTransferCacheOptions} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [
provideClientHydration(
withHttpTransferCacheOptions({
includeHeaders: ['ETag', 'Cache-Control'],
filter: (req) => !req.url.includes('/api/profile'),
includePostRequests: true,
includeRequestsWithAuthHeaders: false,
}),
),
],
});
includeHeaders
Указывает, какие заголовки из ответа сервера следует включать в записи кэша.
По умолчанию заголовки не включаются.
withHttpTransferCacheOptions({
includeHeaders: ['ETag', 'Cache-Control'],
});
ВАЖНО: Не включайте конфиденциальные заголовки, например токены аутентификации. Это может привести к утечке пользовательских данных между запросами.
includePostRequests
По умолчанию кэшируются только запросы GET и HEAD.
Можно включить кэширование запросов POST, если они используются для чтения данных, например в запросах GraphQL.
withHttpTransferCacheOptions({
includePostRequests: true,
});
Используйте эту настройку только в том случае, если запросы POST являются идемпотентными и их безопасно повторно использовать при рендеринге на сервере и клиенте.
includeRequestsWithAuthHeaders
Определяет, можно ли кэшировать запросы с заголовками Authorization, Proxy‑Authorization или Cookie.
По умолчанию такие запросы исключаются, чтобы предотвратить кэширование ответов, зависящих от пользователя. Запросы, отправляемые с withCredentials, также по умолчанию исключаются.
withHttpTransferCacheOptions({
includeRequestsWithAuthHeaders: true,
});
Включайте этот параметр только в том случае, если заголовки аутентификации не влияют на содержимое ответа (например, публичные токены для API аналитики).
Переопределения для отдельных запросов
Поведение кэширования для конкретного запроса можно переопределить с помощью параметра запроса transferCache.
// Include specific headers for this request
http.get('/api/profile', {transferCache: {includeHeaders: ['CustomHeader']}}); Отключение кэширования
Можно отключить HTTP-кэширование запросов, отправляемых с сервера, глобально или для отдельных запросов.
Глобально
Чтобы отключить кэширование всех запросов приложения, используйте функцию withNoHttpTransferCache:
import {
bootstrapApplication,
provideClientHydration,
withNoHttpTransferCache,
} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [provideClientHydration(withNoHttpTransferCache())],
}); Фильтрация
Также можно выборочно отключить кэширование определенных запросов, используя параметр filter в withHttpTransferCacheOptions. Например, можно отключить кэширование для конкретной конечной точки API:
import {
bootstrapApplication,
provideClientHydration,
withHttpTransferCacheOptions,
} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [
provideClientHydration(
withHttpTransferCacheOptions({
filter: (req) => !req.url.includes('/api/sensitive-data'),
}),
),
],
});
Используйте этот параметр, чтобы исключить конечные точки с пользовательскими или динамическими данными (например, /api/profile).
Для отдельного запроса
Чтобы отключить кэширование отдельного запроса, укажите параметр transferCache в объекте HttpRequest.
httpClient.get('/api/sensitive-data', {transferCache: false});
ПРИМЕЧАНИЕ: Если приложение использует разные источники HTTP для вызовов API на сервере и клиенте, токен HTTP_TRANSFER_CACHE_ORIGIN_MAP позволяет сопоставить эти источники, чтобы функция HttpTransferCache распознавала такие запросы как одинаковые и повторно использовала данные, кэшированные на сервере, при гидратации на клиенте.
Настройка сервера
Node.js
@angular/ssr/node расширяет @angular/ssr специально для сред Node.js. Он предоставляет API, упрощающие реализацию серверного рендеринга в приложении Node.js. Полный список функций и примеры использования см. в справочнике API @angular/ssr/node.
// server.ts
import {
AngularNodeAppEngine,
createNodeRequestHandler,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
const app = express();
const angularApp = new AngularNodeAppEngine();
app.use('*', (req, res, next) => {
angularApp
.handle(req)
.then((response) => {
if (response) {
writeResponseToNodeResponse(response, res);
} else {
next(); // Pass control to the next middleware
}
})
.catch(next);
});
/**
* The request handler used by the Angular CLI (dev-server and during build).
*/
export const reqHandler = createNodeRequestHandler(app); Платформы без Node.js
@angular/ssr предоставляет основные API для серверного рендеринга приложения Angular на платформах, отличных от Node.js. Он использует стандартные объекты Request и Response из Web API, что позволяет интегрировать Angular SSR в различные серверные среды. Подробную информацию и примеры см. в справочнике API @angular/ssr.
// server.ts
import {AngularAppEngine, createRequestHandler} from '@angular/ssr';
const angularApp = new AngularAppEngine();
/**
* This is a request handler used by the Angular CLI (dev-server and during build).
*/
export const reqHandler = createRequestHandler(async (req: Request) => {
const res: Response | null = await angularApp.render(req);
// ...
}); Безопасность
Подробные сведения о предотвращении подделки серверных запросов (SSRF) и настройке разрешенных хостов см. в руководстве по безопасности на стороне сервера.
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/ssr