Without changing the configurations of Apache or Express.js, we will not be able to serve React routes as expected. Here’s why: imagine that you have a React application with two pages, Index and About. You probably have an App.js file similar to this:

import React from 'react';
import Home from './pages/Home';
import About from './pages/About';

import {
	BrowserRouter as Router,
	Switch,
	Route
} from 'react-router-dom'

class App extends React.Component {
   render() {
      return (
				<Router>
					<Switch>
						<Route path="/" component={Home} exact />
						<Route path="/about/" component={About} />
					</Switch>
				</Router>
			)
   }
}

export default App;

This tells React to mount the Home component for the main page (/) and the About component for the about page (/about). So, when you go to /about, it seems that you’re now on a new page, where you’re actually not. The /about page is just another component; just like the / page. React is handling everything behind the scene; you actually just have one index.html file that loads your React code to manage routing.

That means for the routing to be working correctly, React should always be present; otherwise, the routing will break, since there’s no one to mount the correct component based on the current path. And who’s responsible for loading your React code: the index.html file and no one else.

So, to wrap it up: for the React routing to work, React code should always be present, which means that the index.html file should always be present, which means that your web server should always be serving the index.html file regardless of the path in the address bar. The path is yourwebsite.com/about? Your web server should serve the index.html file. The path is yourwebsite.com/batman? Your web server should serve the index.html file. The path is … You get the idea. No matter where you want to go inside your website, your web server should (almost) always bring up the index.html file that loads your React code. Period.

However, that’s not the default behavior of web servers. For instance, when you go to yourwebsite.com/about, the Apache web server will normally look for an index.html file inside your /about folder. But that’s not right. There’s no /about folder inside our React application. The Apache web server should instead serve the index.html file from your root directory. That’s also the case with other web application frameworks like Express.js. The rest of this tutorial is about how we can change this default behavior to serve our React routes correctly. Let’s start with Apache.

Configuring Apache

First, go to the root folder of your website. If you are using the default configurations that come with installing Apache, it’s /var/www/html/. Otherwise, it’s the path of your virtual host, like /var/www/example.com/. Create a file with the name .htaccess (or edit the existing file) using this command—I assume that you’re most probably using a Linux distribution to launch your website; so vi should be available:

sudo vi /var/www/html/.htaccess

Once it’s open, put the following script on the top:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]
</IfModule>

Don’t worry too much if it seems complicated to you; it does to pretty much everyone. But just to clarify a little bit, let’s break it down. The first line tells Apache that here we’re trying to rewrite some rules and the second line turns on the Rewrite engine. The third line says that for the following rule (line 4), we should use the prefix / in front of every path. Therefore, on line 4, index.html becomes /index.html which is the correct relative path. Line 4 basically says that if the user looks for /index.html, don’t do anything; just give the user the file they want—remember that the /index.html file is the file that the server should mount (almost) all the time. Lines 5-8 introduce a condition block. For each RewriteCond, if the condition is met, Apache will continue processing the next line. Finally, if all the three conditions are met, Apache will execute Line 8: RewriteRule . /index.html [L].

But what are these conditions anyway? They’re basically checking if the file or folder being asked by the user exists or not—that’s the reason behind all those almosts so far. There are times when we don’t want the server to hand the /index.html file to the user; images, for instance. If there is an image in the path /static/images/elephant.jpg, and somewhere inside our code we refer to that image, our server should be able to catch it and give it to us. It shouldn’t serve the /index.html file this time, since the file does in fact exist. So, if the file or folder doesn’t exist, it means that it’s a route to be handled by React. Therefore, the server should again serve the /index.html file instead. That’s what RewriteRule . /index.html [L] does. The [L] flag at the end simply means to stop going further (to the next line). It doesn’t make much difference at the end of this example, since there’s no line after that. But the first [L] flag is necessary to avoid the conditions. Hope it all makes sense to you. If it doesn’t, don’t worry about it at the moment. Just copy and paste it as it is.

There are still two little steps to take before Apache can serve our React app correctly. First, we should allow the .htaccess file we just made to apply those modifications. To do that, we should edit our Apache config file. Again, if you haven’t touched Apache before, it means that all the default configurations are still in place and your config file is located at /etc/apache2/sites-available/000-default.conf. If you’re using virtual hosts, it’s probably named after your domain. For instance: /etc/apache2/sites-available/mywebsite.com.conf. Use the following command to open it:

sudo vim /etc/apache2/sites-available/000-default.conf

Once it’s open, add the following script at the end of the file, just before the closing </VirtualHost> tag:

<Directory /var/www/html/>
		AllowOverride All
</Directory>

The whole file should look like this:

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

	<Directory /var/www/html/>
		AllowOverride All
	</Directory>
</VirtualHost>

This allows Apache to override any configuration it needs to apply our settings in the .htaccess file. Note that if you’re not using the default virtual host, you want to specify a different path rather than /var/www/html/. Also, I deleted all the comments in the file to save some space; yours should also include the comments (the lines with the # sign at the beginning).

We’re almost there. The last thing we need to do is to enable mode_rewrite and restart Apache. To do that, run the following command:

sudo a2enmod rewrite
sudo systemctl restart apache2

And that’s all. Now you can go to your website and try different React routes. You should be able to see that it’s working as it’s supposed to. Great!

Let’s see how we can achieve the same thing with Express.js.

Configuring Express.js

Compared to Apache, the pain of configuring Express.js is nothing. There are just two steps.

  • Tell Express.js where to find your static files; that is your index.html file, .js files, etc.
  • Tell Express.js to serve the index.html file if the file or path does not exist

Serving static files with Express.js

In order to serve your static files, Express.js needs to know where they are located; put differently, it needs to know the root folder of all your static files. Just add this line after calling the express() function:

var app = express()

//add this line after the above line
app.use(express.static(path.join(__dirname, '/public')))

Here, I assumed that your static files are located inside the /public folder. You can easily change it to something else, of course, based on your application.

Telling Express.js to serve the index.html file (almost) always

By now, you completely understand the importance of this step—we already did the same thing for Apache. To do this in Express.js, you only need to add just three lines (you can also make it one line) at the end of your Express.js main file, just before the line where you listen to a specific port:

//add these 3 lines just before the app.listen line
app.get("*", (req, res) => {
   res.sendFile(path.join(__dirname, "/public/index.html"))
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

It’s important to add this line at the end of the file. The reason is that once Express.js matches a request to a particular route, it won’t continue to consider the remaining routes. So, if you remember from the Apache part, we need our server to serve everything that exists and only show the index.html when there is no matched file, folder, or route. For instance, let’s assume that in your Express.js app, you also have a few other endpoints, like the following:

app.get('/post/:id', cors(), async (req, res) => {
	//stuff
})

If you don’t put the code snippet for serving the index.html after these lines, what happens is that Express.js won’t serve these endpoints anymore because our code snippet basically matches all get requests. So, it’s important to have the code at the end.