I recently had to build multiple subdomain routes into a node and react project. I wanted to make the routing easy by resolving normal base href routes for development as well as meet the subdomain requirements for production. One of the challenges was dealing with both React Router and Express routing. For example, both sub.test.com and localhost:8080/sub will serve up the same pages. I found an approach that solves both types of routing without any need for configuration changes for each deployment target.

Express Routing

The magic to serve up both subdomains and routes lies in Express middleware. Here’s how typical Express routing looks. React handles the client side routing, so the routes for serving up the initial index.html file is pretty sparse.

var router = express.Router()
routes.get("/sub*", (req: Request, res: Response) => {
  res.sendFile(path.resolve("index.html"));
});

The goal is to replace the route with a subdomain when deployed to production. The way I’ll build the routing will avoid the situation where both the subdomain and the route show up in the URL. I don’t want sub.host.test/sub. I want sub.host.test for prod and localhost/sub for development.

So far, none of this groundbreaking. Here’s where things start to get a bit interesting. Basically, the routes above will handle the local development case perfectly. You could setup a reverse proxy to point to the two routes and build the subdomains into the reverse proxy routes and you might be happy with the result. What you’d get is the duplication of the subdomain in the route like sub.host.com/sub. That’s not what I want. I want the routes to look nice!

OK, so enter the magic middleware. The middleware identifies the subdomains and routes to the appropriate index page. It’s routing within routing.

var subdomainMiddlware = (req: Request, res: Response, next: NextFunction) => {
  if (req.hostname.match(/sub\./g)) {
    res.sendFile(path.resolve("index.html"));
  } else {
    next();
  }
};

The middleware works by checking the hostname property on the request. If the hostname begins with a valid subdomain, the appropriate html file is sent on the response. Otherwise, the request passes through the middleware.

Here’s how the middleware is appplied to the routes. This isn’t the most ideal way of adding the middleware, but for this example, it works just fine.

routes.get("/sub*", subdomainMiddleware, (req: Request, res: Response) => {
  res.sendFile(path.resolve("index.html"));
});

Setting Up React Router

Now that the server can handle both subdomains and routes to serve up the same pages, React Router needs a little help to build the correct routes. This is what a typical routing setup looks like for React Router. I’ve included the sub route to reflect the base route.

import { Router, Route, browserHistory } from 'react-router'

render((
  <Router history={browserHistory}>
    <Route path="/sub" component={App} />
  </Router>
), document.getElementById('root'))

However, I’m interested in changing the base route when the app is running on a production machine or during development. Luckily enough, react-router supports setting a base route using the createHistory method from the history package. Once setup, the basename can be set to change based on the current environment.

import { Route, Router, IndexRoute, useRouterHistory } from "react-router";
import { createHistory } from "history";

// Change the base route based on the hostname
const isLocalhost = location.hostname.match(/localhost|127\.0\.0\.1/g);
const history = useRouterHistory(createHistory)({
  basename: isLocalhost ? "/sub" : "/",
});

export default (
  <Router history={history}>
    <Route path="/" component={App}>
      <Route path="/sub" component={App} />
    </Route>
  </Router>
);

I’ve removed the browserHistory module from the code above and replaced it with the useRouterHistory module which accepts the createHistory method as an argument. The whole point is to the change the basename if the site is running in localhost or in production. The basename value has to match what Express routes on the server side.

By following this pattern, I’m able to publish my site to a remote server and use the subdomain to access the site.