Angular 8 return 404 page

Returning a 404 page and setting the response status code is easy but comes with a catch: You need to run your application with Angular Universal.

Why is that?

A client side/single page application (the „normal“ app, without Angular Universal) is rendered once. The rest of the time the pages are rendered in the DOM. Which means all the magic is happening in the client browser – without any server requests (and therefore responses). The server side rendering part comes with Angular Universal.

What does that mean?

You have to take three steps to successfully show a 404 page: Two adjustments in you Angular App, one on your server side:
In your Angular app:

  • create a new component „NotFound“
  • Add a route to app.routing.ts: {path: ‚**‘, pathMatch: ‚full‘, component: NotfoundComponent}

In your NotFoundComponent, you just set the 404 statusCode (aside from a nice „Could not find your page“ text in the template):

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

    selector: 'app-notfound',
    templateUrl: './notfound.component.html',
    styleUrls: ['./notfound.component.css']
export class NotfoundComponent implements OnInit {

        @Optional() @Inject(RESPONSE) private response
    ) {}

    ngOnInit() {
        if (this.response) {
            this.response.statusCode = 404;

If you’d stop here you would already have a 404 page getting „served“ to your user. However it would still be served with a 200 http response. To finally serve a 404 status code you have to adjust your server.ts file as following:

import 'zone.js/dist/zone-node';

import compression from 'compression';
import * as express from 'express';
import { join } from 'path';

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

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');

app.engine('html', ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [

app.set('view engine', 'html');
app.set('views', DIST_FOLDER);

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(DIST_FOLDER, {
    maxAge: '1y'

// All regular routes use the Universal engine
app.get('*', (req, res) => {
            providers: [
                    provide: REQUEST, useValue: (req)
                    provide: RESPONSE, useValue: (res)
        (err, html) => {
            if (!!err) { throw err; }


// Start up the Node server
app.listen(PORT, () => {
    console.log(`Node Express server listening on http://localhost:${PORT}`);

If you now fire up your node server (eg with „npm run build:ssr“ and „npm run serve:ssr“) you can see your 404 page delivering a 404 status code while all other pages (after reloading) should serve a 200 response code.

Dezember 25th, 2019