skip to content

CloudFront supports both CloudFront Functions and Lambda@Edge. Which trigger points can each hook into, and how would you choose between them for a given piece of edge logic?

level: middleimportance: must knowfreq 66%

answer

  1. four hooks, one runtime sees only two
  2. viewer triggers fire on cache hits too
  3. no network calls in the cheap runtime
  4. origin selection needs the bigger runtime
  5. published version, authored in us-east-1

basics

~20 s

CloudFront Functions run only on viewer request and viewer response, in a tiny sandboxed JavaScript runtime with no network access — ideal for header and URI rewrites. Lambda@Edge also hooks origin request and origin response, and can call other services.

solid answer

~50 s

CloudFront exposes four trigger points: viewer request, origin request, origin response, viewer response. **CloudFront Functions** attach to the two viewer triggers only. They run a purpose-built JavaScript sandbox with a sub-millisecond execution budget, a very small code and memory allowance, no network or filesystem access, and no access to the request body — so they are for URI rewrites, redirects, header manipulation, cache-key normalization and simple token checks, at a fraction of the price of Lambda@Edge. **Lambda@Edge** attaches to all four triggers, runs Node.js or Python with the AWS SDK available, can make network calls, and gets a much larger time and memory budget on the origin-facing triggers. Anything that must talk to a database, mutate the origin selection, or inspect a request body needs Lambda@Edge. One cost fact drives design: viewer triggers fire on every single request, including cache hits, while origin triggers fire only on a cache miss.

code

javascript · 8 lines
javascript
function handler(event) {
  var request = event.request;
  var uri = request.uri;
  if (uri.charAt(uri.length - 1) === '/') {
    request.uri = uri + 'index.html';
  }
  return request;
}

go deeper

for a junior

Be able to name the two edge runtimes and say plainly that CloudFront Functions are for lightweight header and URL rewrites on viewer triggers, while Lambda@Edge is fuller Lambda that can also run on the origin side.

for a middle

Explain the four trigger points and which runtime attaches to which, plus the concrete limits: no network or body access and a sub-millisecond budget for Functions, versus SDK access and much larger time and memory budgets for Lambda@Edge.

for a senior

Demonstrate the cost and latency reasoning — viewer triggers fire on cache hits, origin triggers only on misses — and the operational realities: replicated logs in the serving Region, version-pinned associations, and the risk of putting a dependency call on the hot path.

for a principal

Own where edge compute belongs at all. Argue when logic should live at the edge versus in the origin service or the API layer, set the guardrails for who may deploy edge code, and weigh the rollback and blast-radius cost of globally replicated functions.

## The four trigger points A request passing through CloudFront crosses four hook points. **Viewer request** fires when CloudFront receives the request, before the cache is consulted. **Origin request** fires only when the cache misses and CloudFront is about to contact the origin. **Origin response** fires when the origin's reply arrives, before it is cached. **Viewer response** fires just before the reply is returned, whether it came from the origin or from the cache. That asymmetry is the single most useful fact here: viewer triggers run on *every* request; origin triggers run only on cache misses. A per-request cost or latency belongs on a viewer trigger only if you accept paying it on cache hits too, and expensive work that only affects what gets stored belongs on an origin trigger. ## CloudFront Functions CloudFront Functions run in a JavaScript engine built for the edge rather than a general-purpose runtime. They execute at the edge locations themselves and are designed for sub-millisecond execution — the quota is on the order of a millisecond, and exceeding it fails the request rather than slowing it. The sandbox is deliberately austere: a small maximum function size (on the order of kilobytes), a couple of megabytes of memory, **no network access, no filesystem, no access to the request or response body**, and no AWS SDK. The handler signature is `function handler(event)` and it returns either a modified request or a synthesized response. Deployment has two stages, `DEVELOPMENT` and `LIVE`, and a built-in test harness that replays a sample event. For lookup data there is CloudFront KeyValueStore, a read-only key-value store the function can consult without a network call. Typical jobs: append `index.html` to directory-style URIs, normalize or strip query strings so more requests share a cache entry, redirect on `Host` or path, inject security headers on the viewer response, do a cheap presence/format check on an authorization header before the request is allowed further. ```javascript function handler(event) { var request = event.request; var uri = request.uri; if (uri.charAt(uri.length - 1) === '/') { request.uri = uri + 'index.html'; } return request; } ``` ## Lambda@Edge Lambda@Edge is real AWS Lambda: Node.js or Python, the AWS SDK in the environment, outbound network access, and the ordinary Lambda programming model. Functions must be authored in **us-east-1** and associated as a *published numbered version* — `$LATEST` is rejected — after which CloudFront replicates them. The budgets differ by trigger: viewer-facing triggers get a small memory allowance and a few seconds of timeout, while origin-facing triggers get much more memory and tens of seconds, because they sit off the hot path of cache hits. Request and response bodies can be inspected, within size limits, when body inclusion is enabled. Things only Lambda@Edge can do: choose the origin dynamically per request, rewrite the request the origin sees, call DynamoDB or Secrets Manager for a real authorization decision, transform an origin response before it is cached, or generate a response from data fetched elsewhere. ```javascript export const handler = async (event) => { const request = event.Records[0].cf.request; const country = request.headers['cloudfront-viewer-country']; if (country && country[0].value === 'DE') { request.origin.custom.domainName = 'eu.origin.example.com'; request.headers.host = [{ key: 'host', value: 'eu.origin.example.com' }]; } return request; }; ``` ## How to choose Ask three questions in order. *Which trigger do I need?* If the answer is origin request or origin response, the choice is made — Lambda@Edge. *Do I need to call anything, or read a body?* If yes, Lambda@Edge again. *Otherwise* — a URI rewrite, a redirect, header work, a cache-key tidy-up — use a CloudFront Function, because it is dramatically cheaper per invocation, adds effectively no latency, and cannot become a source of edge timeouts. A common production shape puts a CloudFront Function on viewer request to normalize and reject junk, and a Lambda@Edge on origin request for the one decision that genuinely needs data. ## Operational gotchas A Lambda@Edge function is replicated to many Regions, so its logs land in CloudWatch Logs in the Region that served the request, not in us-east-1 — a recurring surprise during incident triage. Deleting a replicated function requires removing the association and waiting for replicas to clear. And any code on a viewer trigger is in the path of every cache hit, so a slow dependency there turns a CDN into a bottleneck.

  • You add a viewer-request function that calls an authorization service. Why is that a design smell?
    Two reasons. Viewer-request runs on every request including cache hits, so you pay the call and its latency even when CloudFront answers from cache. And CloudFront Functions cannot make network calls at all, so the design forces you onto Lambda@Edge with its viewer-trigger timeout. If the decision must be per request, budget for it deliberately or move it to origin request.
  • Where do Lambda@Edge logs actually appear?
    In CloudWatch Logs in the AWS Region that handled the request, under a log group name that includes the replicated function, not in us-east-1 where you published it. During an incident you either check the Region nearest the affected viewers or aggregate across Regions. It surprises people who go looking in us-east-1 and find nothing.
  • Why must Lambda@Edge be associated as a numbered version rather than $LATEST?
    CloudFront replicates the code to many Regions, so the association has to point at immutable content. A mutable alias would mean different edge locations running different code during a deploy. You publish a version, associate it, and the deploy becomes an explicit replication step you can roll back by re-associating the previous version.

saying these in an interview costs you the question

  • Thinks CloudFront Functions can call other AWS services
  • Claims either runtime can hook all four trigger points
  • Believes viewer-request code only runs on cache misses
  • Says Lambda@Edge can be associated from any Region
  • Assumes edge functions can read the request body by default

context