Hosting a Single Page Application with an API with CloudFront and S3
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.