Netlify Edge Handlers are in Early Access (you can request it), but they are super cool and I think they are worth wrapping your brain around now. I think they change the nature of what Jamstack is and can be.
You know about CDNs. They are global. They host assets geographically close to people so that websites are faster. Netlify does this for everything. The more you can put on a CDN, the better. Jamstack promotes the concept that assets, as well as pre-rendered content, should be on a global CDN. Speed is a major benefit of that.
The mental math with Jamstack and CDNs has traditionally gone like this: I’m making tradeoffs. I’m doing more at build time, rather than at render time, because I want to be on that global CDN for the speed. But in doing so, I’m losing a bit of the dynamic power of using a server. Or, I’m still doing dynamic things, but I’m doing them at render time on the client because I have to.
That math is changing. What Edge Handlers are saying is: you don’t have to make that trade off. You can do dynamic server-y things and stay on the global CDN. Here’s an example.
You have an area of your site at /blog and you’d like it to return recent blog posts which are in a cloud database somewhere. This Edge Handler only needs to run at /blog, so you configure the Edge Handler only to run at that URL.
You write the code to fetch those posts in a JavaScript file and put it at: /edge-handlers/getBlogPosts.js.
Now, when you build and deploy, that code will run — only at that URL — and do its job.
So what kind of JavaScript are you writing? It’s pretty focused. I’d think 95% of the time you’re outright replacing the original response. Like, maybe the HTML for /blog on your site is literally this:
With an Edge Handler, it’s not particularly difficult to get that original response, make the cloud data call, and replace the guts with blog posts.
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; }); }
(I’m hitting this site’s REST API as an example cloud data store.)
It’s a lot like a client-side fetch, except instead of manipulating the DOM after request for some data, this is happening before the response even makes it to the browser for the very first time. It’s code running on the CDN itself (“the edge”).
So, this must be slower than pre-rendered CDN content then, because it needs to make an additional network request before responding, right. Well, there is some overhead, but it’s faster than you probably think. The network request is happening on the network itself, so smokin’ fast computers on smokin’ fast networks. Likely, it’ll be a few milliseconds. They are only allowed 50ms of execution time anyway.
I was able to get this all up and running on my account that was granted access. It’s very nice that you can test them locally with:
netlify dev --trafficMesh
…which worked great both in development and deployed.
Anything you console.log() you’ll be able to set in the Netlify dashboard as well:
Some very cool news from Netlify: Edge Handlers are in Early Access (request it here). I think these couple of lines of code do a great job in explaining what an Edge Handler is:
export function onRequest(event) { console.log(`Incoming request for $ {event.request.url}`); event.replaceResponse(() => fetch("https://www.netlify.com/")); }
So that’s a little bitty bit of JavaScript that runs at “the edge” (at the CDN level) for every request through your site. In the case above, I’m totally replacing the response with an Ajax request for another URL. Weird! But cool. This has incredible power. I can replace the response with a slight-manipulated response. Say, change the headers. Or check who the logged-in user is, make a request for data on their behalf, and inject that data into the response. 🤯
So you might think of Jamstack as either pre-render or get data client-side. This is opening up a new door: build your response dynamically at the edge.
What’s nice about the Netlify approach is that the code that runs these sits right alongside the rest of your code in the repo itself, just like functions.
Events are used to respond when a user clicks somewhere, focuses on a link with their keyboard, and changes the text in a form. When I first started learning JavaScript, I wrote complicated event listeners. More recently, I’ve learned how to reduce both the amount of code I write and the number of listeners I need.
Let’s start with a simple example: a few draggable boxes. We want to show the user which colored box they dragged.
I wrote separate event listener functions for each element when I first started learning about JavaScript events. It’s a common pattern because it’s the simplest way to start. We want specific behavior for each element, so we can use specific code for each.
The event listeners in that example are all very similar: each function displays some text. This duplicate code can be collapsed into a helper function.
This is much cleaner, but it still requires multiple functions and event listeners.
Taking advantage of the Event object
The Event object is the key to simplifying listeners. When an event listener is called, it also sends an Event object as the first argument. This object has some data to describe the event that occurred, such as the time the event happened. To simplify our code, we can use the evt.currentTarget property where currentTarget refers to the element that the event listener is attached to. In our example, it will be one of the three colored boxes.
Now there is only one function instead of four. We can re-use the exact same function as an event listener and evt.currentTarget.href will have a different value depending on the element that fires the event.
Using bubbling
One final change is to reduce the number of lines in our code. Rather than attaching an event listener to each box, we can attach a single event listener to the <section> element that contains all the colored boxes.
An event starts off at the element where the event originated (one of the boxes) when it is fired. However, it won’t stop there. The browser goes to each parent of that element, calling any event listeners on them This will continue until the root of the document is reached (the <body> tag in HTML). This process is called “bubbling” because the event rises through the document tree like a bubble.
Attaching an event listener to the section will cause the focus event to bubble from the colored box that was dragged up to the parent element. We can also take advantage of the evt.target property, which contains the element that fired the event (one of the boxes) rather than the element that the event listener is attached to (the <section> element).
Now we’ve reduced many event listeners to just one! With more complicated code, the effect will be greater. By utilizing the Event object and bubbling, we can tame JavaScript events and simplify code for event handlers.
What about click events?
evt.target works great with events like dragstart and change, where there are only a small number of elements that can receive focus or have input changed.
However, we usually want to listen for click events so we can respond to a user clicking on a button in an application. click events fire for any element in the document, from large divs to small spans.
Let’s take our draggable color boxes and make them clickable instead.
When testing this code, notice that sometimes nothing is appended to “Clicked” instead of when clicking on a box. The reason that it doesn’t work is that each box contains a <span> element that can be clicked instead of the draggable <div> element. Since the spans don’t have a set ID, the evt.target.id property is an empty string.
We only care about the colored boxes in our code. If we click somewhere inside a box, we need to find the parent box element. We can use element.closest() to find the parent closest to the clicked element.
const preview = evt => { const element = evt.target.closest('div[draggable]'); if (element != null) { const color = element.id; document.querySelector('#clicked').textContent = `Clicked $ {color}`; } };
Now we can use a single listener for click events! If element.closest() returns null, that means the user clicked somewhere outside of a colored box and we should ignore the event.
More examples
Here are some additional examples to demonstrate how to take advantage of a single event listener.
Lists
A common pattern is to have a list of items that can be interacted with, where new items are inserted dynamically with JavaScript. If we have event listeners attached to each item, then y\our code has to deal with event listeners every time a new element is generated.
<div id="buttons-container"></div> <button id="add">Add new button</button>
let buttonCounter = 0; document.querySelector('#add').addEventListener('click', evt => { const newButton = document.createElement('button'); newButton.dataset.number = buttonCounter; // Make a new event listener every time "Add new button" is clicked newButton.addEventListener('click', evt => { // When clicked, log the clicked button's number. document.querySelector('#clicked').textContent = `Clicked button #$ {newButton.textContent}`; }); buttonCounter++; const container = document.querySelector('#buttons-container'); container.appendChild(newButton); });
By taking advantage of bubbling, we can have a single event listener on the container. If we create many elements in the app, this reduces the number of listeners from n to two.
Now we know how to take advantage of event bubbling and the event object to simplify complex jumbles of event handlers into just a few… and sometimes down to just one! Hopefully this article has helped you think about your event handlers in a new light. I know this was a revelation to me after I’d spent my early development years writing duplicative code to accomplish the same thing.