最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

server side rendering - How would I go about redirecting to 404 on routes with dynamic params in Angular 19 SSR? - Stack Overflo

programmeradmin0浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default 0

try 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;
  }
};

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论
ok 不同模板 switch ($forum['model']) { /*case '0': include _include(APP_PATH . 'view/htm/read.htm'); break;*/ default: include _include(theme_load('read', $fid)); break; } } break; case '10': // 主题外链 / thread external link http_location(htmlspecialchars_decode(trim($thread['description']))); break; case '11': // 单页 / single page $attachlist = array(); $imagelist = array(); $thread['filelist'] = array(); $threadlist = NULL; $thread['files'] > 0 and list($attachlist, $imagelist, $thread['filelist']) = well_attach_find_by_tid($tid); $data = data_read_cache($tid); empty($data) and message(-1, lang('data_malformation')); $tidlist = $forum['threads'] ? page_find_by_fid($fid, $page, $pagesize) : NULL; if ($tidlist) { $tidarr = arrlist_values($tidlist, 'tid'); $threadlist = well_thread_find($tidarr, $pagesize); // 按之前tidlist排序 $threadlist = array2_sort_key($threadlist, $tidlist, 'tid'); } $allowpost = forum_access_user($fid, $gid, 'allowpost'); $allowupdate = forum_access_mod($fid, $gid, 'allowupdate'); $allowdelete = forum_access_mod($fid, $gid, 'allowdelete'); $access = array('allowpost' => $allowpost, 'allowupdate' => $allowupdate, 'allowdelete' => $allowdelete); $header['title'] = $thread['subject']; $header['mobile_link'] = $thread['url']; $header['keywords'] = $thread['keyword'] ? $thread['keyword'] : $thread['subject']; $header['description'] = $thread['description'] ? $thread['description'] : $thread['brief']; $_SESSION['fid'] = $fid; if ($ajax) { empty($conf['api_on']) and message(0, lang('closed')); $apilist['header'] = $header; $apilist['extra'] = $extra; $apilist['access'] = $access; $apilist['thread'] = well_thread_safe_info($thread); $apilist['thread_data'] = $data; $apilist['forum'] = $forum; $apilist['imagelist'] = $imagelist; $apilist['filelist'] = $thread['filelist']; $apilist['threadlist'] = $threadlist; message(0, $apilist); } else { include _include(theme_load('single_page', $fid)); } break; default: message(-1, lang('data_malformation')); break; } ?>