Hosting a React JS single page application on Azure Blob Storage & Azure CDN for SSL, HTTP to HTTPS redirect and rewrite client routing traffic to the default document.
Azure blob storage is a great way to store files. They also offer a feature to turn your blob storage account into a static website host. This basically routes traffic to the main URL of your blob storage to a default document static document. This is great if you can guarantee that all traffic will load your default document, or you have used a static site generator and have all your HTML files split out separately. However most Single Page Applications do some form of client routing and will only work if your ‘index.html’ is loaded first.
E.g. example.com
will work. Azure blob storage will load your default document as configured in blob storage settings.
Howeverexample.com/test/route
will not work, as Azure blob storage will look for a file at that URL and not find it. We want this URL to load index.html
as it is not a request for a static js, css or image file.
Lets see how we can fix this using Azure CDN and URL rewrite rules engine to find URLs that are not for files, and route them through the default document.
I have had a few comments stating you can set the ‘error path’ within the blob storage configuration to your ‘index.html’ then the routing will kick in. I’ve not tried this but feel free to give it a go. Using a CDN as described later in this article will provide other features such as the HTTP to HTTPS redirection, custom domains and obviously the usual benefits of using a CDN.
Step one, set up a sample React SPA with some routing
I used command npx create-react-app
to bootstrap my application, I also used react-router-dom
to do some simple client side routing, so I used npm install react-router-dom --save
to install this separately.
Here is my main App.js
file, removing most of the boiler code and doing some simple routing.
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';function App() {
return (
<Router>
<div className="App">
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/sample">Another page</Link></li>
</ul>
</nav>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/sample" component={AnotherPage} />
</Switch>
</div>
</Router>
);
}const Home = props => (<div>Home</div>);const AnotherPage = props => (<div>Another page</div>)export default App;
Now using npm start
spins up a local development web server which should produce the following
I have two links and clicking ‘Another page’ you can see our route changes in the URL and the other page content is display, notice that even though the URL changes, we do not post back to the web server to generate the page
Refreshing the page locally does make the server load the page again and locally everything is fine, as the development web server loads route traffic through index.html. When deployed to Azure this is not the case and causes problems, but we’re ready to start setting up our Azure resources.
Set up Azure resources
Set up a new storage account, this is straight forward and nothing special has to be enabled during creation.
When you have the resource selected, the menu should now have a ‘Static website’ menu — we need to enable this, set our ‘Index document name’ to index.html
as we want to load this when loading the website.
Make note of the Primary endpoint, this is the URL to our new website, and we will need this when configuring the CDN we will create next.
Note that when you enable this, a storage container named $web
will be created for you, this is where our website files need to be uploaded to. I ran command npm build
which creates a production build of our app. I then uploaded this through the Storage explorer which you can download for free, just Google it.
If all set up correctly, visiting the ‘Primary endpoint’ from earlier in the browser should display our application. Exciting! Our application is now on the internet, hosted on extremely cheap storage without the need for expensive app service plans or app service resources etc.
The home page loads without specifying the index.html
in the URL, and hitting ‘Sample’ loads the sample page and updates the URL, it doesn’t request the server again for the new page.
Uh oh! Refreshing the page when on the /sample
URL causes a 404. Azure blob storage doesn’t know to route requests to /sample
through our index.html
it only knows to route to index.html
when requests are made to the base URL as we configured the default document. This means search crawlers and anyone linking directly to a page will get this error…
Azure CDN to the rescue!
That’s where Azure CDN comes into play, not only will we get the benefit of a CDN delivering our files, it also has a powerful ‘Rules engine’ where we can configure ‘URL rewrites’ which effectively matches patterns of the URL, and internally routes traffic elsewhere without affecting the client side URL.
So we can look for ‘route’ URLs and rewrite them internally to index.html
the React app will then load and route on the client based on the unchanged URL.
We just need to be careful not to rewrite URLs for other static assets such as JS, CSS or image files. My rule only rewrites URLs which do not contain a `.` (as in do not contain a file extension). This should be safe for most cases.
Update 12.03.2021 This is now possible and even easier & cheaper with the ‘Standard Microsoft CDN Profile. I will describe both options below.
Option 1 — Standard Microsoft CDN Profile
Create a new CDN resource using Standard Microsoft tier:
Create a new Endpoint and configure it to point to your blob storage hostname etc.
Then having selected ‘Rules engine’ in the Endpoint configuration. Create rules as the following:
The first rule does at is says and redirects any non https request to https.
The second rule rewrites any requests that do not have an extension (non static files, css js etc.) to our /index.html
Option 2 — Premium Verizon
Create a new CDN resource in the portal, make sure to select ‘Premium Verizon’ as the pricing tier, as this is the only tier which has the URL rewrite engine.
Configure a new endpoint with a ‘Custom origin’ that points to our blob storage primary endpoint that we used to view our website before.
Clicking back to manage the CDN profile (not the endpoint) and you should see a ‘Manage’ button at the top, this will open the Verizon portal to manage the settings there.
Then go HTTP Large > Rules engine from the menu
Then we need to create a new rule that always runs so do not alter the ‘IF’ and ‘Always’ settings. Hit the `+` next to ‘Features’ and select `URL Rewrite`. From here we need a pattern that will match URLs without a `.` and choose `index.html` as the destination.
The pattern I found to work is [^?.]*(\?.*)?$
I played around with many different variations and although this pattern didn’t meet all of my regex tests, it’s the only one I found to work. This took quite a while of Googling but I found this the key missing piece to many other articles and guides out there.
IMPORTANT
It can take up to 4 hours for your new rule to be accepted. And even when the status changes from ‘Pending’ to active, I have found it doesn’t actually mean it’s active yet, give it another hour or so before testing.
Lets find the endpoint URL for the CDN endpoint by going back to our endpoint overview. Copy the ‘Endpoint hostname’ and go to this in your browser.
In Summary
You should now see your React app that is hosted in Blob storage, provided through Azure CDN, and client routing should also work when visiting deep linked ‘pages’ directly, so SEO crawlers should index those pages. Success!
Not sure if this is really true, not sure how crawlers will index pages that require client side execution to create content.
Also using a CDN provides SSL by default, and also allows the use of custom domains, more greatness!
Something doesn’t work? Have a suggestion for next steps, let me know in the comments.