Skip to content

    Frontend System Design

    Edge Rendering: What Runs Near the User, and What Should Not

    Learn what edge runtimes like Cloudflare Workers can do, their documented limits, and how distance to your data decides whether edge rendering pays off.

    By Published 12 min read

    • frontend-system-design
    • edge
    • workers
    • latency
    • cdn
    Share

    Standard lessonadvancedFrontend System Design

    Before this: Rendering Strategies: CSR, SSR, SSG, ISR and Streaming, CDN and Edge Caching From the Browser's Side (coming soon)

    What you'll learn: By the end of this lesson you will be able to separate the three things people call "the edge" (a CDN cache, small edge functions and edge rendering), describe what an edge runtime is and what its documented limits are for Cloudflare Workers and Vercel's Edge runtime, explain why a render at the edge can be slower than one at a regional origin, use placement and caching to reduce that cost, and decide per piece of logic whether it belongs at the edge.

    A bank has a head office in one city and branches in many. A branch can answer "what are your opening hours?" at once and can stamp a form. "What is my balance?" needs a phone call to head office, and a statement that needs four such calls is slower than walking into head office yourself. Edge rendering is the same trade: a branch near you is only a win if it can answer without too many calls home.

    The running example is an online store's product page. Shoppers are spread across continents, but the product database is in one region. The page needs a redirect for old URLs, a country-specific banner, a check for a login cookie, and four database reads for product, price, stock and recommendations. Which of those belong near the shopper?

    The core idea

    The edge is where your code runs close to the user, in many locations, instead of in one region next to your data. Three statements follow.

    1. "Edge" covers different things. A CDN cache serves stored responses. An edge function runs small code on a request. Edge rendering produces HTML at the edge.
    2. Edge code runs in a constrained runtime with Web-standard APIs, not a full Node.js process.
    3. Distance to the user is only half the latency. A render also pays for every round trip between the compute and its data. Moving compute away from the data can make a page slower.

    Figure 1. Where code can run

    A request can be answered at an edge location or travel to the regional origin that sits next to the databaseShopper to CDN cache (hit); Shopper to Edge function (per request); Edge function to Regional origin (miss or data); Regional origin to Database (queries)MANY EDGE LOCATIONSONE ORIGIN REGIONShopperShopperCDN cache: stored responsesCDN cachestored responsesEdge function: small code per requestEdge functionsmall code perrequestRegional origin: Node.js renderRegionaloriginNode.js renderDatabase: one regionDatabaseone regionhitper requestmiss or dataqueries

    How to read this: the shorter the path a request takes, the faster it is. A cache hit or a small function stays at the left. Anything that needs the database must cross to the right, and the question is who makes that trip.

    What an edge runtime is

    Documented facts, read on 2026-11-08 from each vendor's pages.

    Cloudflare Workers. The runtime is built on V8 but isolates code in a lightweight context rather than a container or VM. By default a Worker runs in the data center that receives the request. Cloudflare says an isolate can start "around a hundred times faster" than a Node process on a container or VM, which is the vendor's own comparison. It also advises against mutable global state, because isolates "are not necessarily long-lived".

    Vercel's Edge runtime. Also V8-based, in isolated environments with no container or VM, and by default in the region closest to the request. It offers a subset of Web APIs (fetch, Request, Response, streams, crypto) and a few Node modules such as events, buffer and util.

    What that means for your code:

    • Some Node.js APIs are not available. You cannot read or write the filesystem. require is not allowed, so use import.
    • node_modules can be used if they are ES modules and use no native Node APIs. Vercel notes that most libraries depending on Node APIs cannot be used.
    • eval and new Function(string) are disabled for security. A library that relies on them fails with a runtime error.

    Illustration 1: the minimal example. A function that needs no data, only URL and Response, which both vendors list. It was run in Node 26 with a stand-in request, printing 308 https://example.com/products/shoes?c=1 for /shop/shoes?c=1 and 404 for /about. It was not run on either platform.

    export default {
      async fetch(request) {
        const url = new URL(request.url);
        if (url.pathname.startsWith("/shop/")) {
          url.pathname = url.pathname.replace("/shop/", "/products/");
          return Response.redirect(url.toString(), 308);
        }
        return new Response("Not handled here", { status: 404 });
      },
    };

    Documented limits

    Limits change, so treat the table as a snapshot and open the pages.

    Platform (page date) Limit as documented
    Cloudflare Workers (updated 2026-10-29) CPU time per HTTP request: 10 ms on Free; on Paid 30 seconds by default, up to 5 minutes. Memory 128 MB per isolate. Subrequests per request: 50 Free, 10,000 Paid. Worker size 64 MiB uncompressed. Startup 1 second. Six simultaneous open connections. Time waiting on fetch() or other I/O does not count as CPU time.
    Vercel Edge runtime (updated 2026-08-03) Must begin a response within 25 seconds, then may stream for up to 300 seconds. Code size after gzip: 1 MB Hobby, 2 MB Pro, 4 MB Enterprise, counting imported libraries and bundled files.

    The runtime is being moved away from in Next.js

    The Next.js 16 documentation says the Edge Runtime is deprecated, tells you to remove export const runtime = 'edge', and says Proxy (the renamed middleware) defaults to Node.js and does not accept a runtime option. Vercel's page recommends migrating from edge to Node.js, and says that starting in Next.js 16.3 setting runtime = 'edge' is no longer supported. The two pages use different words, so check the version you run. The Proxy page also says it may be "deployed to your CDN" in optimized cases. So in a current Next.js app, page rendering is regional Node.js, while "edge" mostly means cache plus small routing logic. Other frameworks and Workers still run code at the edge.

    Data locality: why rendering at the edge can lose

    Rendering needs data. Each query is a round trip between where the code runs and where the data lives. Cloudflare's placement page says it directly: if a Worker makes many round trips to a database or API, the latency adds up, and "it may be more performant to run that Worker closer to your back-end than the end user".

    Illustration 2: the realistic example. The product page needs four database reads, one after another. Take illustrative round-trip times, not measurements: shopper to nearest edge 20 ms; shopper to the origin region 140 ms; edge to database 120 ms; origin to database 2 ms.

    Where the page is rendered Arithmetic Total
    Edge, database far away 20 + 4 x 120 500 ms
    Regional origin beside the database 140 + 4 x 2 148 ms
    Edge cache hit 20 20 ms

    Figure 2. Four sequential reads from the edge

    sequenceDiagram accTitle: An edge render making sequential reads to a distant database accDescr: The shopper requests a product page from the nearby edge function. The function reads the database four times in a row, each read crossing the long distance to the one database region, and only then sends the page. participant U as Shopper participant E as Edge function participant D as Database (one region) U->>E: GET /products/123 (short hop) E->>D: read product (long hop) D-->>E: row E->>D: read price (long hop) D-->>E: row E->>D: read stock (long hop) D-->>E: row E->>D: read recommendations (long hop) D-->>E: rows E-->>U: HTML

    How to read this: every "long hop" is paid in full before the next one starts. Running the four reads in parallel, or moving the function next to the database, shortens the page far more than moving it nearer the shopper.

    You have four documented levers:

    • Placement. Cloudflare's Smart Placement ("placement": { "mode": "smart" }) compares a Worker's request duration across locations where it has run and moves it only when a candidate is significantly faster. It cannot place a Worker where it never got traffic, and analysis can take up to 15 minutes. You can also hint a region such as aws:us-east-1. On Vercel, you set a function's region so it runs near its data, and the default for new projects is iad1.
    • Fewer round trips. Batch or parallelise reads. This helps wherever the code runs.
    • Caching. The cache hit in the table removes the data trip entirely. The ISR lesson covers keeping it fresh.
    • Replicated data. Cloudflare KV is eventually consistent: changes are usually visible at the location that made them, but may take "up to 60 seconds or more" to appear elsewhere, and the page says not to rely on same-location visibility. For stronger guarantees it recommends Durable Objects. Replicated edge data suits content that tolerates staleness, not a stock count.

    Edge logic and the cache

    Edge logic sits in front of a cache, so it shapes the cache key. Vercel's CDN cache is segmented by region, and responses with Vary on a high-cardinality header such as Cookie are not cached at all. A country-specific variant that Varys on a country header gets one cache entry per country. Cloudflare says stale-while-revalidate is not supported with its Workers Cache API. So an edge function that personalises a page can quietly stop it being cacheable. Personalise a small piece, or the cache key, not the whole body.

    Choosing what runs where

    Figure 3. Where should this logic run?

    flowchart TD accTitle: Choosing where logic runs accDescr: A decision tree. Logic that needs no data runs at the edge. Logic that needs data that can be cached or replicated can run at the edge with a cache. Logic that needs consistent data from one region runs at a regional origin, with an edge cache in front where possible. Q1{"Does it need
    data from a store?"} -->|"no"| E1["Edge function
    (redirect, rewrite, headers)"] Q1 -->|"yes"| Q2{"Can the data be cached
    or tolerate staleness?"} Q2 -->|"yes"| E2["Edge with cache
    or replicated store"] Q2 -->|"no"| Q3{"Few reads,
    or can they be parallel?"} Q3 -->|"yes"| O1["Edge may work: measure"] Q3 -->|"no"| O2["Regional render near
    the data, cache in front"]

    How to read this: the first question is whether any data is needed at all. Most of the logic that earns its place at the edge answers "no".

    Where Latency to user Data access Runtime Best for
    CDN cache Lowest None (stored copy) Not your code Static files, cacheable pages
    Edge function Low Cached, replicated, or long hops Isolate with Web APIs and limits Redirects, rewrites, header changes, cookie checks
    Edge rendering Low to the user, high to the data Same as above Same limits Pages needing little or replicated data
    Regional origin Higher to the user Close to the database Full Node.js Pages with many consistent reads

    In practice: moving a storefront's rendering to the edge

    NOTE

    This is a realistic scenario, not a case history. It is a composite built from common production patterns. It describes no specific company or product, and every number in it is an illustrative assumption.

    The situation

    An online store renders product pages on a regional Node.js server beside its database. Shoppers far from that region report slow pages, and a proposal says: move all rendering to the platform's edge runtime. Each page makes four sequential reads. The platform allows regional or edge execution per route.

    Starting architecture

    Figure 4. The starting architecture

    Starting architecture: all rendering in one region beside the database, with an optional CDN in frontShopper to CDN (GET); CDN to Node.js render (long hop); Node.js render to Database (4 reads)Shopper: any continentShopperany continentCDN: static files onlyCDNstatic files onlyNode.js render: every page requestNode.js renderevery pagerequestDatabase: same regionDatabasesame regionGETlong hop4 reads

    How to read this: the render is fast, but every page request makes the long hop from the shopper to the origin.

    Options considered

    Option Fixes Costs Risk
    Move all rendering to the edge Short hop for the shopper Rewrite for the runtime Four long hops to the database
    Edge function for routing only, regional render Redirects and country choice are instant A second deployment target Two places to debug
    Cache rendered pages at the CDN Most requests never reach the origin Invalidation work Stale prices without a purge
    Both: routing at the edge, cached regional render Each piece where it fits The most moving parts Cache key mistakes

    The decision

    Field Decision record
    Context Distant shoppers pay a long hop; the data is in one region.
    Decision Keep rendering at the regional origin. Run redirects, the country choice and the cookie check as edge functions. Cache product pages at the CDN.
    Consequences, good The long hop disappears for cached pages; no page pays four long hops.
    Consequences, bad Two runtimes; edge code is limited to Web APIs.
    Revisit when The data is replicated near users, or a page needs one read. Then edge rendering becomes worth measuring.

    Rollout and verification

    1. Measure before moving: lab-measure time to first byte for one product page from two regions, so you have a baseline.
    2. Ship the edge function alone behind a flag and check that cache hit rates do not fall.
    3. Compare both regions' time to first byte after the CDN rule.
    4. Add a CI check that bundles the edge code for its target and reports its size against the platform limit.

    The 2 a.m. failure

    A dependency bump in the edge function brings in a library that reads a file with a Node API. Pages using the cookie check start returning errors, but only on paths that load that library. The platform's docs say such APIs are unavailable and that code relying on eval leads to a runtime error.

    First three checks: (1) read the error log for "not supported" or "eval"; (2) diff the lockfile for the dependency that changed; (3) check which routes load it. Mitigation: roll back the dependency or send those routes to the regional origin. Follow-up: run the edge bundle in CI, add an allow-list for imports, and watch the size limit.

    Architect's notes

    • Ask "what data?" first. If the answer is "none", the edge is easy. If it is "a consistent database read", it usually is not.
    • Count round trips, not milliseconds. A page that needs sequential reads pays the distance each time.
    • Treat limits as a contract. CPU time, size and memory are limits you design against, and they change; re-read the pages.
    • Keep the portable part portable. Write edge logic with Request, Response and URL so it moves between platforms.
    • Measure from the user's region, in the lab and in the field, not from your office.

    Common mistakes

    • Assuming edge is always faster. Four long hops to a distant database are slower than one hop to a nearby origin.
    • Putting a database client in an edge function without checking the runtime supports it.
    • Reading a Node.js tutorial as edge guidance. The runtimes differ.
    • Varying a cached response on a high-cardinality header. The cache stops working.
    • Storing mutable state in global scope. Isolates can be evicted.
    • Ignoring the framework's direction. In Next.js 16 the edge runtime is deprecated.

    Key takeaways

    • "Edge" means a CDN cache, small edge functions, or edge rendering; they have different costs and limits.
    • Edge runtimes are V8 isolates with a subset of Web APIs: no filesystem, no require, no eval.
    • Documented limits include CPU time, memory, size and response deadlines; they vary by plan and change over time.
    • A render pays every round trip to its data. A distant database can make edge rendering slower than a regional origin.
    • Placement, fewer round trips, caching and replicated data are the levers; replicated data may be stale.
    • In Next.js 16 the Edge Runtime is deprecated; Proxy runs on Node.js by default.
    • Most logic that belongs at the edge needs no data at all.

    Practice exercises

    Exercise 1: Starter (30 min). For a page with 3 sequential reads, an edge-to-database round trip of 80 ms and an origin-to-database round trip of 3 ms (both illustrative), compute the data-trip cost at each place.

    Exercise 2: Intermediate (2 hours). List the logic in an app you know (redirects, auth checks, rendering, personalisation) and mark each item "no data", "cacheable data" or "consistent data". Say where each should run.

    Exercise 3: Advanced (half day). Read the Workers limits page and write a one-page budget for a function you would run there: CPU time, subrequests and size. Name the first limit it would hit.

    In this course:

    Elsewhere on the site:

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments