Command
serve, build
Is this a regression?
The previous version in which this bug was not present was
No response
Description
In our application, we need to issue 301 Moved Permanently redirects using a UrlTree inside a canActivate guard. However, we also need to attach custom Cache-Control headers to prevent the highly aggressive, permanent caching that browsers typically apply to 301s.
By default, returning a UrlTree triggers a 302 redirect. We successfully modified the status code to 301 by injecting RESPONSE_INIT and mutating the status:
responseInit.status = 301; // This works perfectly
However, when we attempt to add custom headers to the redirect response using the same RESPONSE_INIT object, they are completely ignored in the final response:
responseInit.headers = { 'X-Test-Header': 'Qwe12345' }; // This is ignored
I dug into the angular-cli source code and found the likely culprit in @angular/ssr.
In packages/angular/ssr/src/app.ts, when handling redirects, the status is correctly extracted from responseInit, but the headers are passed from the matchedRoute instead of picking up or merging the headers defined in responseInit.
Line 356 in app.ts (commit 010cef6):
return createRedirectResponse(result.redirectTo, responseInit.status, headers);
(Notice responseInit.headers is not used here).
In my opinion, if modifying responseInit.headers during a guard execution that returns a UrlTree, those headers should be merged into or applied to the resulting redirect response, just as responseInit.status is.
I am opening this to ask if this is an undocumented, intended behavior or a bug? If it is intended, are there plans to support modifying/adding redirect headers in the future? If it's a bug, I'd love to see it fixed so we can properly manage our cache headers on 301s. Thanks!
(I will provide minimal reproduction repository if necessary, but it's very easy to reproduce on a new empty project using the steps below)
Minimal Reproduction
- Set up an Angular SSR app with a child route.
- Add a
canActivate guard to the route that returns a UrlTree pointing back to home.
- In the guard, inject
RESPONSE_INIT.
- Mutate it:
responseInit.status = 301 and responseInit.headers = { 'X-Test-Header': 'Qwe12345' }.
- Run the SSR server and request the child route.
- Observe the network tab: The response is a
301, but X-Test-Header is missing.
Exception or Error
Your Environment
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI : 22.0.4
Angular : 22.0.2
Node.js : 24.18.0
Package Manager : npm 11.16.0
Operating System : darwin arm64
┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build │ 22.0.4 │ ^22.0.4 │
│ @angular/cli │ 22.0.4 │ ^22.0.4 │
│ @angular/common │ 22.0.2 │ ^22.0.0 │
│ @angular/compiler │ 22.0.2 │ ^22.0.0 │
│ @angular/compiler-cli │ 22.0.2 │ ^22.0.0 │
│ @angular/core │ 22.0.2 │ ^22.0.0 │
│ @angular/forms │ 22.0.2 │ ^22.0.0 │
│ @angular/platform-browser │ 22.0.2 │ ^22.0.0 │
│ @angular/platform-server │ 22.0.2 │ ^22.0.0 │
│ @angular/router │ 22.0.2 │ ^22.0.0 │
│ @angular/ssr │ 22.0.4 │ ^22.0.4 │
│ rxjs │ 7.8.2 │ ~7.8.0 │
│ typescript │ 6.0.3 │ ~6.0.2 │
│ vitest │ 4.1.9 │ ^4.0.8 │
└───────────────────────────┴───────────────────┴───────────────────┘
Anything else relevant?
No response
Command
serve, build
Is this a regression?
The previous version in which this bug was not present was
No response
Description
In our application, we need to issue
301 Moved Permanentlyredirects using aUrlTreeinside acanActivateguard. However, we also need to attach customCache-Controlheaders to prevent the highly aggressive, permanent caching that browsers typically apply to 301s.By default, returning a
UrlTreetriggers a302redirect. We successfully modified the status code to301by injectingRESPONSE_INITand mutating the status:responseInit.status = 301; // This works perfectlyHowever, when we attempt to add custom headers to the redirect response using the same
RESPONSE_INITobject, they are completely ignored in the final response:responseInit.headers = { 'X-Test-Header': 'Qwe12345' }; // This is ignoredI dug into the
angular-clisource code and found the likely culprit in@angular/ssr.In
packages/angular/ssr/src/app.ts, when handling redirects, the status is correctly extracted fromresponseInit, but the headers are passed from thematchedRouteinstead of picking up or merging the headers defined inresponseInit.Line 356 in app.ts (commit 010cef6):
return createRedirectResponse(result.redirectTo, responseInit.status, headers);(Notice
responseInit.headersis not used here).In my opinion, if modifying
responseInit.headersduring a guard execution that returns aUrlTree, those headers should be merged into or applied to the resulting redirect response, just asresponseInit.statusis.I am opening this to ask if this is an undocumented, intended behavior or a bug? If it is intended, are there plans to support modifying/adding redirect headers in the future? If it's a bug, I'd love to see it fixed so we can properly manage our cache headers on 301s. Thanks!
(I will provide minimal reproduction repository if necessary, but it's very easy to reproduce on a new empty project using the steps below)
Minimal Reproduction
canActivateguard to the route that returns aUrlTreepointing back to home.RESPONSE_INIT.responseInit.status = 301andresponseInit.headers = { 'X-Test-Header': 'Qwe12345' }.301, butX-Test-Headeris missing.Exception or Error
Your Environment
Anything else relevant?
No response