How to let react router respond with 404 status code?

42,185

Solution 1

With react-router 2.0.0 you can do:

<Route path="*" component={NoMatch} status={404}/>

EDIT:

What you would need to do, is to create a custom attribute on your route definition, like you can see above (status).

When you are about rendering you component on server side, check on this attribute and send a response with a the code of this attribute:

routes.js

import React from 'react';
import {IndexRoute, Route} from 'react-router';

import {
    App,
    Home,
    Post,
    NotFound,
} from 'containerComponents';

export default () => {
    return (
    <Route path="/" component={App}>

        <IndexRoute component={Home} />
        <Route path='post/' component={Post} />

        { /* Catch all route */ }
        <Route path="*" component={NotFound} status={404} />

    </Route>
  );
};

server.js:

import { match } from 'react-router';
import getRoutes from './routes';
....
app.use((req, res) => {
match({ history, routes: getRoutes(), location: req.originalUrl }, (error, 
        redirectLocation, renderProps) => {
        if (redirectLocation) {
          res.redirect(redirectLocation.pathname + redirectLocation.search);
        } else if (error) {
          console.error('ROUTER ERROR:', error);
          res.status(500);
        } else if (renderProps) {

            const is404 = renderProps.routes.find(r => r.status === 404) !== undefined;
        }
        if (is404) {
          res.status(404).send('Not found');
        } else {
            //Go on and render the freaking component...
        }
    });
});

Sorry about that... certainly my solution wasn't working by itself, and I missed the proper piece of code where you actually check on this attribute and render accordingly.

As you can see, I just send the 'Not found' text to the client, however, it would be best if you catch the component to render from renderProps (NoMatch in this case) and render it.

Cheers

Solution 2

I did some digging, this is how we do things in the v4 release.

<Route
  render={({ staticContext }) => {
    if (staticContext) {
      staticContext.statusCode = 404
    }
    return <NotFound />
  }}
/>

The Route component is used to access the staticContext which has a statusCode prop that you can set if you use the StaticRouter component to render on the server.

Server code looks something like this (with some TypeScript typing)

const currentLocation: Location = {
  pathname: req.pathname,
  search: req.search,
  hash: '',
  state: undefined
}

const staticRouterContext: StaticContext & StaticRouterContext = {}

ReactDOMServer.renderToString(
  <StaticRouter location={currentLocation} context={staticRouterContext}>
  ...
  </StaticRouter>
)

if (staticRouterContext.statusCode) {
  res.status(staticRouterContext.statusCode)
}

Note: I think the typings are a bit wonky because the StaticRouterContext doesn't have the statusCode prop from the StaticContext interface. Probably a mistake in the typings. This works just fine though.

Solution 3

Zen: What is the error code of a silent network?

I puzzled on this question as well. The odd case, you don't change the HTTP error code. We think we should, because a random URL was called, but why?

In a SPA (Single Page Application), there isn't network traffic but just switching of panes. The HTTP error code is in the headers for serving a new page, while all the content is in a named <div> that doesn't reload a new page. One would need to toss out page, transmit and render a new page to have the 404 return code, and then send the original page again when the user moves away.

And, its only the user. The 404 error code, or any other HTTP code, is really a hint to the browser or service. If the requester is a browser, the human is given a better indication than the browser could provide. If the requestor is a crawler, it probably scans for 404 in <h1>'s. If the requester is using your website as an API, why is that a good thing?

So the answer is that we set the HTTP status codes out of habit. Don't.

Solution 4

To do this, you need to run the match on the server as well, to see if a route matches. Of course, if you're going to do this, you may well do full-fledged server-side rendering as well! Consult the server rendering guide.

Share:
42,185
Liang
Author by

Liang

Updated on July 09, 2022

Comments

  • Liang
    Liang almost 2 years

    I'm using react router as root and all requests under "/" are directed to react router. And when react router found that the url is not matched with any of the defined components, it renders with NoMatch component. And here goes the problem, NoMatch is rendered and that's what I want, but the status code is still 200 instead of 404. And when my css or js files are placed with a wrong url react router does the same thing, it responds with 200! And then the page tells me that there's some problem with my resources content type!

    So, how can I use react router to handle everything in the "/" and still get it to treat 404 errors right(to respond with 404 status code)?

    code in react router

    render((
      <Router history={browserHistory}>
        <Route path="/" component={App}>
          <IndexRoute component={Index}/>
          <Route path="archived" component={App}>
            <IndexRoute component={ArchivedPage}/>
            <Route path="project/:projectId" component={ArchivedDetailPage}/>
          </Route>
          <Route path="*" component={NoMatch}/>
        </Route>
      </Router>
    ), document.getElementById('app'));
    

    the servre side

      router.use('/', function(req, res, next) {
        res.render('index-react', {
          title: 'some title'
        });
      });
    
  • Philiiiiiipp
    Philiiiiiipp about 7 years
    I do love the irony of this link going to a 404 page :-)
  • eselk
    eselk about 7 years
  • Marco Lazzeri
    Marco Lazzeri about 6 years
    What's the status parameter for? I cannot find any reference to it in the docs.
  • rmartrenado
    rmartrenado about 6 years
    I never found it on the docs either.. I guess it is the uncompleted "Advanced Usage" part of the 2.x docs. But it works, so it must be missing from there and I must have found this solution somewhere I can't remember...
  • rmartrenado
    rmartrenado about 6 years
    This parameter sets the status automatically on your request object.
  • agorf
    agorf almost 6 years
    Even in a SPA, there can be an initial direct request to a URL that does not exist which the React router will handle. Replacing a decades-old standard way of signifying whether a URL exists or not (status code) with the hope that the website designer will communicate it effectively does not sound like a good idea. Each website designer will communicate this differently, which introduces inconsistency with any attempt to understand (humans) or parse it (crawlers).
  • Charles Merriam
    Charles Merriam almost 6 years
    Note that your argument is "that is different; thus it is bad". You could make a case that a 404 is the correct response for a SPAs that support deep linking but don't support sensible URLs for deep linking and when you can track its a deep link coming in and you want the right answer going to crawlers.
  • panzi
    panzi almost 6 years
    The router doesn't know anything about the response object! How would it be able to set the status? In my server side code I have to set the status myself. How do I find out if the router got to a 404 page?
  • agorf
    agorf almost 6 years
    Not really. I just disagree that setting HTTP status codes is merely a habit. It's not. It's something that was and is done intentionally with specific benefits (mentioned in my first comment). That said, the way I solve this in my case is I have my Rails app point specifically to the routes React is able to handle. This way everything else is a regular HTTP response with a 404 status.
  • SayJeyHi
    SayJeyHi about 4 years
    How to handle renderToNodeStream with this solution?
  • Dave Sottimano
    Dave Sottimano about 4 years
    "If the requestor is a crawler, it probably scans for 404 in <h1>'s" - Google mainly relies on 404 response codes or when they see the same template for 000's of routes, they label as soft 404. Imagine writing an article about 404 responses and your h1 contained '404' and Google automagically removing the page from their index. Nope.
  • Charles Merriam
    Charles Merriam about 4 years
    Sorry, I do not understand you. For SEO, you do need to go through different hoops but never change the error code. An article with an h1 text '404' would not be viewed related to receiving such an error code.
  • kevr
    kevr about 3 years
    If you are going to include an answer, including the entire answer would be appropriate. This answer completely omits any true reference to getting this working at all.
  • rmartrenado
    rmartrenado about 3 years
    @kevr what exactly don't you understand? I explained all the relevant parts, but I can't put in here every tiny detail. Maybe a jsfiddle?
  • rmartrenado
    rmartrenado about 3 years
    As I said on the answer (the last part) the status becomes just a custom prop of your route. Then on server.js you inspect that prop and there your are able to send to the client a certain http status.