How to handle path-based routing for SPA when CloudFront has multiple behaviors?

1

We have an Angular SPA hosted on S3/CloudFront and we would like to use path-based routing. When a link like https://ourapplication.com/foo/bar is opened in a browser, we need the request to resolve to a 200 response containing the contents of index.html, so that the path part of the URL /foo/bar can be processed as a route by the Angular app running in the client.

We got this working by adding "Custom Error Responses" to the CloudFront distribution - when the path /foo/bar is sent to the S3 origin, a 403 is generated. The custom error response maps this 403 to a 200 response with the contents of index.html. Perfect, apart from the fact that we also have another behavior on the CloudFront distribution that routes requests matching /api/* to API Gateway. Since the custom error response applies to the entire distribution, any API response returning 403 will also be mapped to index.html/200 - not what we want.

Ideally we would have a per-behaviour or per-path custom error response.

We tried to replicate something similar with redirection rules on the S3 bucket, but for various reasons this doesn't seem to be possible. Even if this could be made to work, it would always result in a redirect being sent back to the client instead of the index.html content being returned in the original request, so an extra round trip is required.

We also considered Cloudfront/Lambda@Edge functions - we'd prefer not to use the latter for cost reasons. It's not clear how we would implement this with Cloudfront functions given that only Viewer Requests/Responses are supported there.

This SO post describes the same problem, and a possible solution based on Lambda@Edge Origin response handlers, which we'd rather not use for cost reasons.

If we can't solve this, we'll probably fall back to hash-based routing but that has drawbacks for SEO, analytics etc.

A path-based SPA with a same-origin API routed via a CloudFront behavior must be a fairly common scenario, so hopefully there's a better way or perhaps something on the CloudFront roadmap.

1 Answer
1

There are a couple of options I can think of for solving this:

  1. Using CloudFront Functions: Inspect the URI and rewrite (not redirect) any requests for /foo/bar etc. to /index.html. CloudFront will serve index.html from cache if it's present, or from the origin if required.
  2. Using Lambda@Edge: On Origin Request for your non-API behaviors, inspect the URI (if necessary) and return the static HTML directly from Lambda - see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses-in-requests.html for details on generating HTML responses in Lambda@Edge. This may not have the cost impact that you think, as the function will only be invoked in the event of a cache miss. Provided your cache hit ratio is high enough (above about 85%), this option would actually cost less than CloudFront Functions.

You could also use Lambda@Edge to perform a rewrite, if you'd prefer to store the html content in S3 rather than generate it in Lambda every time. Again, this may work out cheaper than CloudFront functions if your cache hit ratio is high enough.

AWS
EXPERT
Paul_L
answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions