From f7157d10a45f3661d8afb4cbc8be72034d7f9c4a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:49:33 +0000 Subject: [PATCH] fix(@angular/build): handle redirects from guards during prerendering During prerendering, if a route returns a redirect, a static HTML page with a meta refresh tag is generated to redirect the user to the specified URL. This now includes support for redirects returned from route guards. Closes #31618 --- .../src/utils/server-rendering/prerender.ts | 26 +------------------ .../utils/server-rendering/render-worker.ts | 9 ++++++- .../build/src/utils/server-rendering/utils.ts | 25 ++++++++++++++++++ .../server-routes-output-mode-static.ts | 13 ++++++++-- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/packages/angular/build/src/utils/server-rendering/prerender.ts b/packages/angular/build/src/utils/server-rendering/prerender.ts index e087262a7f0c..5ece379ec9c0 100644 --- a/packages/angular/build/src/utils/server-rendering/prerender.ts +++ b/packages/angular/build/src/utils/server-rendering/prerender.ts @@ -26,6 +26,7 @@ import { WritableSerializableRouteTreeNode, } from './models'; import type { RenderWorkerData } from './render-worker'; +import { generateRedirectStaticPage } from './utils'; type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions']; type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions']; @@ -380,28 +381,3 @@ function addTrailingSlash(url: string): string { function removeLeadingSlash(value: string): string { return value[0] === '/' ? value.slice(1) : value; } - -/** - * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL. - * - * This function creates a simple HTML page that performs a redirect using a meta tag. - * It includes a fallback link in case the meta-refresh doesn't work. - * - * @param url - The URL to which the page should redirect. - * @returns The HTML content of the static redirect page. - */ -function generateRedirectStaticPage(url: string): string { - return ` - - -
- -Redirecting to ${url}
-
-
-`.trim();
-}
diff --git a/packages/angular/build/src/utils/server-rendering/render-worker.ts b/packages/angular/build/src/utils/server-rendering/render-worker.ts
index f3fc8e93a0d0..7ded0550b826 100644
--- a/packages/angular/build/src/utils/server-rendering/render-worker.ts
+++ b/packages/angular/build/src/utils/server-rendering/render-worker.ts
@@ -12,6 +12,7 @@ import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loa
import { patchFetchToLoadInMemoryAssets } from './fetch-patch';
import { DEFAULT_URL, launchServer } from './launch-server';
import { loadEsmModuleFromMemory } from './load-esm-from-memory';
+import { generateRedirectStaticPage } from './utils';
export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
assetFiles: Record** Destination */ string, /** Source */ string>;
@@ -48,7 +49,13 @@ async function renderPage({ url }: RenderOptions): PromiseRedirecting to ${url}
+
+
+`.trim();
+}
diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-static.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-static.ts
index 617776a94dc7..77f954be4f4d 100644
--- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-static.ts
+++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-static.ts
@@ -29,7 +29,8 @@ export default async function () {
await writeFile(
'src/app/app.routes.ts',
`
- import { Routes } from '@angular/router';
+ import { inject } from '@angular/core';
+ import { Routes, Router } from '@angular/router';
import { Home } from './home/home';
import { Ssg } from './ssg/ssg';
import { SsgWithParams } from './ssg-with-params/ssg-with-params';
@@ -47,6 +48,12 @@ export default async function () {
path: 'ssg-redirect',
redirectTo: 'ssg'
},
+ {
+ path: 'ssg-redirect-via-guard',
+ canActivate: [() => {
+ return inject(Router).createUrlTree(['ssg'], { queryParams: { foo: 'bar' }})
+ }],
+ },
{
path: 'ssg/:id',
component: SsgWithParams,
@@ -106,8 +113,10 @@ export default async function () {
'ssg/index.html': /ng-server-context="ssg".+ssg works!/,
'ssg/one/index.html': /ng-server-context="ssg".+ssg-with-params works!/,
'ssg/two/index.html': /ng-server-context="ssg".+ssg-with-params works!/,
- // When static redirects as generated as meta tags.
+ // When static redirects are generated as meta tags.
'ssg-redirect/index.html': '',
+ 'ssg-redirect-via-guard/index.html':
+ '',
};
for (const [filePath, fileMatch] of Object.entries(expects)) {