Back to blog

Serverless and Edge Computing: Low-Latency Architecture Dominating 2025

Remember when deploying an application meant provisioning servers, configuring load balancers, manually managing scalability, and praying that traffic wouldn't bring everything down? Well, those days seem increasingly distant.

In 2025, the combination of serverless and edge computing has completely transformed how we think about infrastructure. Today, you can write a function, deploy it, and it will be running in over 300 locations around the world, responding in less than 50ms, automatically scaling from 0 to millions of requests - all without you managing a single server.

Sounds like magic? Let's unveil how this architecture works in practice.

Serverless: Beyond the Hype, The Practical Reality

Serverless doesn't mean "without servers" - it means you don't need to worry about servers. Infrastructure is abstracted, you pay only for what you use, and scaling happens automatically.

The Serverless Execution Model

Unlike traditional servers that run 24/7, serverless functions are event-driven:

  1. Cold Start: First invocation initializes the execution environment
  2. Warm: Subsequent executions reuse the environment (if within the time window)
  3. Scaling: Multiple instances are created automatically on demand
  4. Billing: You pay only for execution time (milliseconds)

When Serverless Really Shines

APIs with Irregular Traffic: If your API has usage spikes (e-commerce on Black Friday, for example), serverless scales automatically.

Event Processing: Image processing, webhooks, scheduled jobs - perfect for serverless.

JAMstack Backends: Next.js, Nuxt, Remix - all use serverless functions for API routes.

Lightweight Microservices: Each function can be an independent microservice, deployed separately.

Edge Computing: Taking Code Closer to the User

Edge computing takes serverless a step further: instead of running in a specific region (us-east-1, for example), your code runs in globally distributed data centers, close to your users.

A user in São Paulo accesses the Brazilian edge node. A user in Tokyo accesses the Japanese edge node. Same application, drastically reduced latency.

The Difference Between Traditional Serverless and Edge

AWS Lambda (Regional Serverless):

  • Executes in a specific region (e.g., us-east-1)
  • Cold start: 100-500ms
  • Additional latency for distant users

Cloudflare Workers (Edge Computing):

  • Executes in 300+ locations globally
  • Cold start: <10ms
  • Consistently low latency globally

Ideal Use Cases For Edge

Content Personalization: Modify HTML based on geolocation, A/B testing, feature flags.

Authentication and Authorization: Verify JWT tokens before requests reach the backend.

API Gateways: Intelligent routing, rate limiting, caching.

Request Middleware: Headers, redirects, URL rewrites.

Cloudflare Workers: Edge Computing in Practice

Cloudflare Workers is the most popular edge computing platform in 2025. Let's see a practical example:

Example 1: Geolocation API with Intelligent Cache

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // Geolocation information available automatically
    const country = request.cf.country;
    const city = request.cf.city;
    const timezone = request.cf.timezone;

    // Cache key based on location
    const cacheKey = `geo:${country}:${city}`;

    // Check cache in KV (Cloudflare's key-value store)
    let data = await env.GEO_CACHE.get(cacheKey, { type: 'json' });

    if (!data) {
      // Fetch customized data for this location
      data = await fetchLocationData(country, city);

      // Cache for 1 hour
      await env.GEO_CACHE.put(cacheKey, JSON.stringify(data), {
        expirationTtl: 3600,
      });
    }

    return new Response(JSON.stringify({
      location: { country, city, timezone },
      data,
      cached: !!data,
    }), {
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=3600',
      },
    });
  },
};

async function fetchLocationData(country, city) {
  // Simulate fetching personalized data
  return {
    currency: getCurrencyForCountry(country),
    language: getLanguageForCountry(country),
    popularProducts: await getPopularProducts(country),
    shippingOptions: await getShippingOptions(city),
  };
}

function getCurrencyForCountry(country) {
  const currencies = {
    BR: 'BRL',
    US: 'USD',
    JP: 'JPY',
    GB: 'GBP',
  };
  return currencies[country] || 'USD';
}

This worker runs on the edge closest to the user, automatically detects location, and returns personalized data with minimal latency.

Example 2: JWT Authentication at the Edge

import { verify } from '@tsndr/cloudflare-worker-jwt';

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // Public routes don't require auth
    const publicRoutes = ['/health', '/login', '/register'];
    if (publicRoutes.includes(url.pathname)) {
      return fetch(request);
    }

    // Extract token from header
    const authHeader = request.headers.get('Authorization');
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return new Response('Unauthorized', { status: 401 });
    }

    const token = authHeader.substring(7);

    try {
      // Verify JWT at the edge (without calling backend!)
      const isValid = await verify(token, env.JWT_SECRET);

      if (!isValid) {
        return new Response('Invalid token', { status: 401 });
      }

      // Decode payload to add to request
      const { payload } = jwt.decode(token);

      // Clone request and add user info to headers
      const modifiedRequest = new Request(request);
      modifiedRequest.headers.set('X-User-Id', payload.userId);
      modifiedRequest.headers.set('X-User-Role', payload.role);

      // Forward to origin
      return fetch(modifiedRequest);

    } catch (error) {
      return new Response('Token verification failed', { status: 401 });
    }
  },
};

This approach verifies authentication at the edge, before the request reaches your backend, saving latency and server load.

AWS Lambda@Edge: Serverless on Amazon's CDN

AWS Lambda@Edge allows running Lambda functions at CloudFront edge locations (200+ locations).

Example: Intelligent Device-Based Redirects

// Lambda@Edge for CloudFront
exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  // Detect device type
  const userAgent = headers['user-agent']?.[0]?.value || '';
  const isMobile = /mobile|android|iphone/i.test(userAgent);
  const isTablet = /tablet|ipad/i.test(userAgent);

  // Redirect to optimized versions
  if (request.uri === '/') {
    if (isMobile) {
      return {
        status: '302',
        statusDescription: 'Found',
        headers: {
          location: [{
            key: 'Location',
            value: '/mobile',
          }],
        },
      };
    }

    if (isTablet) {
      return {
        status: '302',
        statusDescription: 'Found',
        headers: {
          location: [{
            key: 'Location',
            value: '/tablet',
          }],
        },
      };
    }
  }

  // Modify headers for optimization
  request.headers['x-device-type'] = [{
    key: 'X-Device-Type',
    value: isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop',
  }];

  return request;
};

Example: Responsive Image Generation at the Edge

const sharp = require('sharp');
const AWS = require('aws-sdk');
const s3 = new AWS.S3();

exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  const response = event.Records[0].cf.response;

  // Extract query parameters
  const params = new URLSearchParams(request.querystring);
  const width = parseInt(params.get('w') || '800');
  const quality = parseInt(params.get('q') || '80');
  const format = params.get('f') || 'webp';

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

  // Process image with Sharp
  const processedImage = await sharp(s3Object.Body)
    .resize(width, null, { withoutEnlargement: true })
    .toFormat(format, { quality })
    .toBuffer();

  // Return processed image
  return {
    status: '200',
    statusDescription: 'OK',
    headers: {
      'content-type': [{
        key: 'Content-Type',
        value: `image/${format}`,
      }],
      'cache-control': [{
        key: 'Cache-Control',
        value: 'public, max-age=31536000, immutable',
      }],
    },
    body: processedImage.toString('base64'),
    bodyEncoding: 'base64',
  };
};

Vercel Edge Functions: Next.js at the Edge

Vercel Edge Functions are optimized for Next.js and run at the edge globally.

Example: A/B Testing at the Edge

// app/middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  // Random bucket for A/B test (0-99)
  const bucket = Math.floor(Math.random() * 100);

  // 50% of users see variant A, 50% see B
  const variant = bucket < 50 ? 'A' : 'B';

  // Clone response to add cookie
  const response = NextResponse.next();

  // Store variant in cookie (persistence between pages)
  response.cookies.set('ab-test-variant', variant, {
    maxAge: 60 * 60 * 24 * 30, // 30 days
  });

  // Add header for analytics
  response.headers.set('X-AB-Test-Variant', variant);

  // Rewrite based on variant
  if (request.nextUrl.pathname === '/pricing') {
    if (variant === 'B') {
      return NextResponse.rewrite(new URL('/pricing-variant-b', request.url));
    }
  }

  return response;
}

export const config = {
  matcher: ['/pricing', '/checkout'],
};

Example: Rate Limiting at the Edge

import { NextResponse } from 'next/server';

const rateLimit = new Map();

export function middleware(request) {
  const ip = request.ip || 'unknown';
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 minute
  const maxRequests = 100;

  // Clean old entries
  for (const [key, value] of rateLimit.entries()) {
    if (now - value.timestamp > windowMs) {
      rateLimit.delete(key);
    }
  }

  // Check rate limit
  const userLimit = rateLimit.get(ip);

  if (!userLimit) {
    rateLimit.set(ip, { count: 1, timestamp: now });
  } else if (now - userLimit.timestamp > windowMs) {
    // Reset window
    rateLimit.set(ip, { count: 1, timestamp: now });
  } else if (userLimit.count >= maxRequests) {
    // Limit exceeded
    return new NextResponse('Rate limit exceeded', {
      status: 429,
      headers: {
        'Retry-After': String(Math.ceil((windowMs - (now - userLimit.timestamp)) / 1000)),
      },
    });
  } else {
    // Increment counter
    userLimit.count++;
  }

  return NextResponse.next();
}

Modern Serverless Architecture Patterns

1. Backend for Frontend (BFF) Pattern

// Cloudflare Worker acting as BFF
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Different backends for different clients
    if (url.pathname.startsWith('/api/mobile')) {
      // Mobile receives optimized data (fewer fields)
      const data = await fetchFromBackend(env.MOBILE_API);
      return new Response(JSON.stringify({
        ...data,
        _optimized: true,
      }));
    }

    if (url.pathname.startsWith('/api/web')) {
      // Web receives complete data
      const data = await fetchFromBackend(env.WEB_API);
      return new Response(JSON.stringify(data));
    }

    return new Response('Not Found', { status: 404 });
  },
};

2. Event-Driven Serverless

// AWS Lambda triggered by S3 upload
exports.handler = async (event) => {
  const s3Event = event.Records[0].s3;
  const bucket = s3Event.bucket.name;
  const key = s3Event.object.key;

  console.log(`Processing file: ${key} from bucket: ${bucket}`);

  // Process image
  const originalImage = await s3.getObject({ Bucket: bucket, Key: key }).promise();

  // Generate thumbnails in parallel
  const thumbnails = await Promise.all([
    generateThumbnail(originalImage, 200, 200),
    generateThumbnail(originalImage, 400, 400),
    generateThumbnail(originalImage, 800, 800),
  ]);

  // Upload thumbnails to S3
  await Promise.all(thumbnails.map((thumb, i) =>
    s3.putObject({
      Bucket: `${bucket}-thumbnails`,
      Key: `${key}-${[200, 400, 800][i]}px`,
      Body: thumb,
      ContentType: 'image/jpeg',
    }).promise()
  ));

  return { statusCode: 200, body: 'Thumbnails generated' };
};

3. Strategic Caching at the Edge

export default {
  async fetch(request, env, ctx) {
    const cache = caches.default;
    const cacheKey = new Request(request.url, request);

    // Check cache first
    let response = await cache.match(cacheKey);

    if (!response) {
      // Cache miss - fetch from backend
      response = await fetch(request);

      // Cache if status is 200
      if (response.status === 200) {
        // Clone response to cache (can only be consumed once)
        const responseToCache = response.clone();

        // Add to cache (async via waitUntil)
        ctx.waitUntil(cache.put(cacheKey, responseToCache));
      }
    }

    return response;
  },
};

Challenges and Practical Considerations

Cold Starts: The Achilles Heel

Serverless functions suffer from "cold starts" when inactive. Mitigation strategies:

1. Keep-Alive Pings: Periodically invoke functions to keep them warm
2. Provisioned Concurrency (AWS): Keep instances always warm (additional cost)
3. Edge Computing: Workers have drastically smaller cold starts (<10ms)

Execution Limits

AWS Lambda: 15 minutes maximum
Cloudflare Workers: 50ms CPU time (free plan), 50ms-30s (paid plan)
Vercel Edge: 30 seconds

For long processing, consider splitting into multiple functions or using Step Functions.

Monitoring and Debugging

Serverless makes traditional debugging difficult. Use:

  • Structured Logging: JSON logs for easy parsing
  • Distributed Tracing: AWS X-Ray, Datadog, Sentry
  • Metrics: CloudWatch, Prometheus to monitor latency and errors

The Future: Edge-First Architecture

In 2025, the trend is clear: edge-first. Modern applications are being designed from the start to run at the edge, leveraging:

  • Global low latency: Users in Brazil and Japan have the same experience
  • Automatic scalability: From 0 to millions without configuration
  • Optimized cost: Pay only for what you use
  • Resilience: Globally distributed, no single point of failure

If you're building modern web applications, serverless and edge computing are no longer "nice to have" - they're essential to compete in performance and user experience.

If you feel inspired by the power of serverless and edge computing, I recommend checking out another article: Microfrontends: Modular Architecture where you'll discover how to combine edge computing with microfrontend architecture to create truly scalable applications.

📚 Want to Deepen Your JavaScript Knowledge?

This article covered serverless and edge computing, but there's much more to explore in modern development.

Developers who invest in solid, structured knowledge tend to have more opportunities in the market.

Complete Study Material

If you want to master JavaScript from basics to advanced, I've prepared a complete guide:

Investment options:

  • $4.90 (single payment)

👉 Learn About JavaScript Guide

💡 Material updated with industry best practices

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments