React-router: Redirect to NotFoundRoute

Created on 8 Nov 2014  路  34Comments  路  Source: ReactTraining/react-router

I was trying to redirect my "NotFoundRoute", but I figured out that this is not working as you can see in the example below:

http://jsbin.com/pulomegogi/5/edit

The reason I want to do something like this is if I have a an identifier as part of my URL (for instance an title of an blog article) and when I want to get that single item, but it doesn't exists. Then I would get an answer from the server, that this specific item does not exists. So I want to redirect to an NotFoundRoute.

Now my question: Should I use a normal Route for something like that? Or is there already another possibility I could not find here?

Most helpful comment

@halt-hammerzeit & @ryanflorence

For me when I kept <Route path="*" component={ErrorPage} /> it worked. This is on Client side. See below.

<Router history={appHistory}>
    <Route path = "/" component = {App}>
      <IndexRoute component = {ClusterTabPane} />
      <Route path = "cluster" component = {ClusterTabPane} />
    </Route>
    <Route path="*" component={ErrorPage}/>
  </Router>

All 34 comments

I would keep the URL and just render a "not found" view there.

However, I think we should be able to redirect to a NotFoundRoute

This issue kind of gets to the point of why I think our notion of a "redirect" is a little more like a "rewrite" of the current URL. In a server setting, you probably wouldn't want to incur the overhead of an extra request just to render a 404 page.

@rpflorence how could we make then the server responding with a 404 status?

I'm in a similar case as @Safari92, where the param refers to an inexistent id. As you suggest, I render a NotFound view:

render: function () {
  var place = findPlace(this.getParams().id);
  if (!place) return <NotFound />;
  // else render the actual component

When a NotFound view is returned, the server should also set a 404 status:

Router.run(routes, req.url, function (Handler, state) {  
  if ("pagenotfound") { // How to get here?
     res.status(404);
  }  
  var handlerElement = React.createElement(Handler);
  res.render('page', { html: React.renderToString(handlerElement); });

});

What could be the correct approach here?

@gpbl I have the same issue and found this.

@Nevon I could send a 404 status by using the name of the route in the state:

    Router.run(routes, req.url, (Handler, state) => {
       const html = React.renderToString //.....
       const isNotFound = _.find(state.routes, { 'name': 'not-found' });
       res.status(isNotFound ? 404 : 200).send(html);

not sure if the best way of doing it...

Doing the same as @gpbl now but I think when the NotFoundRoute is matched, it should be properly indicated within the state param.

In case it helps anyone -- in addition to rendering a not found view for a non-existent record etc, if you pass the context around (node req/res), you can set the response to a 404 status code anywhere

ajax.get(url, null, function(err, res) {
  if (err) {
    reject(err);
  } else {
    if (!!res.notFound && !!context.res) {
      context.res.status(404);
    }
    resolve(res.body);
  }
});

@gpbl I would still render the view the same on the server, but send a 404 status code instead. You can still render the same UI.

I'm not sure the router needs to change at all here, these are all questions for the app to answer.

  • You can redirect to a 404 route
  • You can render a 404 view in the current route
  • You can send a 404 status code and render the current route on the server
  • You can redirect to a 404 route on the server

@ryanflorence how would you handle the option _You can send a 404 status code and render the current route on the server_? It's similar to what @gpbl asked in https://github.com/rackt/react-router/issues/458#issuecomment-64838487

In other words, how would you pass info from component's render(...) to router.run(...) that the resource hasn't been found and res.status(404); should be sent?

The solution

       const isNotFound = _.find(state.routes, { 'name': 'not-found' });
       res.status(isNotFound ? 404 : 200).send(html);

doesn't work here because all routes matched OK and there is no 'not-found' route in state.routes.

@Snegostoop you can check if your route is NotFoundRoute by:

var isNotFound = state.routes.some(function(route) {
  return route.isNotFound;
});

@amrnt this solution doesn't work here. Imagine such a setup:

var routes = (
    <Route name="app" path="/" handler={App}>
        <Route name="user" path="/user/:user_id" handler={User} />
        <NotFoundRoute name="notfound" handler={NotFound}/>
    </Route>
class User extends React.Component {
    ...
    render() {
        var { router } = this.context;
        var user_id = router.getCurrentParams().user_id;
        var user = fetchUser(user_id);
        if (!user) return <NotFound />
        // else render the actual component

If you'd request _http://yourapp.com/user/non_existing_user_ all routes would match OK, and in state.routes there would be "app" and "user" (there would be no "notfound" route there). So there is no way to distinguish 404 from other "good" requests this way.
Or I am completely missing something.

@Snegostoop My answer above doesnt answer your question, I meant that is a better way than const isNotFound = _.find(state.routes, { 'name': 'not-found' });.

I'm having the same problem you are facing, I use flux so I have another story... :sob:

I suggest to do so: if (!user) return <NotFound /> will render a view and nothing to do with the routes. So I suggest to redirect.

class User extends React.Component {
    ...
    render() {
        var { router } = this.context;
        var user_id = router.getCurrentParams().user_id;
        var user = fetchUser(user_id);
        if (!user) {
          router.transition.redirect('notfound')
          return;
        }
        // else render the actual component

Give it a try and tell me if it works!

@amrnt I get _TypeError: Cannot read property 'redirect' of undefined_ for the line router.transition.redirect('notfound');

BTW, I also use flux (flummox), and in the real app it's a little different but the main issue is the same.

@Snegostoop sorry, my fault! Try router.transitionTo('notfound');

If the above works, maybe using replaceWith instead of transitionTo would be a better option!

https://github.com/rackt/react-router/blob/master/docs/api/RouterContext.md#replacewithroutenameorpath-params-query

for both transitionTo and replaceWith I get:

invariant.js:42 
Uncaught (in promise) 
Error: Invariant Violation: Missing "splat" parameter for path "/*"

Actually I'm not testing... I'm just guessing...

The issue here that NotFoundRoute doesnt accept path! What if redirect to unknown page? like router.transitionTo('/404');

Well, I guess redirecting to another pages will throw abort, so you will have it status 301 or 302 as it is handled.

There should be a way to throw NotFoundRoute handler instead of redirecting or so...

/cc: @ryanflorence

Yup, I am running into the same issue. There doesnt seem to be an easy way to return a 404 if the dynamic segment of the url is invalid. The route technically matches so I cant simply look for the isNotFound flag in the state.routes object on the server.

Was there ever a resolution on this?

@tizmagik please search the repo and issues, https://github.com/rackt/react-router/blob/master/UPGRADE_GUIDE.md#notfound-route

Thanks @knowbody I did that. Unfortunately, no mention of how to handle the common case of setting the server response status of 404 with an unmatched route that I could find. There are some hacky workarounds I saw in some closed issues -- is that what you're referring to? Or have I missed some official documentation on this?

Thanks @ammt I saw that, but technically the route does match because we'd want to serve up a NoMatch page:

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

But would like to set the status to 404. Maybe there's just no good way of doing it :(

@tizmagik I suggest they have to support status prop:

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

Which you could use like:

res.status( renderProps.status );

But unless they support it we need a workaround:

<Route name="404" path="*" component={ NotFound } />

Below is Koa code:

app.use ( next ) ->
      [ error, redirect, renderProps ] = yield preRender Routes, @originalUrl

      yield return @status = 500 if error?
      if redirect?
         @status = 301
         yield return @redirect redirect

      isNotFound = renderProps.routes.filter ( route ) -> route.name is "404"
      @status = 404 if isNotFound.length

      # https://github.com/facebook/relay/issues/136
      response = renderToStaticMarkup(
         <Layout
            hash={ stats.hash }
            isProduction={ process.env.NODE_ENV is "production" }
         >
            <RoutingContext {...renderProps}/>
         </Layout>
      )
      yield return @body = "<!doctype>\n" + response

+1 for an official api to solve this use case.

Thanks @droganov , I adapted that slightly so it looks a bit closer to what would ideally be supported by react-router:

The route:
<Route status={404} path="*" component={ NotFound } />

Express server code:

// ... server code ...

match({routes, location}, (error, redirectLocation, renderProps) => {

  // ... error, redirect handling, etc ...

  const isNotFound = renderProps.routes.filter((route) => {
    return route.status === 404;
  }).length > 0;

  res.status(isNotFound ? 404 : 200).end(generateSSRPayload());
});

So, is there no solution for this generic use case?
Can http status be set to 404 for the Not_found page (on the client)? Or can't it?

<Route path="/" component={Layout}>
    <Route path="*" component={Not_found}/>
</Route>

For the server side I got it working with the suggestion above

@halt-hammerzeit & @ryanflorence

For me when I kept <Route path="*" component={ErrorPage} /> it worked. This is on Client side. See below.

<Router history={appHistory}>
    <Route path = "/" component = {App}>
      <IndexRoute component = {ClusterTabPane} />
      <Route path = "cluster" component = {ClusterTabPane} />
    </Route>
    <Route path="*" component={ErrorPage}/>
  </Router>

Hey, is this not still outstanding?
I'm looking for the server side implementation.

Since this thread is still first match when searching help for server side rendering and returning 404 status code, here is my solution with react-router and react-redux.

Instead of matching react-routes, I keep page state in Redux store, initializing it with value "200". Any component may update it during rendering static html by executing redux action. Especially my PageNotFound component updates status to "404" on componentWillMount() callback:

const PageNotFound = React.createClass({  
  componentWillMount() {
    // Update status by executing redux-action
    this.props.onPageSetStatus(404);
  },
  render() {
    ...
  }
});

match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
  const html = ReactDOMServer.renderToString(<HTMLComponent {...params} />);

  // Status will be 200 or 404 depending if PageNotFound component was rendered
  res.status(store.getState().page.status).send(html);
});

Inspired by @hegdeashwin's example, I found a way better suit for my needs. It's all on the client side.
Just use an empty component to match all undefined routes. When 404 errors happens, we can dispatch an action to alter redux store for App component to show the 404 error.

Doing it so works better when the user navigate to an item not exist, which will match the "item" route instead of the "*" route.

<Router history={appHistory}>
    <Route path = "/" component = {App}>
      <IndexRoute component = {Home} />
      <Route path = "item/:itemId" component = {Item} />
      <Route path="*" component={EmptyPage}/>
    </Route>
  </Router>

We have a pretty odd setup (for better or worse) where we have multiple backend applications that each server up different parts of the front end application. Sitting in front we have a proxy which routes requests to the correct backend. What this meant was that there were multiple Router's on different entry points, each sharing a common layout Component, and each one only containing the Route's that they contained. We still wanted to be able to route between them.

So what we needed was the ability for the Router to route to any Route's it was responsible for and if there were no matching routes then to do a real request to the server.

The only way I could get this working was something like this:

const routeForReal = (nextState, callback) => {
  location.reload(true);
};

<Router history={browserHistory}>
  <Route path={JsRoutes.controllers.Application.base().url}>
    // Use getComponent here to trick the router into thinking we are loading the component asynchronously and
    // then reload the page to hit the server for real. If we don't do this then we get a blank page.
    <IndexRoute getComponent={routeForReal} />
    // ... other routes here...
    <Route path="*" getComponent={routeForReal} />
  </Route>
</Router>

This also means that we can serve the 404 page from the proxy instead of being forced to render some NotFound component.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yormi picture yormi  路  3Comments

andrewpillar picture andrewpillar  路  3Comments

jzimmek picture jzimmek  路  3Comments

stnwk picture stnwk  路  3Comments

winkler1 picture winkler1  路  3Comments