Skip to content

Commit 0cfe2e7

Browse files
committed
fix(@angular/ssr): propagate status code to redirect
This commit ensures that status codes set in code are propagated to the redirect when using `Router.navigate`. (cherry picked from commit 081e313)
1 parent bb54747 commit 0cfe2e7

File tree

4 files changed

+58
-26
lines changed

4 files changed

+58
-26
lines changed

packages/angular/ssr/src/app.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { InlineCriticalCssProcessor } from './utils/inline-critical-css';
2525
import { LRUCache } from './utils/lru-cache';
2626
import { AngularBootstrap, renderAngular } from './utils/ng';
2727
import { promiseWithAbort } from './utils/promise';
28+
import { createRedirectResponse } from './utils/redirect';
2829
import { buildPathWithParams, joinUrlParts, stripLeadingSlash } from './utils/url';
2930

3031
/**
@@ -351,7 +352,7 @@ export class AngularServerApp {
351352
}
352353

353354
if (result.redirectTo) {
354-
return createRedirectResponse(result.redirectTo, status);
355+
return createRedirectResponse(result.redirectTo, responseInit.status);
355356
}
356357

357358
if (renderMode === RenderMode.Prerender) {
@@ -546,20 +547,3 @@ function appendPreloadHintsToHtml(html: string, preload: readonly string[]): str
546547
html.slice(bodyCloseIdx),
547548
].join('\n');
548549
}
549-
550-
/**
551-
* Creates an HTTP redirect response with a specified location and status code.
552-
*
553-
* @param location - The URL to which the response should redirect.
554-
* @param status - The HTTP status code for the redirection. Defaults to 302 (Found).
555-
* See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
556-
* @returns A `Response` object representing the HTTP redirect.
557-
*/
558-
function createRedirectResponse(location: string, status = 302): Response {
559-
return new Response(null, {
560-
status,
561-
headers: {
562-
'Location': location,
563-
},
564-
});
565-
}

packages/angular/ssr/src/routes/ng-routes.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Console } from '../console';
2828
import { AngularAppManifest, getAngularAppManifest } from '../manifest';
2929
import { AngularBootstrap, isNgModule } from '../utils/ng';
3030
import { promiseWithAbort } from '../utils/promise';
31+
import { VALID_REDIRECT_RESPONSE_CODES, isValidRedirectResponseCode } from '../utils/redirect';
3132
import { addTrailingSlash, joinUrlParts, stripLeadingSlash } from '../utils/url';
3233
import {
3334
PrerenderFallback,
@@ -59,11 +60,6 @@ const CATCH_ALL_REGEXP = /\/(\*\*)$/;
5960
*/
6061
const URL_PARAMETER_REGEXP = /(?<!\\):([^/]+)/g;
6162

62-
/**
63-
* An set of HTTP status codes that are considered valid for redirect responses.
64-
*/
65-
const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
66-
6763
/**
6864
* Additional metadata for a server configuration route tree.
6965
*/
@@ -163,7 +159,7 @@ async function* handleRoute(options: {
163159
includePrerenderFallbackRoutes,
164160
);
165161
} else if (redirectTo !== undefined) {
166-
if (metadata.status && !VALID_REDIRECT_RESPONSE_CODES.has(metadata.status)) {
162+
if (metadata.status && !isValidRedirectResponseCode(metadata.status)) {
167163
yield {
168164
error:
169165
`The '${metadata.status}' status code is not a valid redirect response code. ` +
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
/**
10+
* An set of HTTP status codes that are considered valid for redirect responses.
11+
*/
12+
export const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
13+
14+
/**
15+
* Checks if the given HTTP status code is a valid redirect response code.
16+
*
17+
* @param code The HTTP status code to check.
18+
* @returns `true` if the code is a valid redirect response code, `false` otherwise.
19+
*/
20+
export function isValidRedirectResponseCode(code: number): boolean {
21+
return VALID_REDIRECT_RESPONSE_CODES.has(code);
22+
}
23+
24+
/**
25+
* Creates an HTTP redirect response with a specified location and status code.
26+
*
27+
* @param location - The URL to which the response should redirect.
28+
* @param status - The HTTP status code for the redirection. Defaults to 302 (Found).
29+
* See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
30+
* @returns A `Response` object representing the HTTP redirect.
31+
*/
32+
export function createRedirectResponse(location: string, status = 302): Response {
33+
if (ngDevMode && !isValidRedirectResponseCode(status)) {
34+
throw new Error(
35+
`Invalid redirect status code: ${status}. ` +
36+
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
37+
);
38+
}
39+
40+
return new Response(null, {
41+
status,
42+
headers: {
43+
'Location': location,
44+
},
45+
});
46+
}

packages/angular/ssr/test/app_spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import '@angular/compiler';
1212
/* eslint-enable import/no-unassigned-import */
1313

1414
import { APP_BASE_HREF } from '@angular/common';
15-
import { Component, REQUEST, inject } from '@angular/core';
15+
import { Component, REQUEST, RESPONSE_INIT, inject } from '@angular/core';
1616
import { CanActivateFn, Router } from '@angular/router';
1717
import { AngularServerApp } from '../src/app';
1818
import { RenderMode } from '../src/routes/route-config';
@@ -33,6 +33,12 @@ describe('AngularServerApp', () => {
3333
})
3434
class RedirectComponent {
3535
constructor() {
36+
const responseInit = inject(RESPONSE_INIT);
37+
if (responseInit) {
38+
// TODO(alanagius): Remove once https://github.com/angular/angular/pull/66126 is merged and released.
39+
(responseInit as { status: number }).status = 308;
40+
}
41+
3642
void inject(Router).navigate([], {
3743
queryParams: { filter: 'test' },
3844
});
@@ -318,7 +324,7 @@ describe('AngularServerApp', () => {
318324
it('returns a 302 status and redirects to the correct location when `router.navigate` is used', async () => {
319325
const response = await app.handle(new Request('http://localhost/redirect-via-navigate'));
320326
expect(response?.headers.get('location')).toBe('/redirect-via-navigate?filter=test');
321-
expect(response?.status).toBe(302);
327+
expect(response?.status).toBe(308);
322328
});
323329

324330
it('returns a 302 status and redirects to the correct location when `urlTree` is updated in a guard', async () => {

0 commit comments

Comments
 (0)