2

I struggle to settle a 404 page that returns to navigators/crawlers a specific HTTP status code : 404. Following SSR guide in https://angular.io/guide/universal, my application is correctly served with SSR.

I set a 'PageNotfound' route in my app-routing.module.ts :

{path: '*', component: NotFoundComponent}

But obviously it still returns 200 as http return statuses. How I can change http return code for this specific route ?

3 Answers 3

10

For me the following snippet worked (Angular 8.2.11). No need to change the default server.ts file.

import { Component, OnInit, Inject, PLATFORM_ID, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { isPlatformServer } from '@angular/common';
import { Request } from 'express';

@Component({
  selector: 'app-error-not-found',
  templateUrl: './error-not-found.component.html',
  styleUrls: ['./error-not-found.component.scss']
})
export class ErrorNotFoundComponent implements OnInit {

  constructor(
    @Inject(PLATFORM_ID) private platformId: any,
    @Optional() @Inject(REQUEST) private request: Request
  ) { }

  ngOnInit() {
    if (isPlatformServer(this.platformId)) {
      if (this.request.res) {
        this.request.res.status(404);
      }
    }
  }

}

And offcourse we need our routes to be something like this:

export const routes: Routes = [
  ...
  { path: '404', component: ErrorNotFoundComponent },
  { path: '**', redirectTo: '/404' }
];
Sign up to request clarification or add additional context in comments.

4 Comments

This would import express into the frontend build.
It's just importing a typing, Request, from Express, not the library itself. It won't be included in the build.
works in Angular 9, thanks. Didn't work in versions before...
this works fine when I run it with dev:ssr locally, but when I upload on AWS Lambda with vendia solution, it still returns a 200
1

Outdated. Since angular 9 the issue is resolved. Use for <=8 only

Although it is quite well-documented here https://github.com/angular/universal/tree/master/modules/express-engine the proposed token does not work.

It is simply undefined in SSR (in the client it is easy to mock it).

However there is a (very) dirty solution.

Just put some marker on your 404 page (change whatevervaluehere to something unique and meaningless):

<!-- indicator for SSR to return 404 status code -->
<!-- MUST BE LEFT HERE -->
<input type="hidden" value="whatevervaluehere">

and in the server.ts

app.get('*', (req, res) => {
  res.render('index', {
    req,
    res,
  }, (err: Error, html: string) => {
    res.status(html && html.includes('whatevervaluehere') ? 404 : html ? 200 : 500).send(html || err.message);
  });
});

Again, very, very dirty. However, works.

3 Comments

yeah dirty, but the only thing that worked for me as well :)
is this only work for less than Angular 9.?
Please help me out from this issue. stackoverflow.com/questions/70926063/…
0

add this to the component NotFoundComponent

import {Component, Inject, OnInit, Optional, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
import {RESPONSE} from '@nguniversal/express-engine/tokens';
import {Response} from 'express';

@Component({
  selector: 'app-not-found',
  templateUrl: './not-found.component.html',
  styleUrls: ['./not-found.component.scss']
})
export class NotFoundComponent implements OnInit {

  constructor(@Inject(PLATFORM_ID) private platformId: Object,
              @Optional() @Inject(RESPONSE) private response: Response) {
  }


  ngOnInit() {
    if (!isPlatformBrowser(this.platformId)) {
      this.response.status(404);
    }
  }
}

modify server.js file to this

import {REQUEST, RESPONSE} from '@nguniversal/express-engine/tokens';
import {ValueProvider} from '@angular/core';


app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    // Our index.html
    document: template,
    url: options.req.url,
    extraProviders: [
      // make req and response accessible when angular app runs on server
      <ValueProvider>{
        provide: REQUEST,
        useValue: options.req
      },
      <ValueProvider>{
        provide: RESPONSE,
        useValue: options.req.res,
      },
    ]
  }).then(html => {
    callback(null, html);
  });
});

5 Comments

Hi Exterminator, thanks a lot for your response. Unfortunately, when I set injection of "response" field in constructor, compilation fails : ERROR in ./node_modules/cookie-signature/index.js Module not found: Error: Can't resolve 'crypto' in '/home/germain.sigety/workspace-typescript/ng-pas/node_modules/cookie-signature' ERROR in ./node_modules/etag/index.js Module not found: Error: Can't resolve 'crypto' in '/home/germain.sigety/workspace-typescript/ng-pas/node_modules/etag' ERROR in ./node_modules/destroy/index.js... Have you got an idea ?
Thanks again for your time Unfortunately, I have no server.js but a server.ts (typescript). My app.engine function call is very different : app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); Does your solution work with Angular 6 ? If I replace my method by yours, it does not compile.
any progress on this issue?
@dcballs the only way I found is stackoverflow.com/a/57677168/1990451 ...
Please help me out for angular 9 404 status code - stackoverflow.com/questions/70926063/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.