25.8 CloudFront Functions: Lightweight JavaScript at the Edge
Alright, let’s talk about CloudFront Functions. You’ve heard of Lambda@Edge, right? Its powerful, do-anything older sibling that can run for up to a second and change almost everything about a request? This isn’t that. CloudFront Functions are the scrappy, hyper-caffeinated cousin. They’re for the small, screamingly fast jobs that need to happen on every single request without adding a millisecond of latency. We’re talking sub-millisecond execution. They are the Usain Bolt of the edge compute world: blindingly fast, but they can’t carry a lot of luggage.
The core idea is simple: you write a tiny bit of JavaScript (ES5, because we’re living in the edge-literal past for maximum compatibility) that manipulates either the incoming request (viewer request) or the outgoing response (viewer response). You don’t get Node.js APIs. You get a stripped-down, purpose-built environment with a specific set of APIs for messing with HTTP stuff. This is a feature, not a bug. It’s why they’re so cheap and fast.
The Two Trigger Types: Viewer Request vs. Viewer Response
You attach your function to one of two points in the request lifecycle. Picking the right one is 90% of the battle.
The viewer request trigger is your bouncer. It fires after CloudFront gets a request from your user (the viewer) but before it checks its cache or forwards the request to your origin. This is where you do things like:
- Rewrite URLs (e.g., adding a prefix, removing a suffix).
- Normalize headers (e.g., making sure
Accept-Languageis always lowercase). - Implement URL-based authentication or access checks.
- Add a
X-Device-Typeheader based on theUser-Agentstring.
The viewer response trigger is your finishing school. It fires after CloudFront gets the response from the origin (or its cache) but before it sends it back to the viewer. Use this for:
- Adding, modifying, or removing response headers (e.g., stuffing in security headers like
Content-Security-Policy). - Rewriting the status code (e.g., changing a 404 to a 302 redirect).
- Logging specific response characteristics.
Here’s the critical, “I-learned-this-the-hard-way” distinction: a viewer request function can change what CloudFront uses as the cache key. Modify the URL or a header in the viewer request? That changes the request, so CloudFront might now be checking its cache for a different object. A viewer response function, however, cannot affect caching because the cache has already been hit (or missed) by then.
Anatomy of a CloudFront Function
The handler is simple. You get an event object, and you call back with a modified response or request. Let’s look at a classic example: using a viewer request function to redirect users from a non-www to a www domain. This is the kind of trivial-but-essential task these functions were made for.
// viewer-request function to redirect non-www to www
function handler(event) {
var request = event.request;
var host = request.headers.host.value;
var uri = request.uri;
// Check if the host starts with 'www.'
if (!host.startsWith('www.')) {
// Build the new URL
var newUrl = 'https://www.' + host + uri;
// Return a redirect response (302 Found)
return {
statusCode: 302,
statusDescription: 'Found',
headers: {
'location': { 'value': newUrl }
}
};
}
// If it's already www, just return the original request to proceed normally
return request;
}
See? No fuss. The event.request object is what CloudFront is about to process. We inspect it, and if it doesn’t meet our criteria, we short-circuit the whole process and return a redirect response directly from the edge. If it’s fine, we just return the request object to let CloudFront carry on. Beautifully efficient.
The Stark Reality: Limits and Quirks
This power comes with chains. Glorious, sensible chains.
- Execution Time: Max 1 millisecond. Yes, you read that right. If your function exceeds this, it’s terminated. This is not for complex logic.
- Memory: 128 MB. Again, tiny.
- Code Size: The uncompressed code can’t be more than 10 KB. This forces you to keep it simple.
- Runtime: It’s a custom JavaScript engine that supports ES5 (and a few bits of ES6 like
Promise). You cannotimportorrequireexternal modules. You copy-paste your tiny utility functions in. It feels archaic, but it’s the price of speed.
Let’s see a viewer response example. Here’s how you might add a bunch of security headers to every response before it goes to the browser.
// viewer-response function to add security headers
function handler(event) {
var response = event.response;
var headers = response.headers;
// Add or override headers
headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload' };
headers['x-content-type-options'] = { value: 'nosniff' };
headers['x-frame-options'] = { value: 'DENY' };
headers['x-xss-protection'] = { value: '1; mode=block' };
headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' };
// Return the modified response
return response;
}
The key here is that we’re modifying the event.response object. This happens so late in the process that it’s virtually free.
When to Use It (And When to Run Screaming to Lambda@Edge)
Use CloudFront Functions for the simple, high-volume stuff. Header manipulation, URL rewrites, simple redirects, and cache key normalization. If your task can be described in one sentence and doesn’t require reading a body, it’s probably a good fit.
Do not use it if you need to:
- Make network calls (you can’t).
- Read the request body or write a response body (impossible here, you need Lambda@Edge).
- Use any external library.
- Have complex logic that takes more than a millisecond.
- Do any form of image manipulation.
The pricing model is what makes this a no-brainer for these small tasks. You get a massive free tier (2 million invocations per month for free in the US East region) and then it’s a fraction of a cent per million requests after that. It’s practically free for most use cases. So stop doing these simple tasks in your origin server and offload them to the edge where they belong. Your server will thank you, and your users will get their responses a tiny bit faster. And in this game, those tiny bits add up.