Skip to content

Commit 45e498f

Browse files
committed
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 (cherry picked from commit cc16b10)
1 parent e6ccc74 commit 45e498f

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

packages/angular/build/src/utils/server-rendering/prerender.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
WritableSerializableRouteTreeNode,
2727
} from './models';
2828
import type { RenderWorkerData } from './render-worker';
29+
import { generateRedirectStaticPage } from './utils';
2930

3031
type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions'];
3132
type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions'];
@@ -380,28 +381,3 @@ function addTrailingSlash(url: string): string {
380381
function removeLeadingSlash(value: string): string {
381382
return value[0] === '/' ? value.slice(1) : value;
382383
}
383-
384-
/**
385-
* Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
386-
*
387-
* This function creates a simple HTML page that performs a redirect using a meta tag.
388-
* It includes a fallback link in case the meta-refresh doesn't work.
389-
*
390-
* @param url - The URL to which the page should redirect.
391-
* @returns The HTML content of the static redirect page.
392-
*/
393-
function generateRedirectStaticPage(url: string): string {
394-
return `
395-
<!DOCTYPE html>
396-
<html>
397-
<head>
398-
<meta charset="utf-8">
399-
<title>Redirecting</title>
400-
<meta http-equiv="refresh" content="0; url=${url}">
401-
</head>
402-
<body>
403-
<pre>Redirecting to <a href="${url}">${url}</a></pre>
404-
</body>
405-
</html>
406-
`.trim();
407-
}

packages/angular/build/src/utils/server-rendering/render-worker.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loa
1212
import { patchFetchToLoadInMemoryAssets } from './fetch-patch';
1313
import { DEFAULT_URL, launchServer } from './launch-server';
1414
import { loadEsmModuleFromMemory } from './load-esm-from-memory';
15+
import { generateRedirectStaticPage } from './utils';
1516

1617
export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
1718
assetFiles: Record</** Destination */ string, /** Source */ string>;
@@ -48,7 +49,13 @@ async function renderPage({ url }: RenderOptions): Promise<string | null> {
4849
new Request(new URL(url, serverURL), { signal: AbortSignal.timeout(30_000) }),
4950
);
5051

51-
return response ? response.text() : null;
52+
if (!response) {
53+
return null;
54+
}
55+
56+
const location = response.headers.get('Location');
57+
58+
return location ? generateRedirectStaticPage(location) : response.text();
5259
}
5360

5461
async function initialize() {

packages/angular/build/src/utils/server-rendering/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,28 @@ export function isSsrRequestHandler(
1919
): value is ReturnType<typeof createRequestHandler> {
2020
return typeof value === 'function' && '__ng_request_handler__' in value;
2121
}
22+
23+
/**
24+
* Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
25+
*
26+
* This function creates a simple HTML page that performs a redirect using a meta tag.
27+
* It includes a fallback link in case the meta-refresh doesn't work.
28+
*
29+
* @param url - The URL to which the page should redirect.
30+
* @returns The HTML content of the static redirect page.
31+
*/
32+
export function generateRedirectStaticPage(url: string): string {
33+
return `
34+
<!DOCTYPE html>
35+
<html>
36+
<head>
37+
<meta charset="utf-8">
38+
<title>Redirecting</title>
39+
<meta http-equiv="refresh" content="0; url=${url}">
40+
</head>
41+
<body>
42+
<pre>Redirecting to <a href="${url}">${url}</a></pre>
43+
</body>
44+
</html>
45+
`.trim();
46+
}

tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-static.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export default async function () {
2929
await writeFile(
3030
'src/app/app.routes.ts',
3131
`
32-
import { Routes } from '@angular/router';
32+
import { inject } from '@angular/core';
33+
import { Routes, Router } from '@angular/router';
3334
import { Home } from './home/home';
3435
import { Ssg } from './ssg/ssg';
3536
import { SsgWithParams } from './ssg-with-params/ssg-with-params';
@@ -47,6 +48,12 @@ export default async function () {
4748
path: 'ssg-redirect',
4849
redirectTo: 'ssg'
4950
},
51+
{
52+
path: 'ssg-redirect-via-guard',
53+
canActivate: [() => {
54+
return inject(Router).createUrlTree(['ssg'], { queryParams: { foo: 'bar' }})
55+
}],
56+
},
5057
{
5158
path: 'ssg/:id',
5259
component: SsgWithParams,
@@ -106,8 +113,10 @@ export default async function () {
106113
'ssg/index.html': /ng-server-context="ssg".+ssg works!/,
107114
'ssg/one/index.html': /ng-server-context="ssg".+ssg-with-params works!/,
108115
'ssg/two/index.html': /ng-server-context="ssg".+ssg-with-params works!/,
109-
// When static redirects as generated as meta tags.
116+
// When static redirects are generated as meta tags.
110117
'ssg-redirect/index.html': '<meta http-equiv="refresh" content="0; url=/ssg">',
118+
'ssg-redirect-via-guard/index.html':
119+
'<meta http-equiv="refresh" content="0; url=/ssg?foo=bar">',
111120
};
112121

113122
for (const [filePath, fileMatch] of Object.entries(expects)) {

0 commit comments

Comments
 (0)