How would I go about redirecting to 404 on routes with dynamic params in Angular 19 SSR?

Discussão em 'Angular' iniciado por user12288241, Fevereiro 4, 2025.

  1. user12288241

    user12288241 Guest

    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-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',
    },
    },
    ];


    usage of resolvedData:

    this.subscriptions.push(
    this.route.data.subscribe(({ resolvedData }) => {
    this.blogPost = resolvedData;

    this.init();
    }),
    );

    Continue reading...

Compartilhe esta Página