Angular 19 SSR中带动态参数路由的404重定向问题
Angular 19 SSR下博客404重定向问题与SEO优化方案
问题背景
我有一个blog/:slug路由,博客文章数据通过resolver获取。当GET请求返回404状态时,用户应被重定向到/404页面。但目前存在两个核心问题:
- 页面闪烁问题:当resolver返回
RedirectCommand时,服务器会先在blog/:slug路径下渲染404页面并返回,之后再重定向到/404路径,再次渲染404页面,导致页面闪烁。该问题仅在直接修改URL路由时出现,应用内路由跳转则正常。 - SEO不友好问题:/404页面本身的状态码是404,但通过
RedirectCommand重定向时状态码变为200,爬虫会识别为软404,影响SEO。
需要实现:不先渲染博客文章页面就直接重定向到/404页面,同时保证爬虫能识别到正确的404状态码。
环境与代码
依赖版本(package.json)
"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-view.component').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-404.component').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', }, }, ];
组件中使用resolvedData
this.subscriptions.push( this.route.data.subscribe(({ resolvedData }) => { this.blogPost = resolvedData; this.init(); }), );
解决方案
1. 解决页面闪烁:替换重定向为抛出NotFoundError
问题根源是RedirectCommand会触发当前路由的服务器渲染后再发送重定向指令。要避免这个问题,直接在resolver中抛出NotFoundError,让Angular SSR直接渲染404页面并返回正确状态码,无需重定向。
修改blog.resolver.ts:
import { NotFoundError } from '@angular/router'; export const blogPostResolver: ResolveFn<BlogPost> = async (route: ActivatedRouteSnapshot) => { 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); if (response.status === 200) { return response.data; } else { // 抛出NotFoundError,触发Angular渲染404路由 throw new NotFoundError('Blog post not found'); } };
2. 确保SEO友好:配置路由错误处理器
在路由配置中添加errorHandler,让Angular捕获NotFoundError时直接渲染/404页面,并保留404状态码。
修改app.routes.ts及app.config.ts中的路由配置:
import { Routes, withRouterConfig } from '@angular/router'; import { NotFoundError } from '@angular/router'; // 新增错误处理函数 const errorHandler = (error: unknown) => { if (error instanceof NotFoundError) { return { component: () => import('./components/not-found-404/not-found-404.component').then(m => m.NotFound404Component), title: 'Page Not Found | Apexara', status: 404, }; } throw error; }; // 更新provideRouter配置 export const appConfig: ApplicationConfig = { providers: [ // ...其他providers provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }), withComponentInputBinding(), withRouterConfig({ onSameUrlNavigation: 'reload', errorHandler }) // 注入错误处理器 ), // ...其他providers ], };
3. 验证效果
- 直接访问不存在的
blog/xxx时,服务器会直接返回404状态码和404页面,不会先渲染博客页面再重定向,彻底解决页面闪烁问题。 - 爬虫抓取时会识别到正确的404状态码,避免软404影响SEO。
- 应用内路由跳转时,Angular会正常处理错误并导航到404页面,保持原有交互逻辑。
内容的提问来源于stack exchange,提问作者user12288241
相关产品推荐
相关产品推荐

