Back to Blog

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.

Viprasol Tech Team
May 28, 2027
13 min read

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

FeatureCloudFront FunctionsLambda@Edge
Max execution time1ms5s (viewer) / 30s (origin)
Max memory2MB128MB–10GB
Max package size10KB1MB (viewer) / 50MB (origin)
RuntimesJavaScript (ES5.1)Node.js, Python, Ruby, Java, Go
npm packages❌ No✅ Yes
Network access❌ No✅ Yes
TriggersViewer request/responseAll 4 triggers
Pricing$0.10/1M invocations$0.60/1M + compute
Cold startsNone~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

ScenarioCloudFront FunctionsLambda@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


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-1 provider alias, Lambda@Edge IAM with edgelambda.amazonaws.com, publish=true, CloudFront lambda_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-country header → 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.

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

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.