Search code examples
amazon-web-servicesamazon-s3react-routersingle-page-applicationamazon-cloudfront

Client-side routing for S3 / Cloudfront with multiple path-based Single Page Apps


I have the following situation:

  • An S3 bucket with multiple path-based applications, grouped by version number. Simplified example:
/v1.0.0
  index.html
  main.js
/v1.1.0
  index.html
  main.js
  • Each application is a (React) SPA and requires client-side routing (via React router)

I am using S3 with Cloudfront and have everything mostly working, however the client-side routing is broken. This is to say I am able to visit the root of each application, ie. https://<app>.cloudfront.net/<version>, but cannot reach any client-side routes.

I'm aware that an error document can be set to redirect to an index.html, but I believe this solution only works when there is one index.html per bucket (ie. I cannot set an error document per route-based path).

What's the best way to get around this issue?


Solution

  • One simple way to deal with SPA through Cloudfront is by using Lambda@Edge - Origin request (or Cloudfront functions). The objective is to change the Origin URI.

    A simple js code that I use very often for SPAs (for the v1.0.0 webapp):

    exports.handler = async (event) => {
       const request = event.Records[0].cf.request;
       const hasType = request.uri.split(/\#|\?/)[0].split('.').length >= 2;
       if (hasType) return request; // simply forward to the S3 object as it is an asset
       request.uri = '/v1.0.0/index.html'; // handle all react routes
       return request;
    };
    

    I check if there is an extension (.png, .js, .css, ...) in the URL. If it is an asset, I simply forward to the S3 object otherwise I send the index.html.
    In that case, index.html is sent for the path /v1.0.0/my-react-router.

    Updated
    For dynamic handling, you can do like this (for the idea):
    request.uri = '/' + request.uri.split('/')[1] + '/index.html';

    Or even better, use regexp to parse the request.uri in order to extract the version, the asset's extension or the spa route.