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

1

5

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?

Bicapsular answered 19/5, 2021 at 14:27 Comment(0)
G
7

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.

Glottis answered 19/5, 2021 at 14:57 Comment(4)
Thanks! This is helpful. The only downside is it appears I would need to hard-code the version? I'll be uploading frequent versions, ideally the handler would be able to route to the correct version dynamicallyBicapsular
Do you have everytime the version in the path? I can change the answer for a dynamic route handling.Glottis
@NicholasHaley, I've added an example to handle the version. You can optimize the code by parsing the path one time with regexp.Glottis
Happy to confirm that (mostly) everything worked! The only thing is the asset check hasType doesn't work with my particular URL since the version number contains periods and creates a false positiveBicapsular

© 2022 - 2024 — McMap. All rights reserved.