React Router and Nginx over HTTP/2
Marco Franssen /
10 min read • 1834 words
In this blogpost I want to show you how you can easily get your React SPA app with clientside router work properly with your Nginx setup. I will also show you how to serve your React App over HTTP/2 and how you can leverage from http2 server pushes. To do so I will show you how to do that with the Nginx Docker image.
When running your webapp using the development server you will in general not face any issues, however when running the static build on a production server you will most likely face some issues. E.g. If you use React Router with a route for /todo/42 your webserver will be looking for a file called /build/todo/42 which can't be found. Therefore we will need to direct our webserver to the index.html.
When using Nginx Docker image with the default configuration you will figure that you will get 404 responses as soon you reach a page navigated by the clientside React router. We will explore further on how to resolve that and how to add some performance tuning as well.
React SPA
First we will create a new React SPA using Create React App. We will also add the react-router-dom
package to be able to reproduce the issue you will face when hosting with default Nginx setup.
Lets start our app using the development server to have a look how it looks like.
React Router
Now we have a basic React application in place we can add the React router.
Now we will also update the code in src/App.js
to use the react-router-dom
package, so we can reproduce the issue when hosting this app in Nginx.
In this example I have just added 2 links utilizing the React router. Each of them show a different Header text.
Now with the development server still running you can now navigate to both http://localhost:3000/ and you will notice all works as expected.
Nginx docker setup
Now we can add our setup for Nginx and Docker to run our app from a Nginx Docker container. I always prefer to add a docker-compose.yml as it simplifies running my application.
In this example you see we make a volume mount on the static build of our React App in the Nginx webserver root. This helps us a lot in development so we can just have the files served without relaunching the container.
Now let us run this Docker container to see the issue I described in the beginning of this blogpost.
Navigate to any of the pages in your webbrowser by clicking any of the links in your React App. As you can see, so far all works as expected. However if you now refresh the page at http://localhost:5000/about it will fail with a 404 Not Found. On Nginx there is no such file to be served. This page only exists clientside inside your React router.
We can resolve that by changing the Nginx configuration. To do so we will replace the default server configuration in our Nginx container.
The configuration which will make it work is the try_files
line. This will try to serve any file requested or fallback to index.html
. In index.html we have our React code running that takes care of navigating the different pages client-side.
To use this configuration in our docker image we can simply mount it as a volume to the image.
Of course you could also build your own Docker image which contains the Nginx configuration and your app assets.
With this Dockerfile we are using a Multi stage build that utilizes the Node 13 image to build our static webpage and then we build our Nginx container containing the Nginx configuration and the static assets. Using a multi-stage Dockerfile we limit the size of the image, by leaving all the NodeJS dependencies as well the source files out of the production Nginx container.
As you can see we can now use the Nginx image as an immutable artifact without mounting volumes or changing things at runtime.
Also see the following docker-compose.yml showcasing how we can easily build and run this image using docker-compose.
This builds the image, start the container and maps port 5000 to port 80 of your container.
Performance improvements
Since the arrival of HTTP/2 we can also push artifacts from the serer to the browser. This will reduce the page load time drastically as the DOM doesn't have to be parsed to search for the images, stylesheets and javascript before it will be downloaded. Therefore with HTTP/2 we can push the most important assets already to the browser, before the browser requests them. In terms of our React app this would be our javascript bundles and our styles.
To be able to use HTTP/2, an SSL certificate is mandatory. So lets first generate a selfsigned SSL certificate. For this you will need openssl installed. Don't worry if you don't have it, further on I will show you how to do this within the Dockerfile.
This will use selfsigned-cert.conf to do this unattended without any prompts for details.
In the Dockerfile I have included this step to generate the certs automatically on building the Docker image.
Once we have an SSL certificate we simply update our server configuration to serve as HTTP/2 over TLS.
With all of that in place we can test again and see if our webpage is served over HTTP/2. The last step is to also push the initial dependencies for our index.html. This can be done as following. Simply check the index.html to see which javascripts, stylesheets and images are requested.
Feel free to add more lines yourself. Now it is probably time to check out the changes. docker-compose up --build
and navigate to https://localhost. Open the developer tools of your browser and you will notice in the network tab which assets are pushed via HTTP/2 server push (Initiator column). Also compare the same on http://localhost:5000 which is not running on HTTP/2 and therefore not able to do the server side push.
Now there are way more things you could do to improve the performance, like gzipping and tweaking Nginx worker processes. On this already many blogposts are written, so I am not going to repeat that over here. These will also require you to more closely analyze the specifics of your application. You will have to find the sweet spot on when to gzip and when not. Some tool to help you analyse the performance of your web application from the browser is Lighthouse on which I have written before. In the references at the end of this blog I included some Nginx tweaking articles.
Tryout Lighthouse to see if you could make some small improvements performance wise to the current setup. E.g. Caching of your assets.
Security
Now it is also good to check some security bits and pieces. You can use www.ssllabs.com to check your SSL settings. E.g. Configure the SSL protocols and SSL ciphers. Give the scan with SSL Labs a try and see for yourself to reach your desired level of security. I always opt for a minimum of an A rating and then try to optimize for A+.
Then you could also check with securityheaders.com to secure some other aspects of web applications like CORS policies etc. There are many resources available on how to apply the settings so I won't go in detail in this blog on how to apply those.
Both I leave out of scope for this blog, but never the less I find it important to mention so you can ensure this will be handled by you properly.
TL;DR
For everyone who reached this point you might want to bookmark the TL;DR for future reference. So you can download the full example here.
See below the most important files we worked on:
App.js
We modified the default App.js to utilize the React router package.
Dockerfile
The Dockerfile creates a selfsigned certificate, builds your React application and hosts it in Nginx.
docker-compose.yml
The docker-compose.yml file simplifies using your Docker image using a more convenient commandline.
selfsigned-cert.conf
Provides the paramaters for openssl to generate the selfsigned certificate.
nginx/conf.d/default.conf
Holds the Nginx webserver configuration running as HTTP/2, including the server side push of important assets.
References
- Download the full example here
- Lighthouse
- securityheaders.com
- www.ssllabs.com
- www.nginx.com/blog/performance-tuning-tips-tricks/
- www.nginx.com/blog/tuning-nginx/
- ops.tips/blog/nginx-http2-server-push/
Following 2 resources highlight some caveats:
I hope you enjoyed this blog. For your production webservers I can also recommend Letsencrypt certificates which won't cost you a single Penny but will bring you the security layer and capability to use HTTP/2. Please see my other blogs on Letsencrypt.