Navigate back to the homepage

Pretty URLs with AWS Cloudfront

Jarred Kenny
December 3rd, 2020 · 2 min read

I recently migrated a handful of the websites I host from a VPS I’ve had running on Digital Ocean for years over to Amazon Web Services. The sites that I needed to continue hosting were sites composed of static files, so I opted to store them in S3 and serve them out to the world using CloudFront.

Amazon CloudFront is a content delivery network offered by Amazon Web Services. Content delivery networks provide a globally-distributed network of proxy servers which cache content, such as web videos or other bulky media, more locally to consumers, thus improving access speed for downloading the content.

A content delivery system such as Cloudfront is great for serving static files quickly to viewers around the world and the pay per request cost model is ideal for the sites I host which do not receive massive amounts of traffic.

The Problem

I have used Cloudfront extensively to serve content but always as a component of a larger application. I had never tried to use Cloudfront to host a proper website as I would nginx or apache. I quickly noticed that some of the features I’ve come to take for granted in modern web servers were missing. Most importantly, what is known as “Pretty URLs”. If you have any level of experience in Wordpress you’ve no doubt come across the term.

Put simply, “Pretty URLs” allow for a default document to be served from a path when none is specified. For example, if the users navigated to https://website.com/my-awesome-article the web server would actually serve https://website.com/my-awesome-article/index.html behind the scenes. If you are running apache or nginx implementing this behavior is usually achieved by adding a Rewrite Rule of some kind to the configuration responsible for serving your site. I was shocked to discover that despite Cloudfront’s in depth set of options and customization, it was not possible to achieve the same behavior by simply tweaking some settings on the Cloudfront distribution.

How do we do it with CloudFront?

After some research, I discovered this can be achieved using Lambda@Edge functions. If you are new to AWS or have not yet discovered the power of Lambda, Lambda is Amazons Functions as a Service (FaaS) offering and it allows you to deploy functions rather than servers which are invoked when needed to preform tasks or serve an application or backend.

One feature of Lambda is Lambda@Edge functions which allow you to run Lambda functions directly on Cloudfront edge servers when requests are made. These can be used to modify or reject the request as it is made or kick off some additional task or automation when a request occurs. This is an incredibly powerful tool as it allows you to pragmatically control how requests are handled in the language of your choice while also providing the benefits of a globally distributed CDN.

I set out to write a Lambda function which would modify incoming requests in such a way that would allow me to serve my pages without including that ugly /index.html or my-post.html at the end of my URLs. Rather than reinventing the wheel I discovered I am obviously not the first person who has had to solve this problem and found lambda-edge-nice-urls which can be deployed in Lambda and assigned to a Cloudfront distribution.

This small function implemented in javascript does everything I needed!

1/* Public domain project by Cloud Under (https://cloudunder.io).
2 * Repository: https://github.com/CloudUnder/lambda-edge-nice-urls
3 */
4
5const config = {
6 suffix: ".html",
7 appendToDirs: "index.html",
8 removeTrailingSlash: false,
9};
10
11const regexSuffixless = /\/[^/.]+$/; // e.g. "/some/page" but not "/", "/some/" or "/some.jpg"
12const regexTrailingSlash = /.+\/$/; // e.g. "/some/" or "/some/page/" but not root "/"
13
14exports.handler = function handler(event, context, callback) {
15 const { request } = event.Records[0].cf;
16 const { uri } = request;
17 const { suffix, appendToDirs, removeTrailingSlash } = config;
18
19 // Append ".html" to origin request
20 if (suffix && uri.match(regexSuffixless)) {
21 request.uri = uri + suffix;
22 callback(null, request);
23 return;
24 }
25
26 // Append "index.html" to origin request
27 if (appendToDirs && uri.match(regexTrailingSlash)) {
28 request.uri = uri + appendToDirs;
29 callback(null, request);
30 return;
31 }
32
33 // Redirect (301) non-root requests ending in "/" to URI without trailing slash
34 if (removeTrailingSlash && uri.match(/.+\/$/)) {
35 const response = {
36 // body: '',
37 // bodyEncoding: 'text',
38 headers: {
39 location: [
40 {
41 key: "Location",
42 value: uri.slice(0, -1),
43 },
44 ],
45 },
46 status: "301",
47 statusDescription: "Moved Permanently",
48 };
49 callback(null, response);
50 return;
51 }
52
53 // If nothing matches, return request unchanged
54 callback(null, request);
55};

By using this Lambda function to modify incoming requests on my Cloudfront distribution I was able to achieve the Pretty URLs I desired while still leveraging a content delivery network but without having to keep a web server running 24/7. In fact, if you are reading this post your request was likely processed by this function.

Enjoy this article? Join our email list.

Be the first to receive our latest content with the ability to opt-out at anytime. We promise to not spam your inbox or share your email with any third parties.

More articles from BuildSavvy

SSH Gymnastics - Jump Hosts and Port Forwarding

SSH Jump Hosts connections to be chained and routed via many servers and is often useful for connecting to servers where firewall restrictions may prevent you from connecting directly.

December 3rd, 2020 · 2 min read

Secure Private Networking with Wireguard

I've seen a huge amount of hype online in the last few months about Wireguard. WireGuard® is an extremely simple yet fast and modern VPN…

December 3rd, 2020 · 3 min read
© 2020 BuildSavvy
Link to $https://twitter.com/SavvyBuildLink to $https://github.com/BuildSavvyLink to $https://instagram.com/BuildSavvy.devLink to $https://www.linkedin.com/company/BuildSavvy/Link to $https://dribbble.com/BuildSavvy