Pavan Rangani

HomeBlogAWS CloudFront and Lambda@Edge: Edge Computing for Global Applications

AWS CloudFront and Lambda@Edge: Edge Computing for Global Applications

By Pavan Rangani · April 7, 2026 · Cloud Management

AWS CloudFront and Lambda@Edge: Edge Computing for Global Applications

CloudFront and Lambda@Edge: Processing at the Edge

CloudFront Lambda@Edge computing enables you to run code at AWS edge locations worldwide, processing requests as close to users as possible. Instead of routing all requests to a single origin region, Lambda@Edge intercepts requests at the nearest CloudFront POP and can modify requests, responses, generate content, or perform authentication — all within milliseconds. Therefore, global applications achieve lower latency and reduced origin load.

Lambda@Edge runs at over 400 CloudFront edge locations globally. Moreover, CloudFront Functions (a lighter alternative) execute at an even wider set of locations with sub-millisecond latency. Consequently, you can build sophisticated request routing, personalization, and security logic that runs right next to your users.

CloudFront Lambda@Edge Computing: Function Types

Lambda@Edge provides four trigger points in the request lifecycle — viewer request, origin request, origin response, and viewer response. Each trigger point serves different use cases. Furthermore, CloudFront Functions offer a simpler, faster option for lightweight request/response manipulation.

The distinction between these triggers matters more than it first appears. Viewer-request and viewer-response functions execute on every single request, even cache hits, so they bill on full traffic volume. In contrast, origin-request and origin-response functions only fire on a cache miss, which means they run far less frequently behind a well-tuned cache. As a result, expensive work like image transformation belongs on the origin side, while cheap header tweaks can live on the viewer side.

// Viewer Request: A/B testing at the edge
exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // Check for existing experiment cookie
    const cookies = headers.cookie || [];
    const experimentCookie = cookies.find(c =>
        c.value.includes('experiment='));

    if (!experimentCookie) {
        // Assign user to experiment group
        const group = Math.random() < 0.5 ? 'control' : 'variant';
        headers.cookie = headers.cookie || [];
        headers.cookie.push({
            key: 'Cookie',
            value: 'experiment=' + group
        });

        // Route to different origin path based on group
        if (group === 'variant') {
            request.uri = request.uri.replace('/page', '/page-v2');
        }
    }

    return request;
};

// Origin Response: Add security headers
exports.handler = async (event) => {
    const response = event.Records[0].cf.response;
    const headers = response.headers;

    headers['strict-transport-security'] = [{
        key: 'Strict-Transport-Security',
        value: 'max-age=31536000; includeSubdomains; preload'
    }];
    headers['x-content-type-options'] = [{
        key: 'X-Content-Type-Options',
        value: 'nosniff'
    }];
    headers['x-frame-options'] = [{
        key: 'X-Frame-Options',
        value: 'DENY'
    }];
    headers['content-security-policy'] = [{
        key: 'Content-Security-Policy',
        value: "default-src 'self'; script-src 'self' 'unsafe-inline'"
    }];

    return response;
};
CloudFront edge computing global network
Lambda@Edge runs code at 400+ edge locations for sub-millisecond response times

Constraints and Quotas You Must Design Around

Edge compute is powerful, but it ships with hard limits that shape every design decision. Lambda@Edge functions must be authored in Node.js or Python, deployed only from the us-east-1 region, and published as numbered versions — aliases like $LATEST are not allowed on a distribution. Moreover, the timeout caps differ by event type: viewer triggers allow only 5 seconds and 128 MB of memory, while origin triggers allow up to 30 seconds and more memory.

Environment variables are unsupported, so configuration has to be baked into the bundle or fetched at runtime. Additionally, the deployment package is capped — roughly 1 MB for viewer events and 50 MB for origin events — which rules out heavy native dependencies on the viewer side. The docs recommend keeping cold-start-sensitive logic small, because a brand-new function at a rarely hit POP will pay an initialization penalty before it can respond.

CloudFront Functions are even more constrained. They run a restricted JavaScript (ECMAScript 5.1-ish) runtime with no network access, no file system, a 1 ms execution budget, and a 10 KB code limit. However, that severity is the point: they execute inline with no cold start and bill at roughly a sixth the price of Lambda@Edge per invocation.

Image Optimization at the Edge

Resize and format images on-the-fly based on device type, screen size, and browser capabilities. This approach eliminates the need to pre-generate multiple image variants and reduces storage costs while delivering optimized images to every user. Importantly, place this logic on the origin-request trigger so the transformed result lands in the CloudFront cache — otherwise you would re-transform the same image on every viewer hit and lose the entire benefit.

// Origin Request: Dynamic image resizing
const Sharp = require('sharp');

exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const params = new URLSearchParams(request.querystring);

    const width = parseInt(params.get('w')) || null;
    const height = parseInt(params.get('h')) || null;
    const format = params.get('f') || 'webp';
    const quality = parseInt(params.get('q')) || 80;

    if (!width && !height) return request;

    // Fetch original from S3
    const s3Response = await s3.getObject({
        Bucket: 'my-images',
        Key: request.uri.substring(1)
    }).promise();

    // Transform image
    let transformer = Sharp(s3Response.Body);
    if (width || height) transformer = transformer.resize(width, height);
    transformer = transformer.toFormat(format, { quality });

    const buffer = await transformer.toBuffer();

    return {
        status: '200',
        body: buffer.toString('base64'),
        bodyEncoding: 'base64',
        headers: {
            'content-type': [{ value: 'image/' + format }],
            'cache-control': [{ value: 'public, max-age=31536000' }]
        }
    };
};

One subtle trap lurks in this pattern: a generated response body is capped at 1 MB after base64 encoding for Lambda@Edge. Therefore, very large originals can blow past the limit, and a common production pattern is to stream oversized assets directly from S3 instead of buffering them through the function. Another caveat is that the query string must be part of the cache key, or CloudFront will serve the first-generated size to every subsequent visitor regardless of their requested dimensions.

Geo-Based Routing and Personalization

CloudFront provides geo headers (CloudFront-Viewer-Country, CloudFront-Viewer-City) that enable location-based content delivery. Route users to regional origins, display localized content, or enforce geo-restrictions — all at the edge. Additionally, combine geo data with user preferences for personalized experiences. Remember that these headers only reach your function if the cache policy explicitly forwards them; by default CloudFront strips most headers to maximize cache hit ratio.

Personalization at the edge creates a tension with caching that teams underestimate. Every dimension you vary on — country, device, language, experiment group — multiplies the number of distinct cache objects. Consequently, a naive design that varies on a raw User-Agent string can fragment the cache so badly that hit rates collapse. A common pattern is to normalize many real-world values down to a handful of buckets (mobile/tablet/desktop, a short country allowlist) before they touch the cache key.

Global content delivery network
Geo-based routing delivers localized content from the nearest edge location

Observability and Debugging at the Edge

Debugging edge functions is harder than debugging a regional Lambda, precisely because the code runs in hundreds of places. Logs from Lambda@Edge are written to CloudWatch in the region nearest to where the function executed, not in us-east-1, so a single user’s request might land in ap-south-1 or eu-west-1. As a result, teams typically aggregate logs centrally or rely on CloudWatch Logs Insights cross-region queries to trace a request.

CloudFront Functions, by contrast, log only to us-east-1 regardless of execution location, which simplifies collection but offers far less detail. For production rollouts, the documentation recommends staging changes behind a continuous-deployment configuration so a small percentage of traffic hits the new function before a full promotion. This staged approach catches malformed responses early, since a function that returns an invalid object shape will surface as a 5xx error to real users with no way to roll back instantly.

When NOT to Use Edge Compute

Edge functions are not a universal upgrade. If your logic needs a database round trip, a secrets-manager lookup, or a heavy third-party SDK, you are usually better served by a regional Lambda behind an Application Load Balancer or API Gateway, where cold starts are cheaper to manage and connections can be pooled. Furthermore, Lambda@Edge versions cannot be deleted until every distribution that references them is updated and the replicas drain, which can take hours — an operational friction that surprises teams expecting instant cleanup.

Cost is another honest consideration. Lambda@Edge bills per request and per GB-second at a premium over standard Lambda, so running compute on every viewer request at high traffic can dwarf the savings from offloading the origin. In short, reach for the edge when the work is genuinely latency-sensitive and cacheable; keep stateful, connection-heavy, or rapidly iterating logic in a region where the tooling is friendlier.

CloudFront Functions vs Lambda@Edge

Use CloudFront Functions for lightweight operations (header manipulation, URL rewrites, redirects) — they’re faster and cheaper. Use Lambda@Edge for complex logic requiring network access, larger payloads, or longer execution time. A practical rule of thumb: if the operation reads or rewrites headers and finishes in well under a millisecond, it belongs in a CloudFront Function; if it calls S3, signs a token, or transforms a body, it belongs in Lambda@Edge. See the CloudFront edge functions documentation for detailed feature comparison.

Key Takeaways

  • Start with a solid foundation and build incrementally based on your requirements
  • Test thoroughly in staging before deploying to production environments
  • Monitor performance metrics and iterate based on real-world data
  • Follow security best practices and keep dependencies up to date
  • Document architectural decisions for future team members
Edge computing performance comparison
Choose CloudFront Functions for speed or Lambda@Edge for complexity

In conclusion, CloudFront Lambda@Edge computing brings application logic to the network edge, reducing latency and offloading work from origin servers. Use it for A/B testing, security headers, image optimization, and geo-based routing. Start with CloudFront Functions for simple tasks and graduate to Lambda@Edge when you need full compute power at the edge.

← Back to all articles