The first decision for an engineering blog is how much engineering to put into the blog. We landed on almost none. This site is a folder of HTML files in an S3 bucket with CloudFront in front of it. There’s no framework. The only build step is a short Python script that turns Markdown posts into HTML, and the only dependency is the Markdown library it calls.
The moving parts
- A private S3 bucket. Nothing is public on the bucket itself.
- A CloudFront distribution that reads from the bucket through Origin Access Control, so only CloudFront can fetch objects.
- An ACM certificate for
engineering.haggle.com, validated with a DNS record. - One CloudFront Function, about ten lines, that turns
/blog/into/blog/index.htmlso the URLs stay clean.
The URL rewrite
S3 has no idea what a directory index is when you’re going through the REST endpoint. CloudFront only applies the default root object at the top level. So a viewer-request function does the rest:
function handler(event) {
var req = event.request;
var uri = req.uri;
if (uri.endsWith('/')) {
req.uri = uri + 'index.html';
} else if (!uri.includes('.')) {
req.uri = uri + '/index.html';
}
return req;
}
That’s the whole thing. Trailing-slash URLs get index.html appended, extensionless URLs get /index.html, and anything with a file extension passes through untouched.
Deploying
Deploy uses the AWS CLI directly: render the site with Python, sync the folder to the bucket with aws s3 sync, then invalidate the distribution with aws cloudfront create-invalidation. HTML is served with a short cache lifetime so edits show up quickly; the stylesheet and images get a longer lifetime at CloudFront while browsers revalidate them. If we ever need more than that, we’ll write about why.
The AWS CLI commands, API request bodies, and routing function live in the site’s repository so anyone on the team can recreate the setup.
