Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 252
SRandazzo Avatar asked Dec 09 '18 13:12

SRandazzo


People also ask

Can CloudFront have multiple S3 origins?

You can configure a single CloudFront web distribution to serve different types of requests from multiple origins. For example, your website might serve static content from an Amazon Simple Storage Service (Amazon S3) bucket and dynamic content from a load balancer.

Can we host multiple websites on S3 bucket?

Hosting several subdomains in the same S3 bucket can be a great solution if you want to save time and money. Using only one S3 bucket means saving money because Amazon charges you for the size and amount of buckets, so fewer buckets mean fewer expenses.


1 Answers

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 not 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:

  • https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/
  • https://github.com/riboseinc/terraform-aws-s3-cloudfront-website/issues/1
  • How do you set a default root object for subdirectories for a statically hosted website on Cloudfront?
like image 176
SRandazzo Avatar answered Sep 23 '22 08:09

SRandazzo