Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express status 404 with react-router

I have an express server that handles: 1 API route and rendering my initial index.html to include bundle.js holding my React/React-Router/Redux application.

As it stands, it is impossible to 404 on my web page as I have a catch all:

app.use(function (req, res) {
  return res.render('index')
})

In order for react-router's NoMatch to work I need to send a 404 code.

My routes are as follows:

Express — /api/test/:x/:y

React Router — :x/, :x/:y

What I am essentially trying to achieve is, if the user ever goes to a URL of: :x/:y/z/and/further then return a 404, unless what they've gone to is /api/test/:x/:y

Questions:

  1. How can I match routes, excluding my API routes, preferably in a scalable way, returning appropriate status codes?
  2. For something so simple, is there significant overhead in setting this up on a subdomain? Would that even alleviate the issue? Would I face issues when the app grows?
like image 275
speak Avatar asked Feb 20 '16 13:02

speak


2 Answers

I managed to get my React app with React Router v4 working with an express server running liquid.js similar to handlebars the solution would work the same way for any other template engine.

In your React App.js make sure you have the React Router v4 installed and set up like this:

import React, { Component } from 'react';
import { TransitionGroup, CSSTransition } from "react-transition-group";
import 'foundation-sites';
import {
  BrowserRouter as Router,
  Route,
  Switch
} from "react-router-dom";
import './assets/css/foundation.min.css';
import './assets/css/settings.scss';
import './assets/css/structure.css';
import './assets/css/owl-slider.css';
import './assets/fonts/fontawesome/all.css'; 


// ## COMPONENTS
import Header from './components/Header/header';
import Footer from './components/Footer/footer';

// ## PAGES 
import HomePage from './components/Pages/Home/homePage';
import AboutPage from './components/Pages/About/aboutPage';
import ServicesPage from './components/Pages/Services/services';
import ContactPage from './components/Pages/Contact/contact';


class App extends Component {


  render() {
    return (
      <Router>
        <div className="App page-a blur" id="page" data-toggler=".blur" >
          <Header/>
          <div className="pageWrapper">
          <Route render={({ location }) => (
              <TransitionGroup>
                <CSSTransition key={location.key} classNames="pageTransition" timeout={500}>
                  <Switch location={location}>
                    <Route exact path="/" exact component={HomePage} />
                    <Route path="/services" render={props => <ServicesPage {...props}/>} />
                    <Route path="/about" component={AboutPage} />
                    <Route path="/contact" component={ContactPage} />
                    <Route render={() => <div>Not Found</div>} />
                  </Switch>
                </CSSTransition>
            </TransitionGroup>
          )}/>
          </div>
          <Footer footerMessage="Liliana Alves // Sport &amp; Soft Tissue Therapist"/>
        </div>
      </Router>
    );
  }
}

export default App;

The above code will make sure that when the user navigates your React app and the routes are doing their job sending the user to a page they navigate, refresh or enter in the URLs manually.

In your express server app.js you want to define the main access root "/" to your react app "Do not use a wildcard * (asterisk) this will not work!" :

app.get('/', (req, res) => {

        res.status(200).sendFile(path.join(__dirname+'/react-site/build/index.html'));

}); //THE REQUEST CONTENT

Then if you would have a 404 in your express it would redirect your user back to React to handle the 404s using React Router, this method is done using an express error handler:

app.use((req, res, next) => {
    const error = new Error('Not Found'); //Error object
    error.status = 404;

    //res.render('./404'); by default in express applications you would render a 404 page

    res.status(200).sendFile(path.join(__dirname+'/react-site/build/index.html'));

    next(error);

});

I spent a decent time research to get it working if anybody thinks this should be improved or it might need more support in the error function please let me know.

like image 58
Ricardo Alves Avatar answered Oct 06 '22 05:10

Ricardo Alves


Take a look at react-router server side rendering docs: reacttraining.com/react-router/web/guides/server-rendering

Solution:

  1. Extract routes to separate files and require it in express app
  2. Add a middleware in express app that check url in express using match function from react-router. It should be written after middlewares that responsible for API routes.
  3. In case there is no appropriate routes for request url, response with 404.

So, middleware should be similar to this:

// origin code from https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
// this is ES6, but easily can be written on a ES5.

import { match, RouterContext } from 'react-router'
import routes from './routes' 

var app = express();

// ...

app.use((req, res, next) => {
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
         // You can also check renderProps.components or renderProps.routes for
        // your "not found" component or route respectively, and send a 404 as
        // below, if you're using a catch-all route.

        // Here you can prerender component or just send index.html 
        // For prependering see "renderToString(<RouterContext {...renderProps} />)"
        res.status(200).send(...)
    } else {
      res.status(404).send('Not found')
    }
  })
});

If any routes change, you don't need to do something on express app, because you're using same code for frontend and backend.

like image 39
Timur Bilalov Avatar answered Oct 06 '22 06:10

Timur Bilalov