diff --git a/packages/core/src/render3/after_render_hooks.ts b/packages/core/src/render3/after_render_hooks.ts
index 4662c4b711ab..9b4038023975 100644
--- a/packages/core/src/render3/after_render_hooks.ts
+++ b/packages/core/src/render3/after_render_hooks.ts
@@ -148,6 +148,11 @@ export interface InternalAfterNextRenderOptions {
export function internalAfterNextRender(
callback: VoidFunction, options?: InternalAfterNextRenderOptions) {
const injector = options?.injector ?? inject(Injector);
+
+ // Similarly to the public `afterNextRender` function, an internal one
+ // is only invoked in a browser.
+ if (!isPlatformBrowser(injector)) return;
+
const afterRenderEventManager = injector.get(AfterRenderEventManager);
afterRenderEventManager.internalCallbacks.push(callback);
}
diff --git a/packages/platform-server/test/hydration_spec.ts b/packages/platform-server/test/hydration_spec.ts
index cbdb911864f3..448ff232811e 100644
--- a/packages/platform-server/test/hydration_spec.ts
+++ b/packages/platform-server/test/hydration_spec.ts
@@ -2374,6 +2374,53 @@ describe('platform-server hydration integration', () => {
verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
});
+ it('should not reference IntersectionObserver on the server', async () => {
+ // This test verifies that there are no errors produced while rendering on a server
+ // when `on viewport` trigger is used for a defer block.
+ @Component({
+ selector: 'my-lazy-cmp',
+ standalone: true,
+ template: 'Hi!',
+ })
+ class MyLazyCmp {
+ }
+
+ @Component({
+ standalone: true,
+ selector: 'app',
+ imports: [MyLazyCmp],
+ template: `
+ @defer (when isVisible; prefetch on viewport(ref)) {
+