From ea45b4d54cada5ffd8d6e4fe7e020f1ba97e95b9 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:01:37 -0500 Subject: [PATCH] docs: add platform-specific implementation and helpers (cherry picked from commit 0c7508bf56faedba8cc0c348c59e817281dc19f3) --- adev/src/content/guide/ssr.md | 71 +++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/adev/src/content/guide/ssr.md b/adev/src/content/guide/ssr.md index bd88fbd42d8..9376b6790c1 100644 --- a/adev/src/content/guide/ssr.md +++ b/adev/src/content/guide/ssr.md @@ -267,6 +267,10 @@ export class MyComponent { } ``` +NOTE: Prefer [platform-specific providers](guide/ssr#providing-platform-specific-implementations) over runtime checks with `isPlatformBrowser` or `isPlatformServer`. + +IMPORTANT: Avoid using `isPlatformBrowser` in templates with `@if` or other conditionals to render different content on server and client. This causes hydration mismatches and layout shifts, negatively impacting user experience and [Core Web Vitals](https://web.dev/learn-core-web-vitals/). Instead, use `afterNextRender` for browser-specific initialization and keep rendered content consistent across platforms. + ## Setting providers on the server On the server side, top level provider values are set once when the application code is initially parsed and evaluated. @@ -274,6 +278,73 @@ This means that providers configured with `useValue` will keep their value acros If you want to generate a new value for each request, use a factory provider with `useFactory`. The factory function will run for every incoming request, ensuring that a new value is created and assigned to the token each time. +## Providing platform-specific implementations + +When your application needs different behavior on the browser and server, provide separate service implementations for each platform. This approach centralizes platform logic in dedicated services. + +```ts +export abstract class AnalyticsService { + abstract trackEvent(name: string): void; +} +``` + +Create the browser implementation: + +```ts +@Injectable() +export class BrowserAnalyticsService implements AnalyticsService { + trackEvent(name: string): void { + // Sends the event to the browser-based third-party analytics provider + } +} +``` + +Create the server implementation: + +```ts +@Injectable() +export class ServerAnalyticsService implements AnalyticsService { + trackEvent(name: string): void { + // Records the event on the server + } +} +``` + +Register the browser implementation in your main application configuration: + +```ts +// app.config.ts +export const appConfig: ApplicationConfig = { + providers: [ + { provide: AnalyticsService, useClass: BrowserAnalyticsService }, + ] +}; +``` + +Override with the server implementation in your server configuration: + +```ts +// app.config.server.ts +const serverConfig: ApplicationConfig = { + providers: [ + { provide: AnalyticsService, useClass: ServerAnalyticsService }, + ] +}; +``` + +Inject and use the service in your components: + +```ts +@Component({/* ... */}) +export class Checkout { + private analytics = inject(AnalyticsService); + + onAction() { + this.analytics.trackEvent('action'); + } +} +``` + ## Accessing Document via DI When working with server-side rendering, you should avoid directly referencing browser-specific globals like `document`. Instead, use the [`DOCUMENT`](api/core/DOCUMENT) token to access the document object in a platform-agnostic way.