Next.js: [Suggestion] ability for getInitialProps to return a 404 error

Created on 31 Aug 2017  路  12Comments  路  Source: vercel/next.js


I have a project at the moment that uses nextjs embedded in an express server. I have a page called event.js and I route /event/:eventId to /event?eventId={id}.

My issue now is that if the user goes to /event without specifying an ID my page component is rendered anyway.

My suggestion is: Allow getInitialProps function to return null (or false or 404 or new Error("")), which will result in next intercepting this and rendering the 404 page.

  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Current Setup

expressApp.get('/event/:eventId', (req, res) => {
  const mapToUrl = '/event'
  const query = {
    ...req.query,
    eventId: req.params.eventId
  }
  nextApp.render(req, res, mapToUrl, query)
})

expressApp.get('*', nextHandler)

Most helpful comment

@rovansteen if you are following Tim's example, you should set the ctx.res.statusCode to 404 on the Component you are passing to the HOC

import ErrorPage from 'next/error'

export withError = (Component) => class extends React.Component {
    static async getInitialProps(ctx) {
        const props = await Component.getInitialProps(ctx)
        const {statusCode} = ctx.res
        return {statusCode, ...props}
    }

    render() {
        const {statusCode} = this.props
        if(statusCode && statusCode !== 200) {
            return <ErrorPage statusCode={statusCode} />
        }
        return <Component {...this.props} />
    }
}

---

import withError from './withError'

class Example extends React.Component {
    static async getInitialProps(ctx) {
        if (error) { // define your app error logic here
          ctx.res.statusCode = 404;
        }

        return { /* ... */ }
    }

    render() {
        /* ... */
    }
}

export default withError(Example);

All 12 comments

You can do something like this:

import React from 'react'
// Import some custom error page
import Error404 from './Error404'

export default class MyPage extends React.Component {
  static async getInitialProps ({ res, query }) {
    // Make your validations
    const statusCode = !query.eventId ? 404 : 200;

    // Change the server response code if needed
    if(statusCode !== 200) {
       res.statusCode = statusCode;
     }
     return { statusCode };
  }

  render () {
    const { statusCode } = this.props;

    if (statusCode === 404) {
      return <Error404 />
    }

    return (
      <div>
        Your component code
      </div>
    )
  }
}

@coluccini thanks for the response.
I thought of doing something like this, but it doesn't seem like the most optimal solution. You would need to replicate these same checks on most of your "dynamic" routes and also, by importing Error404 you're bundling more JS code than you need for your page, even if it's not required.

I also thought of using a HOC to achieve a similar behaviour, but I still thought it could be beneficial to bake it directly in Next.

The custom error component is optional. You can just set the res.statusCode to 404 and next will render the default error component (if you wish to customize it, create a _error.js under /pages. There's an example on the docs)

@coluccini solution is the right one. Though instead of Error404 you can just use _error or next/error if you don't use a custom 404. Now as to easily including. You can create a hoc similar to this:

import ErrorPage from 'next/error'

export default (Component) => class extends React.Component {
    static async getInitialProps(ctx) {
        const props = await Component.getInitialProps(ctx)
        const {statusCode} = ctx.res
        return {statusCode, ...props}
    }

    render() {
        const {statusCode} = this.props
        if(statusCode && statusCode !== 200) {
            return <ErrorPage statusCode={statusCode} />
        }
        return <Component {...this.props} />
    }
}

Am I right that this does not actually return a 404 status code? Just a 200 that looks like a 404?

@rovansteen if you are following Tim's example, you should set the ctx.res.statusCode to 404 on the Component you are passing to the HOC

import ErrorPage from 'next/error'

export withError = (Component) => class extends React.Component {
    static async getInitialProps(ctx) {
        const props = await Component.getInitialProps(ctx)
        const {statusCode} = ctx.res
        return {statusCode, ...props}
    }

    render() {
        const {statusCode} = this.props
        if(statusCode && statusCode !== 200) {
            return <ErrorPage statusCode={statusCode} />
        }
        return <Component {...this.props} />
    }
}

---

import withError from './withError'

class Example extends React.Component {
    static async getInitialProps(ctx) {
        if (error) { // define your app error logic here
          ctx.res.statusCode = 404;
        }

        return { /* ... */ }
    }

    render() {
        /* ... */
    }
}

export default withError(Example);

@timneutkens Can you answer @rovansteen question. I'm facing the problem that your solution render a view of 404 with 200 status in header.

@Everettss is my answer doesn't work for you? you need to set ctx.res.statusCode if you want to have an different http response code

@coluccini Oh god! Sorry for the mix-up. I've tried your example inaccurately. :sweat:
Your example work like a charm :heart:

@coluccini I have used your answer. When entering the url manually in the address bar, everything works fine, however when navigating using next/link, it shows the following error:

TypeError: Cannot read property 'statusCode' of undefined

Here is my index.js code:

import React, { Component } from "react";
import Link from "next/link";
import PostLink from "../components/PostLink";
import { connect } from "react-redux";
import { itemsFetchData } from "../actions/items";
import withError from "../lib/withError";

class Index extends Component {
  static async getInitialProps({ reduxStore, query, res }) {
    let pageNum = 1;
    var statusCode = 200;
    if (query.pageNum) {
      if (/^([1-9][0-9]*)$/.test(query.pageNum)) {
        pageNum = parseInt(query.pageNum, 10);
      } else {
        statusCode = 404;
      }
    }
    if (statusCode == 200) {

    }
    res.statusCode = statusCode;
    return {};
  }

  render() {
    return (
      <div>
        <p>React Component! Index {this.props.items.length}</p>
        <div className="row justify-content-center">
              <PostLink id="hello-nextjs" title="Hello Next.js" />
              <PostLink id="learn-nextjs" title="Learn Next.js is awesome" />
              <PostLink id="deploy-nextjs" title="Deploy apps with Zeit" />
        </div>
      </div>
    );
  }
}
const mapStateToProps = state => {
  return {
    status: state.tagsReducer.tagsData.status,
    items: state.tagsReducer.tagsData.items,
    hasError: state.tagsReducer.tagsHasError,
    isLoading: state.tagsReducer.tagsIsLoading
  };
};
export default withError(connect(mapStateToProps)(Index));

My withError component is exactly the same as Tim's code.

UPDATE: If the page is being rendered on the server, ctx.res is defined, however if it is rendered on the client side, it is undefined! What should we do?

@hi-rad this is the normal behaviour of getInitialProps.
req & res are coming from the http request so from the server.

You can use the shallow routing to route without running getInitialProps

For posterity, here's what I landed on:

import React from 'react';
import PropTypes from 'prop-types';
import ErrorPage from '../../pages/_error';

const withError = Component => {
  class WithErrorComponent extends React.Component {
    static async getInitialProps(ctx) {
      const props = await Component.getInitialProps(ctx);
      const { statusCode } = ctx.res || {};
      return { statusCode, ...props };
    }

    render() {
      const { statusCode } = this.props;
      if (statusCode && statusCode !== 200) {
        return <ErrorPage statusCode={statusCode} />;
      }
      return <Component {...this.props} />;
    }
  }

  WithErrorComponent.propTypes = {
    statusCode: PropTypes.number,
  };

  WithErrorComponent.defaultProps = {
    statusCode: null,
  };

  return WithErrorComponent;
};

export default withError;
Was this page helpful?
0 / 5 - 0 ratings