Serving React SPA in Koa

Today was the second time I spent a little bit of time trying to configure a Koa application with a GraphQL API endpoint as well static websites including a landing page and a React Single Page application.

The landing page is simple because it’s only a single index.html page with some assets. Nothing complicated and is served on the root path. The React application on the other hand needs to handle its own routing so when hitting the Koa server route such as /app/profile the React application should be served.

For structure of the directories in the application is as follows:

api/
  app.ts
client/
  public/
    index.html
  package.json
landing-page/
  index.html
package.json
The Solution

Going in to the app.ts file, this was my end result:

import Koa from 'koa';
import mount from 'koa-mount';
import serve from 'koa-static';

const staticLanding = new Koa();
staticClient.use(serve(`${__dirname}/../landing-page`));

const SPA_PATH = `${__dirname}/../client/public`
const staticSpa = new Koa();
staticSpa.use(serve(SPA_PATH));

const staticSpaMiddleware = async (ctx, next) => {
  return await serve(SPA_PATH)(Object.assign(ctx, { path: 'index.html' }), next);
};

// API Routes
app.use(mount('/api/health', health(config)));
app.use(mount('/api/graphql', graphQL));

// React application served only on /app route
app.use(mount('/app', staticSpa));

// Landing page
app.use(mount('/', staticLanding));

// Catch all route will also go to react /app
app.use(staticSpaMiddleware);

Comments :point_up describe the order of the route definitions.

But why is the react app served on multiple routes instead of a wildcard? I vaguely remember in Express, to be a simple `/app/*` and something I’d prefer. Many attempts were to use the wildcard which requires a generator function as a handler:

/app/(*/)

But I find this very confusing and still haven’t got my head around generators just yet. Instead I find that having a catch all route to simply always go to the React application. It’s just simple and neat.