I’ve written about how to host a single page application (SPA) on AWS using CloudFront and S3 before, using the CloudFront “rewrite not found errors as a 200 response with index.html” trick.
Recently, working on a few serverless apps, I’ve realized that this trick, while quick, isn’t perfect. The specific case where it broke down was when the API is configured as a behavior on CloudFront (I usually scope the API to /api
on the same domain as the frontend, so CORS and OPTIONS requests aren’t necessary). If the API returned a 404 Not Found
response, CloudFront would rewrite it to 200 OK index.html
, and the front-end application would get confused. Unfortunately, CloudFront doesn’t support customized error responses per behavior, so the only way to fix this was to use Lambda@Edge instead.
Here’s the code for the Lambda function:
'use strict'
const path = require('path')
exports.handler = (evt, context, cb) => {
const { request } = evt.Records[0].cf
const uriParts = request.uri.split("/")
if (
// Root resource with a file extension.
(
uriParts.length === 2 && path.extname(uriParts[1]) !== ""
) ||
// Anything inside the "static" directory.
uriParts[1] === "static"
) {
// serve the original request to S3
} else {
// change the request to index.html
request.uri = '/index.html'
}
cb(null, request)
}
This code assumes all requests to a root request with a file extension, or anything in the /static/
directory is a static file that should be served from S3. All other requests will be rewritten to index.html
. These are the defaults for create-react-app, but you’ll probably need to change them to meet your requirements. (Remember, Lambda@Edge functions need to be created in us-east-1
)
Attach this Lambda function to the CloudFront behavior responsible for serving from the S3 origin as origin-request
, and you should be good to go. Don’t forget to remove the 404-to-200 rewrite.
4 replies on “Hosting a Single Page Application with an API with CloudFront and S3”
Hmm, I’ll have to look into this, as I didn’t run into this issue when building nanotwit.ch with that basic model in early 2018 (without Edge I thought).
I’ve also got Cloudflare in front of CloudFront. Slightly overkill, but oh well. Haven’t had to update the app at all since launching it. Praise #serverless !
Can’t you just make the lambda@edge function be called during origin-response and convert 404 -> 200?
See https://aws.amazon.com/about-aws/whats-new/2017/12/lambda-at-edge-now-allows-you-to-customize-error-responses-from-your-origin/ and https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-custom-error-static-body
Good point. Yes, that would work as well. In my specific case, the
origin-request
method is preferable because it makes 404s explicit (for example, a request to old static resources won’t be rewritten to index.html, etc). Thanks 🙂I see — and your method also seems to be faster because it doesn’t have to wait for the S3 404 response.