Where Dynamic Logic Can Live

Netlify Edge Handlers (currently in Early Access) point to a different way of thinking about Jamstack. The usual mental model has been a tradeoff: move work to build time so content can sit on a global CDN for speed, but give up the ability to do server-side dynamic work at request time. Or keep dynamism but push it to the client at render time because there's no other option.

Edge Handlers change that equation. They make it possible to run server-like logic while still serving responses from the CDN edge. The execution target is a JavaScript file placed in the project that runs on the CDN itself, not on an origin server and not in the browser.

A Blunt Example

Say a section of the site at /blog needs to return the most recent posts, which live in a cloud database. An Edge Handler can be configured to run only at that URL. The project would contain a file at /edge-handlers/getBlogPosts.js with the logic that fetches those posts. On build and deploy, that code runs when /blog is requested, and only there.

Most handlers do one straightforward thing: replace the original response entirely. If the HTML served for /blog is little more than a shell, the handler can take that original response, call the cloud data source, and swap in the actual post content:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test a Netlify Edge Function</title>
</head>
<body>
  <div id="blog-posts"></div>
</body>
</html>

The interaction looks a lot like the fetch pattern used client-side, but the timing is different. Instead of requesting data after load and patching the DOM, this happens before the first response ever reaches the browser.

export function onRequest(event) {
  event.replaceResponse(async () => {
    // Get the original response HTML
    const originalRequest = await fetch(event.request);
    const originalBody = await originalRequest.text();

    // Get the data
    const cloudRequest = await fetch(
      `https://css-tricks.com/wp-json/wp/v2/posts`
    );
    const data = await cloudRequest.json();

    // Replace the empty div with content
    // Maybe you could use Cheerio or something for more robustness
    const manipulatedResponse = originalBody.replace(
      `<div id="blog-posts"></div>`,
      `
        <h2>
          <a href="${data[0].link}">${data[0].title.rendered}</a>
        </h2>
        ${data[0].excerpt.rendered}
      `
    );

    let response = new Response(manipulatedResponse, {
      headers: {
        "content-type": "text/html",
      },
      status: 200,
    });

    return response;
  });
}

In this case, the handler hits a site's REST API as a stand-in for any cloud data store.

Speed and Limits

Because the handler runs on the network itself, the extra hop is between fast machines on fast connections. The added latency is typically on the order of milliseconds, not perceptible delays. Handlers are also constrained to a maximum of 50ms of execution time.

Local testing is baked into the workflow. Netlify Dev supports running handlers locally, which worked cleanly in both development and after deployment:

netlify dev --trafficMesh

Handlers can also be managed and observed from the Netlify dashboard, including anything sent to the console:

The complete working example, including the handler code and configuration, is available in the test_an_edge_function repository.