I have a route like blog/:slug
. The blog post data is fetched through a resolver. When I receive status 404 from the GET request the user should get redirected to the /404 page. The problem is that when the resolver returns a RedirectCommand
the server renders the 404 page and returns it on the blog/:slug
path and then it redirects to the /404 path it renders the 404 page html again and the page flickers. This problem appears only when I route by changing the url. It works fine when I route through the app. Is it possible to redirect to the /404 page without initially rendering the blog post page? The page status of the /404 page is 404, but it is 200 when redirect by RedirectCommand
. Is there an SEO friendly workaround/way to show the crawler page is 404 without it being a soft 404?
I am using Angular 19
package.json dependencies
"dependencies": {
"@angular/animations": "19.1.3",
"@angular/common": "19.1.3",
"@angular/compiler": "19.1.3",
"@angular/core": "19.1.3",
"@angular/forms": "19.1.3",
"@angular/platform-browser": "19.1.3",
"@angular/platform-browser-dynamic": "19.1.3",
"@angular/platform-server": "19.1.3",
"@angular/router": "19.1.3",
"@angular/ssr": "19.1.4",
"express": "4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "19.1.4",
"@angular/cli": "19.1.4",
"@angular/compiler-cli": "19.1.3",
"@types/express": "4.17.17",
"@types/node": "18.18.0",
"typescript": "~5.5.2"
}
server.ts
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
app.use(
express.static(browserDistFolder, {
maxAge: '1y',
index: false,
redirect: false,
}),
);
app.use('/**', (req, res, next) => {
angularApp
.handle(req)
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
export const reqHandler = createNodeRequestHandler(app);
app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
provideHttpClient(withFetch(), withInterceptors([ authInterceptor ])),
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }), withComponentInputBinding(), withRouterConfig({ onSameUrlNavigation: 'reload' })),
provideClientHydration(withIncrementalHydration(), withHttpTransferCacheOptions({ includeRequestsWithAuthHeaders: true, includeHeaders: ['Authorization-Refresh', 'Authorization-Access'] })),
],
};
app.config.server.ts
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRoutesConfig(serverRoutes)
]
};
blog.resolver.ts
export const blogPostResolver: ResolveFn<BlogPost> = async (route: ActivatedRouteSnapshot) => {
const router = inject(Router);
const userService = inject(UserService);
const requestsBlogService = inject(RequestsBlogService);
const slug = route.paramMap.get('slug')!;
const request = userService.isUserLogged()
? requestsBlogService.getBlogPostAsAdmin(slug)
: requestsBlogService.getBlogPost(slug);
const response = await lastValueFrom(request);
switch(response.status) {
case 200:
return response.data;
default:
return new RedirectCommand(router.parseUrl('/404'));
}
};
app.routes.ts
export const routes: Routes = [
{
path: 'blog/:slug',
loadComponent: () => import('./components/blog/post/single-view/blog-post-single-viewponent').then(m => m.BlogPostSingleViewComponent),
title: 'Article | Apexara',
resolve: { resolvedData: blogPostResolver },
runGuardsAndResolvers: 'paramsChange',
data: { pageName: 'Article' },
},
{
path: '404',
loadComponent: () => import('./components/not-found-404/not-found-404ponent').then(m => m.NotFound404Component),
title: 'Page Not Found | Apexara',
data: { pageName: 'Not Found' },
},
{
path: '**',
redirectTo: '404',
},
]
app.routes.server.ts
export const serverRoutes: ServerRoute[] = [
{
path: 'blog/:slug',
renderMode: RenderMode.Server,
},
{
path: '404',
renderMode: RenderMode.Server,
status: 404,
headers: {
'Cache-Control': 'no-cache',
},
},
];
usage of resolvedData:
this.subscriptions.push(
this.route.data.subscribe(({ resolvedData }) => {
this.blogPost = resolvedData;
this.init();
}),
);
I have a route like blog/:slug
. The blog post data is fetched through a resolver. When I receive status 404 from the GET request the user should get redirected to the /404 page. The problem is that when the resolver returns a RedirectCommand
the server renders the 404 page and returns it on the blog/:slug
path and then it redirects to the /404 path it renders the 404 page html again and the page flickers. This problem appears only when I route by changing the url. It works fine when I route through the app. Is it possible to redirect to the /404 page without initially rendering the blog post page? The page status of the /404 page is 404, but it is 200 when redirect by RedirectCommand
. Is there an SEO friendly workaround/way to show the crawler page is 404 without it being a soft 404?
I am using Angular 19
package.json dependencies
"dependencies": {
"@angular/animations": "19.1.3",
"@angular/common": "19.1.3",
"@angular/compiler": "19.1.3",
"@angular/core": "19.1.3",
"@angular/forms": "19.1.3",
"@angular/platform-browser": "19.1.3",
"@angular/platform-browser-dynamic": "19.1.3",
"@angular/platform-server": "19.1.3",
"@angular/router": "19.1.3",
"@angular/ssr": "19.1.4",
"express": "4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular/build": "19.1.4",
"@angular/cli": "19.1.4",
"@angular/compiler-cli": "19.1.3",
"@types/express": "4.17.17",
"@types/node": "18.18.0",
"typescript": "~5.5.2"
}
server.ts
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
app.use(
express.static(browserDistFolder, {
maxAge: '1y',
index: false,
redirect: false,
}),
);
app.use('/**', (req, res, next) => {
angularApp
.handle(req)
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
export const reqHandler = createNodeRequestHandler(app);
app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
provideHttpClient(withFetch(), withInterceptors([ authInterceptor ])),
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }), withComponentInputBinding(), withRouterConfig({ onSameUrlNavigation: 'reload' })),
provideClientHydration(withIncrementalHydration(), withHttpTransferCacheOptions({ includeRequestsWithAuthHeaders: true, includeHeaders: ['Authorization-Refresh', 'Authorization-Access'] })),
],
};
app.config.server.ts
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRoutesConfig(serverRoutes)
]
};
blog.resolver.ts
export const blogPostResolver: ResolveFn<BlogPost> = async (route: ActivatedRouteSnapshot) => {
const router = inject(Router);
const userService = inject(UserService);
const requestsBlogService = inject(RequestsBlogService);
const slug = route.paramMap.get('slug')!;
const request = userService.isUserLogged()
? requestsBlogService.getBlogPostAsAdmin(slug)
: requestsBlogService.getBlogPost(slug);
const response = await lastValueFrom(request);
switch(response.status) {
case 200:
return response.data;
default:
return new RedirectCommand(router.parseUrl('/404'));
}
};
app.routes.ts
export const routes: Routes = [
{
path: 'blog/:slug',
loadComponent: () => import('./components/blog/post/single-view/blog-post-single-viewponent').then(m => m.BlogPostSingleViewComponent),
title: 'Article | Apexara',
resolve: { resolvedData: blogPostResolver },
runGuardsAndResolvers: 'paramsChange',
data: { pageName: 'Article' },
},
{
path: '404',
loadComponent: () => import('./components/not-found-404/not-found-404ponent').then(m => m.NotFound404Component),
title: 'Page Not Found | Apexara',
data: { pageName: 'Not Found' },
},
{
path: '**',
redirectTo: '404',
},
]
app.routes.server.ts
export const serverRoutes: ServerRoute[] = [
{
path: 'blog/:slug',
renderMode: RenderMode.Server,
},
{
path: '404',
renderMode: RenderMode.Server,
status: 404,
headers: {
'Cache-Control': 'no-cache',
},
},
];
usage of resolvedData:
this.subscriptions.push(
this.route.data.subscribe(({ resolvedData }) => {
this.blogPost = resolvedData;
this.init();
}),
);
Share
Improve this question
asked Feb 4 at 9:40
user12288241user12288241
111 bronze badge
1 Answer
Reset to default 0try this:
export const blogPostResolver: ResolveFn<BlogPost> = async (route: ActivatedRouteSnapshot) => {
const router = inject(Router);
const userService = inject(UserService);
const requestsBlogService = inject(RequestsBlogService);
const slug = route.paramMap.get('slug')!;
const request = userService.isUserLogged()
? requestsBlogService.getBlogPostAsAdmin(slug)
: requestsBlogService.getBlogPost(slug);
try {
const response = await lastValueFrom(request);
if (response.status === 200) {
return response.data;
}
throw new Error('Not found');
} catch (error) {
router.navigate(['/404'], { skipLocationChange: false });
return EMPTY;
}
};