Universal: Handle 404 and 500 errors

Created on 3 Mar 2017  路  13Comments  路  Source: angular/universal

Note: for support questions, please use one of these channels: https://github.com/angular/universal/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports. Also, Preboot has moved to https://github.com/angular/preboot - please make preboot-related issues there.

  • I'm submitting a ...
  • [ ] bug report
  • [x] feature request
  • [ ] support request => Please do not submit support request here, see note at the top of this template.

  • What modules are related to this Issue?

  • [ ] express-engine
  • [ ] grunt-prerender
  • [ ] gulp-prerender
  • [ ] hapi-engine
  • [ ] universal-next
  • [x] universal
  • [ ] webpack-prerender

  • Do you want to request a feature or report a bug?
    Request a feature

  • What is the current behavior?
    There's currently no way I can find to make sure the server responds with an HTTP status code 404 when I need to.

Furthermore, when there's an error the server currently refuses the connection.

  • What is the expected behavior?
    There should be a way to send responses with HTTP status 404.
    Additionally, there should be a way to handle errors and send responses with HTTP status 500.

  • What is the motivation / use case for changing the behavior?
    When application logic determines there should be a 404 error (for instance, having a route for { path: 'clients/:client-id' }, doing an API request and finding the client ID requested doesn't exist) you should respond with a 404 page. Currently, you can handle this in your application and _show_ a 404 page, but it'll be served with a 200 status code.

Additionally, there should be a way to handle any error and show the user a 500 error page, served with the correct status code.

Most helpful comment

And this is how you apply @CanKattwinkel鈥檚 howto on the latest v6 server.ts from the docs:

app.engine('html', (_, options, callback) =>
  ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
      provideModuleMap(LAZY_MODULE_MAP),
      {
        provide: REQUEST,
        useValue: options.req,
      },
      {
        provide: RESPONSE,
        useValue: options.req.res,
      },
    ],
  })(_, options, callback)
)

All 13 comments

I'm upvoting this one.
This is a serious issue for SEO, because soft-404 is not well handled by search engines.

Hi :). I have solved this in angular 4 using so called StatusCodeService

Here you can see my solution. https://github.com/kukjevov/ng-universal-demo/blob/mine/app/pages/notFound/notFound.component.ts

Just before i render content to string i retrieve status code from service and then send it together with html and statusCode and process it in https://github.com/kukjevov/ng-universal-demo/blob/mine/server.js

It is working. Maybe not best solution but working one :). But I am not using ngExpressEngine, but rendering result directly by myselft.

Thank you @kukjevov that's one of the best ways to handle it that I'm aware of!

I'm sorry to revisit an old issue, but I still quite haven't figured this one out. I can't seem to find the definition for StatusCodeService and I'm having a hard time trying to figure out how I should implement my own solution for this.

As far as I understand this, there should be a way to manipulate the status code inside the ngExpressEngine, but I haven't figured out how that should be done.

Hi i try to explain myself one more time :).

To make it working you must implement following:

StatusCodeService

import {Injectable} from '@angular/core';

/**
 * Service used for transfering http status code for response
 */
@Injectable()
export class StatusCodeService
{
    //######################### private fields #########################

    /**
     * Current status code
     */
    private _statusCode?: number;

    //######################### public properties #########################

    /**
     * Gets current status code
     */
    public get statusCode(): number | null | undefined
    {
        return this._statusCode;
    }

    //######################### public methods #########################

    /**
     * Sets current status code
     * @param {number} code Status code value that will be set
     */
    public setStatusCode(code?: number)
    {
        this._statusCode = code;
    }
}

You need to create your own method for rendering to string see https://github.com/angular/angular/blob/master/packages/platform-server/src/utils.ts

modify function at line 37

function _render<T>(
    platform: PlatformRef, moduleRefPromise: Promise<NgModuleRef<T>>): Promise<{html: string, statusCode?: number}> {
  return moduleRefPromise.then((moduleRef) => {
    const transitionId = moduleRef.injector.get(傻TRANSITION_ID, null);
    if (!transitionId) {
      throw new Error(
          `renderModule[Factory]() requires the use of BrowserModule.withServerTransition() to ensure
the server-rendered app can be properly bootstrapped into a client app.`);
    }
    const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
    return toPromise
        .call(first.call(filter.call(applicationRef.isStable, (isStable: boolean) => isStable)))
        .then(() => {
          let statusCodeService = moduleRef.injector.get(StatusCodeService);
          let statusCode: number | null = null;

          if(statusCodeService)
          {
              statusCode = statusCodeService.statusCode;
          }

          const output = {html: platform.injector.get(PlatformState).renderToString(), statusCode};
          platform.destroy();
          return output;
        });
  });
}

That means that method used for rendering to string returns object which contains html as string and status code as number. You will use this in your server, does not matter what kind of server (nodejs, .net) to set response status code and content.

For example if you use Connect nodejs server.

            res.setHeader('Content-Type', 'text/html');

            if(succ.statusCode)
            {
                res.statusCode = succ.statusCode;
            }

            res.end(err || succ.html);

Where succ is returned object with html and statusCode properties. res is nodejs response object and err is error in case that there is any.

Then when you use StatusCodeService to set status code during server side rendering (somewhere in your component or service or anywhere), last set status will be returned to your browser.

I hope this helps.

@kukjevov after a long time I finally got this to work. Thank you so much! you are a gentleman and a scholar.

A very simple solution is to provide response object, under some injection token (e.g. angular universal's HTTP_RESPONSE), by passing extraProviders option to renderModuleFactory() and set status in your app whenever required by injecting response and calling response.status(status).

@alirezamirian that is the best solution so far. Since I just stumbled upon this problem myself, I recorded my procedure in a blog post:

https://blog.thecodecampus.de/angular-universal-handle-404-set-status-codes/

And this is how you apply @CanKattwinkel鈥檚 howto on the latest v6 server.ts from the docs:

app.engine('html', (_, options, callback) =>
  ngExpressEngine({
    bootstrap: AppServerModuleNgFactory,
    providers: [
      provideModuleMap(LAZY_MODULE_MAP),
      {
        provide: REQUEST,
        useValue: options.req,
      },
      {
        provide: RESPONSE,
        useValue: options.req.res,
      },
    ],
  })(_, options, callback)
)

@msklvsk Thanks for saving my evening.

When I send the 404 status, my express server just shows a Not Found text in the document, does anyone have managed to send the 404 status and still render the Angular NotFoundComponent?

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

k-schneider picture k-schneider  路  4Comments

ahmedwerpx picture ahmedwerpx  路  4Comments

leon picture leon  路  4Comments

jeffwhelpley picture jeffwhelpley  路  6Comments

PatrickJS picture PatrickJS  路  5Comments