Next.js: writeHead(404) in getInitialProps does not return Next.js _error.js page

Created on 1 Dec 2017  路  6Comments  路  Source: vercel/next.js

Currently I am following this example on how to redirect users in getInitialProps

https://github.com/zeit/next.js/wiki/Redirecting-in-%60getInitialProps%60

The problem is, if I want to return 404 like this, it will return a blank page instead of the usual Next.js 404 error page.

context.res.writeHead(404)
context.res.end();

Expected Behavior

It should show _error.js page when writeHead 404

Current Behavior

Shows blank HTML document with 404 status

Steps to Reproduce (for bugs)

Put this inside your getInitialProps function

context.res.writeHead(404, {"Content-Type": "text/plain"})
context.res.end("Page not found");
context.res.finished = true

Context

I have auto redirections depending on what URL the user inputs. Therefore I need to be able to also return 404 page depending on specific logic.

Your Environment

Next.js latest
next-routes
Mac OS High Sierra
Chrome latest
node latest
npm latest

Most helpful comment

import React from 'react'
import ErrorPage from 'next/error'

export default Component => {
  return class WithError extends React.Component {
    static async getInitialProps(ctx) {
      const props =
        (Component.getInitialProps
          ? await Component.getInitialProps(ctx)
          : null) || {}

      if (props.statusCode && ctx.res) {
        ctx.res.statusCode = props.statusCode
      }

      return props
    }

    render() {
      if (this.props.statusCode) {
        return <ErrorPage statusCode={this.props.statusCode} />
      }

      return <Component {...this.props} />
    }
  }
}

To further extend my previous answer, I wouldn't recommend throwing, instead you can use above 404 HOC if you want to render the default error page.

Usage is:

import withErrorPage from '../lib/with-error-page.js'
class SomePage extends React.Component {
  static async getInitialProps() {
    return {
      statusCode: 404
    }
  }
  render() {
    return <div>What is rendered when there's no 404</div>
  }
}

export default withErrorPage(SomePage)

If no statusCode is provided the component render is executed. Otherwise the next/error page is rendered. If you have a custom _error.js you can import it instead of next/error.

Note that you could also provide this behavior on a global level using _app.js. In that case, you don't need a higher order component:

import App, {Container} from 'next/app'
import ErrorPage from 'next/error'
import React from 'react'

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }

    return {pageProps}
  }

  render () {
    const {Component, pageProps} = this.props
    if (pageProps.statusCode) {
      return <ErrorPage statusCode={this.props.statusCode} />
    }
    return <Container>
      <Component {...pageProps} />
    </Container>
  }
}

pageProps is the result of the page's getInitialProps

All 6 comments

No express allowed in this project FYI. I know sendStatus works in express.

See below comment instead: https://github.com/zeit/next.js/issues/3362#issuecomment-414932170

In theory you could do:

static async getInitialProps() {
    const err = new Error('')
    err.code = 'ENOENT'
    throw err
}

This would work for SSR on next@canary. With client side navigation you'd run into all sorts of issues. So I'd recommend you to implement the error page yourself using import ErrorPage from 'next/error'

import React from 'react'
import ErrorPage from 'next/error'

export default Component => {
  return class WithError extends React.Component {
    static async getInitialProps(ctx) {
      const props =
        (Component.getInitialProps
          ? await Component.getInitialProps(ctx)
          : null) || {}

      if (props.statusCode && ctx.res) {
        ctx.res.statusCode = props.statusCode
      }

      return props
    }

    render() {
      if (this.props.statusCode) {
        return <ErrorPage statusCode={this.props.statusCode} />
      }

      return <Component {...this.props} />
    }
  }
}

To further extend my previous answer, I wouldn't recommend throwing, instead you can use above 404 HOC if you want to render the default error page.

Usage is:

import withErrorPage from '../lib/with-error-page.js'
class SomePage extends React.Component {
  static async getInitialProps() {
    return {
      statusCode: 404
    }
  }
  render() {
    return <div>What is rendered when there's no 404</div>
  }
}

export default withErrorPage(SomePage)

If no statusCode is provided the component render is executed. Otherwise the next/error page is rendered. If you have a custom _error.js you can import it instead of next/error.

Note that you could also provide this behavior on a global level using _app.js. In that case, you don't need a higher order component:

import App, {Container} from 'next/app'
import ErrorPage from 'next/error'
import React from 'react'

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }

    return {pageProps}
  }

  render () {
    const {Component, pageProps} = this.props
    if (pageProps.statusCode) {
      return <ErrorPage statusCode={this.props.statusCode} />
    }
    return <Container>
      <Component {...pageProps} />
    </Container>
  }
}

pageProps is the result of the page's getInitialProps

import React from 'react'
import ErrorPage from 'next/error'

export default Component => {
  return class WithError extends React.Component {
    static async getInitialProps(ctx) {
      const props =
        (Component.getInitialProps
          ? await Component.getInitialProps(ctx)
          : null) || {}

      if (props.statusCode && ctx.res) {
        ctx.res.statusCode = props.statusCode
      }

      return props
    }

    render() {
      if (this.props.statusCode) {
        return <ErrorPage statusCode={this.props.statusCode} />
      }

      return <Component {...this.props} />
    }
  }
}

To further extend my previous answer, I wouldn't recommend throwing, instead you can use above 404 HOC if you want to render the default error page.

Usage is:

import withErrorPage from '../lib/with-error-page.js'
class SomePage extends React.Component {
  static async getInitialProps() {
    return {
      statusCode: 404
    }
  }
  render() {
    return <div>What is rendered when there's no 404</div>
  }
}

export default withErrorPage(SomePage)

If no statusCode is provided the component render is executed. Otherwise the next/error page is rendered. If you have a custom _error.js you can import it instead of next/error.

Note that you could also provide this behavior on a global level using _app.js. In that case, you don't need a higher order component:

import App, {Container} from 'next/app'
import ErrorPage from 'next/error'
import React from 'react'

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let pageProps = {}

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }

    return {pageProps}
  }

  render () {
    const {Component, pageProps} = this.props
    if (pageProps.statusCode) {
      return <ErrorPage statusCode={this.props.statusCode} />
    }
    return <Container>
      <Component {...pageProps} />
    </Container>
  }
}

pageProps is the result of the page's getInitialProps

Hi @timneutkens,
we actually run a website on Next.js backed by a custom Express.js server, and we implemented your proposed solution to handle errors at the global level.

But instead of returning a plain object we tried to throw an error of type HttpError (https://www.npmjs.com/package/http-errors) from our getInitialProps, handling it in _app.js with a try/catch block.

Doing so we incurred in a strange behavior where:

  1. The error page is rendered on the server with the right status code for a short period of time
  2. Next continue the rendering on the client of the original route, where the props are obviously empty, causing another error.

A simple example of our implementation (the same can be found on similar threads in Stack Overflow ie: here)

class SomePage extends React.Component {
  static async getInitialProps() {
    // Retrieve some data from an external service
    // Create and throw an error 
    const err = createError(404);
    throw err;
  }
  render() {
    return <div>What is rendered when there's no 404</div>
  }
}

Then, in _app.js:

import App, {Container} from 'next/app'
import ErrorPage from 'next/error'
import React from 'react'

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let error;
    let pageProps = {};

    try {
      pageProps = await Component.getInitialProps(ctx);
    } catch (err) {
      if (err.statusCode && ctx.res) {
        ctx.res.statusCode = err.statusCode;
      }

      error = err;
    }

    return { pageProps, error };
  }

  render () {
    const { Component, pageProps, error } = this.props;

    if (error && error instanceof Error) {
      return <ErrorPage statusCode={error.statusCode} />
    }

    return (
      <Container>
        <Component {...pageProps} />
      </Container>
    );
  }
}

Any idea on why this happens?

@gabrieledarrigo are you sure that client-side, your error is an instanceof Error? Keep in mind that data (which includes what will come from this.props) is serialized using JSON.stringify and then JSON.parse client-side. Because of that, I'm pretty sure your error is now just a plain object.

const str = JSON.stringify({error: new Error()})
const parsed = JSON.parse(str)
parsed.error instanceof Error // false

This only works up to version next 8.0.3
but in version 9 it gives an error
Error:
Function.getInitialProps
const err = new Error()

const err = new Error()
err.code = 'ENOENT'
throw err
Was this page helpful?
0 / 5 - 0 ratings

Related issues

havefive picture havefive  路  3Comments

ghost picture ghost  路  3Comments

lixiaoyan picture lixiaoyan  路  3Comments

swrdfish picture swrdfish  路  3Comments

olifante picture olifante  路  3Comments