AWS Lambda@Edge vs CloudFront Functions: A/B Testing, Auth at Edge, and Geo-Routing
Choose between Lambda@Edge and CloudFront Functions for edge logic. Covers A/B testing with cookie-based variant assignment, JWT authentication at the edge, geo-based routing, request/response manipulation, and Terraform setup.
Edge functions run at CloudFront's 450+ PoPs — milliseconds from users, before requests reach your origin. Lambda@Edge handles complex logic (JWT verification, database lookups, large payloads). CloudFront Functions handle lightweight transforms (header manipulation, URL rewrites, simple A/B routing) at 1/6th the cost.
Choosing wrong means either paying Lambda@Edge prices for work CloudFront Functions could do, or hitting CloudFront Functions' 10KB code size limit with complex logic.
Lambda@Edge vs CloudFront Functions
| Feature | CloudFront Functions | Lambda@Edge |
|---|---|---|
| Max execution time | 1ms | 5s (viewer) / 30s (origin) |
| Max memory | 2MB | 128MB–10GB |
| Max package size | 10KB | 1MB (viewer) / 50MB (origin) |
| Runtimes | JavaScript (ES5.1) | Node.js, Python, Ruby, Java, Go |
| npm packages | ❌ No | ✅ Yes |
| Network access | ❌ No | ✅ Yes |
| Triggers | Viewer request/response | All 4 triggers |
| Pricing | $0.10/1M invocations | $0.60/1M + compute |
| Cold starts | None | ~100ms (warm) |
| TypeScript | ❌ Must transpile | ✅ Bundled |
Use CloudFront Functions for: Header manipulation, URL normalization, simple redirects, basic A/B routing.
Use Lambda@Edge for: JWT verification (requires crypto), geo-complex routing, external API calls, any logic requiring npm packages.
Terraform: CloudFront with Lambda@Edge
# terraform/cloudfront-edge.tf
# Lambda@Edge must be deployed in us-east-1
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
# IAM role for Lambda@Edge
resource "aws_iam_role" "edge_lambda" {
provider = aws.us_east_1
name = "${var.app_name}-edge-lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]
}
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "edge_lambda_basic" {
provider = aws.us_east_1
role = aws_iam_role.edge_lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Lambda@Edge function (auth check)
data "archive_file" "auth_edge" {
type = "zip"
source_file = "${path.module}/../edge/auth-check.js"
output_path = "${path.module}/../.terraform/auth-check.zip"
}
resource "aws_lambda_function" "auth_edge" {
provider = aws.us_east_1
function_name = "${var.app_name}-auth-check"
role = aws_iam_role.edge_lambda.arn
handler = "auth-check.handler"
runtime = "nodejs22.x"
filename = data.archive_file.auth_edge.output_path
source_code_hash = data.archive_file.auth_edge.output_base64sha256
publish = true # Must publish a version for Lambda@Edge
tags = local.tags
}
# CloudFront distribution with Lambda@Edge
resource "aws_cloudfront_distribution" "main" {
origin {
domain_name = var.alb_dns_name
origin_id = "alb-origin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "alb-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = true
headers = ["Authorization", "CloudFront-Viewer-Country"]
cookies { forward = "whitelist"; whitelisted_names = ["session", "ab-variant"] }
}
# Attach Lambda@Edge to viewer request (auth check)
lambda_function_association {
event_type = "viewer-request"
lambda_arn = "${aws_lambda_function.auth_edge.arn}:${aws_lambda_function.auth_edge.version}"
include_body = false
}
}
# CloudFront Functions association (lightweight transforms)
# Added via aws_cloudfront_function resource
enabled = true
is_ipv6_enabled = true
price_class = "PriceClass_100" # US + Europe only (cheapest)
restrictions {
geo_restriction { restriction_type = "none" }
}
viewer_certificate {
acm_certificate_arn = var.acm_cert_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = local.tags
}
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
Lambda@Edge: JWT Auth at Edge
// edge/auth-check.js — runs at CloudFront viewer-request
// Must be vanilla JS (no TypeScript, no ESM, no npm in the zip)
// Bundle dependencies with webpack/esbuild before deploying
const PUBLIC_PATHS = ["/", "/login", "/signup", "/api/auth", "/blog", "/_next"];
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const uri = request.uri;
// Allow public paths without auth
if (PUBLIC_PATHS.some((p) => uri === p || uri.startsWith(p + "/"))) {
return request;
}
// Extract session cookie
const cookies = parseCookies(request.headers["cookie"] || []);
const token = cookies["session"];
if (!token) {
return redirect("/login?from=" + encodeURIComponent(uri));
}
try {
// Verify JWT — jose library bundled into the zip
const { jose } = require("./jose-bundle");
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jose.jwtVerify(token, secret);
// Add user ID to request header so origin knows who's authenticated
request.headers["x-user-id"] = [{ key: "X-User-Id", value: payload.sub }];
request.headers["x-workspace-id"] = [{ key: "X-Workspace-Id", value: payload.workspaceId }];
return request;
} catch (err) {
// Invalid/expired JWT
return redirect("/login?from=" + encodeURIComponent(uri));
}
};
function redirect(location) {
return {
status: "302",
headers: { location: [{ key: "Location", value: location }] },
};
}
function parseCookies(cookieHeaders) {
const cookies = {};
cookieHeaders.forEach(({ value }) => {
value.split(";").forEach((cookie) => {
const [k, v] = cookie.trim().split("=");
if (k && v) cookies[k] = decodeURIComponent(v);
});
});
return cookies;
}
CloudFront Functions: A/B Testing
// cloudfront-functions/ab-test.js — runs at viewer-request
// CloudFront Functions use ES5.1 — no const/let, no arrow functions, no async
function handler(event) {
var request = event.request;
var cookies = request.cookies;
// Check for existing variant assignment
if (cookies["ab-variant"]) {
// Inject variant into header for origin to read
request.headers["x-ab-variant"] = { value: cookies["ab-variant"].value };
return request;
}
// Assign variant: 50/50 split using random
var variant = Math.random() < 0.5 ? "control" : "treatment";
// Set cookie on response — use viewer-response trigger for this
// For now, just forward to origin with header
request.headers["x-ab-variant"] = { value: variant };
return request;
}
// Route to different origins based on variant:
// Lambda@Edge origin-request trigger — rewrites origin based on header
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const variant = request.headers["x-ab-variant"]?.[0]?.value;
if (variant === "treatment") {
// Route treatment group to v2 origin
request.origin = {
custom: {
domainName: "v2.myapp.com",
port: 443,
protocol: "https",
sslProtocols: ["TLSv1.2"],
readTimeout: 30,
keepaliveTimeout: 5,
customHeaders: {},
},
};
request.headers["host"] = [{ key: "Host", value: "v2.myapp.com" }];
}
return request;
};
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Geo-Based Routing
// Lambda@Edge: route users to nearest regional origin
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const country = request.headers["cloudfront-viewer-country"]?.[0]?.value ?? "US";
// Map regions to origins
const REGIONAL_ORIGINS = {
EU: { domain: "eu.myapp.com", regions: ["DE", "FR", "GB", "NL", "IT", "ES"] },
AP: { domain: "ap.myapp.com", regions: ["JP", "SG", "AU", "IN", "KR"] },
US: { domain: "us.myapp.com", regions: [] }, // Default
};
const region = Object.entries(REGIONAL_ORIGINS).find(
([, config]) => config.regions.includes(country)
)?.[0] ?? "US";
const origin = REGIONAL_ORIGINS[region];
request.origin.custom.domainName = origin.domain;
request.headers["host"] = [{ key: "Host", value: origin.domain }];
request.headers["x-region"] = [{ key: "X-Region", value: region }];
return request;
};
Cost Comparison
| Scenario | CloudFront Functions | Lambda@Edge |
|---|---|---|
| 10M requests/month | $1.00 | $6.00 + compute |
| Auth check (simple cookie) | ✅ $1 | ❌ Overkill |
| JWT verification (crypto) | ❌ Can't (no crypto) | ✅ Required |
| URL normalization | ✅ $1 | ❌ Overkill |
| A/B cookie assignment | ✅ $1 | ❌ Overkill |
| Geo-routing | ✅ $1 (simple) | ✅ Required (complex) |
| External API call | ❌ Cannot | ✅ Required |
See Also
- AWS CloudFront Lambda@Edge Patterns
- AWS CloudFront Edge Caching
- Next.js Middleware Auth Patterns
- AWS IAM Least Privilege
- Terraform State Management
Working With Viprasol
Lambda@Edge and CloudFront Functions are powerful but have sharp edges: Lambda@Edge must be deployed to us-east-1, must publish a version (not $LATEST), can't use environment variables directly (embed secrets at build time), and adds 100ms cold start. Our team picks the right trigger type, bundles dependencies for Lambda@Edge with esbuild, and implements auth-at-edge patterns that reduce origin load by blocking unauthenticated requests at the CDN layer.
What we deliver:
- Terraform:
us-east-1provider alias, Lambda@Edge IAM withedgelambda.amazonaws.com,publish=true, CloudFrontlambda_function_association - Auth Lambda@Edge: PUBLIC_PATHS whitelist, cookie JWT verify with bundled jose, X-User-Id/X-Workspace-Id headers injected, 302 redirect on failure
- A/B test CloudFront Function: cookie check → random 50/50 → x-ab-variant header
- Geo-routing Lambda@Edge:
cloudfront-viewer-countryheader → regional origin rewrite - Cost comparison table: CF Functions $0.10/1M vs Lambda@Edge $0.60/1M + compute
Talk to our team about your edge computing architecture →
Or explore our cloud infrastructure services.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.