Hosting multiple SPA web apps on S3 + Cloudfront under same URL
S

1

21

I have two static web apps (create-react-apps) that are currently in two separate S3 buckets. Both buckets are configured for public read + static web hosting, and visiting their S3 hosted URLs correctly display the sites.

Bucket 1 - First App:
   index.html
   static/js/main.js

Bucket 2 - Second App:
   /secondapp/
       index.html
       static/js/main.js

I have setup a single Cloudfront for this - The default cloudfront origin loads FirstApp correctly, such that www.mywebsite.com loads the index.html by default.

For the SecondApp, I have set up a Cache Behavior so that the path pattern secondapp/* points to the SecondApp bucket URL.

In my browser, when I visit www.mywebsite.com/secondapp/ it correctly displays the second web app.

If I omit the trailing slash however, I instead see the First App, which is undesired. If I visit www.mywebsite.com/secondapp/something, I am also shown the First App, which is also undesired. (I want it to load the .html of secondapp)

Both apps are configured to use html5 push state via react-router-dom.

My desired behavior is that visiting the following displays the correct site/bucket:

www.mywebsite.com - Currently working

www.mywebsite.com/secondapp/ - Currently working

www.mywebsite.com/secondapp - (Without trailing slash) Not working, shows First App

www.mywebsite.com/secondapp/something_else - Not working, show First App

How can I achieved the desired behavior?

Thanks!

Saltwater answered 9/12, 2018 at 13:38 Comment(4)
What is your order your other patterb ?Carrell
Precedence 0 is secondapp/* Precedence 1 is the default /*Saltwater
Did you find a solution? I have a similar scenario.Jackal
@Jackal I did! I will post an answer below, but in short I used lambda@edgeSaltwater
S
21

After researching this issue, I was able to resolve it using lambda@edge (https://aws.amazon.com/lambda/edge/)

By deploying a simple javascript function to route specific paths to the desired s3 bucket, we are able to achieve an nginx-like routing setup. The function sits on lambda@edge on our Cloudfront CDN, meaning you can specify when it is triggered. For us, it's on "Origin Request"

My setup is as follows:

  • I used a single s3 bucket, and deployed my second-app in a subfolder "second-app"
  • I created a new Lambda function, hosted on "U.S. East N Virginia". The region is important here, as you can only host lambda function an @edge in this region.
  • See below for the actual Lambda function
  • Once created, go to your CloudFront configuration and go to "Behaviors > Select the Default (*) path pattern and hit Edit"
  • Scroll to the bottom where there is "Lambda Function Associations"
  • Select "Origin Request" form the drop down
  • Enter the address for your lambda function (arn:aws:lambda:us-east-1:12345667890:function:my-function-name)

Here is an example of the lambda function I used.


var path = require('path');

exports.handler = (event, context, callback) => {
  // Extract the request from the CloudFront event that is sent to Lambda@Edge
  var request = event.Records[0].cf.request;

  const parsedPath = path.parse(request.uri);

  // If there is no extension present, attempt to rewrite url
  if (parsedPath.ext === '') {
    // Extract the URI from the request
    var olduri = request.uri;

    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    var newuri = olduri.replace(/second-app.*/, 'second-app/index.html');

    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;
  }
  // If an extension was present, we are trying to load static access, so allow the request to proceed
  // Return to CloudFront
  return callback(null, request);
};

These are the resources I used for this solution:

Saltwater answered 22/3, 2019 at 16:33 Comment(5)
how does this handle assets requests on paths like /second-app/static/index.cssOrissa
He's ignoring things with extensions.Kinslow
I'm currently doing something similar; however doesn't this essentially mean every single package and URL that's requested from that domain goes through this lambda request? I'm concerned that at high load, the lambda edge function will become the bottleneck with only 1000 concurrent (by default) requests allowed. Have you seen anything else since this?Odlo
Hi. Could you share your configuration of your cloud front distro? Did you declare a default object? Did you point your path patterns to the static S3 URL’s? EtcMug
Do you observe any issues on reloading your applications and maintaining deep/nested links? That is to say, does the addition of /index.html break any potential routes the user may be on in the event of a refresh?Grunion

© 2022 - 2024 — McMap. All rights reserved.