Delivering faster user experiences with Vercel Edge Functions and Fauna
Accelerate development of your frontend apps with Fauna's database delivered as an API
Watch a technical deep dive into how you can ship dynamic frontend applications using Fauna integrated with frontend platforms like Netlify and Vercel.
Learn how Fauna’s database delivered as an API can help you accelerate development and give you the confidence to scale.
It's a debate as old as time (or at least as old as the Internet): Where should our website's data come from?
By the early 2010s, everybody had their own idea about this. All the webmasters were hydrating their pages differently, and they all had their reasons. But as the years passed and the technology matured, we started to settle into two main camps:
Static only
- Any content that doesn't change per request is baked into the static HTML at build-time
- Any dynamic data is hydrated on the client side, but that's used very sparingly
- Because the HTML is baked ahead of time, it can be pushed out to the edge — the network of global servers who can serve a page from the closest location to the user in milliseconds
Server rendered
- Only basic scaffolding that is consistent between pages is actually stored in HTML
- As much data as possible is stored in traditional databases
- Pages are hydrated on request — as soon as you visit a site, that HTML is built on-demand for you by a server that isn't optimized for the user's physical location
What if there is a compromise between static and server?
Recently, there's been a lot of hype about that. There are a couple different ways to bridge the gap between static and dynamic:
- Use a serverless function to ping third-party services for extra features like authentication. Here's an example of Zach Leatherman doing that with an Eleventy site.
- You could build a PWA with little pieces of dynamically generated content, while the rest is prebuilt.
- Perhaps configure your site to pull in all data from a headless CMS from the client side and hydrate the page with dynamic content.
There are plenty of options; those are just the first three that came to mind. The catch is that they all have a key flaw: option 1 is cumbersome, option 2 is slow and often inaccessible, and option 3 will make your visitors wait behind spinning loading wheels. Can we have our cake and eat it too with the benefits of static and dynamic sites?
There is a better solution.
What if I told you that we could not only push raw static HTML out to the edge, but also the dynamic code that produces it?
They exist, and they're called edge functions.
By pushing some of that business logic to the edge and running our serverless functions there, we can get the best of both worlds:
- Everything is optimized to the user's location
- No server setup
- Microservice architectural style
- Crazy speedy serving time since most of it is static
Vercel, the hosting company behind Next.JS, is one of the leaders in this space. Let's give their edge function beta integration a shot and see what we can build!
Let's build some stuff.
Let's start with a slightly modified (and TypeScriptified) version of the sample function on Vercel's main feature page:
// pages/_middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest, NextFetchEvent } from 'next/server';
export default function middleware (request: NextRequest, event: NextFetchEvent) {
return new NextResponse({
ip: request.ip,
geo: request.geo,
ua: request.ua
});
};
Make sure to visit that page at https://vercel.com/features/edge-functions to see the results. They don't explain it very much there though, so let's dissect it a bit:
// pages/_middleware.js
: This function is a part of a Next.JS app. Inside Next.JS, we're taking advantage of something called "middleware" — code that runs before a request is completed. What we're writing here is an edge function, which is just a special type of middleware that just runs at the edge.export default function
: We're writing a function here that is going to run every time we get a request to this endpoint. The function doesn't have to be namedmiddleware
, it can be named whatever you'd like.request
: Middleware edge functions take two arguments,request
andevent
.request
is the typical[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)
class with a couple fancy new additions, like user location data and a cookie interface.event
: This is the other argument that goes into our edge function, and its just an extended version of the usual FetchEvent.NextResponse
: This is just the usual Response object, but like the last two notable variables, it has a few tricks up its sleeve like redirecting and some middleware-specific routing stuff. Definitely check out these docs if you're curious about the details.
So in short, Next gives us a couple extended versions of the typical classes we use in normal serverless functions, and we can swap them in and make ourselves something that acts like a server-side monolithic language like PHP while having almost all the benefits of static sites.
OK, that's cool. Where is it useful?
That example above was helpful to understand the technology, but that alone is rather useless; really, when are you going to make a function just to tell the user what region of the world they're in?
But the data that we can get in middleware functions like this is actually very useful. For example, when you're using a Database-as-a-Service like Fauna, you might make use of Region Groups, which let you isolate a whole database to a specific region of the world. One common reason for doing this is to comply with locale-based privacy regulations like GDPR without resorting to applying the most stringent common denominator to every region. Knowing what region your user is in by the edge node that they connected to on the CDN means that you know which region group they should access. That, and we still have the added performance benefits of doing all of this from the edge instead of from our typical microservices.
Here's a quick demo of how this would work:
// pages/_middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest, NextFetchEvent } from 'next/server';
export default async function middleware (request: NextRequest, event: NextFetchEvent) {
const CLIENT_SECRET : string = process.env.FAUNA_KEY;
// Here we get the right Region Group (RG) for the user.
// If the user is in the United States, use the US RG.
// Otherwise, use the Europe RG.
// Fauna plans to add add more Region Groups in the future.
const REGION : string = request.geo.country == "US" ? "us" : "eu";
const FAUNA_GRAPHQL_BASE_URL : string = `https://graphql.${REGION}.fauna.com/graphql`;
// here's a sample query
// the exact query doesn't matter, this function will just return whatever the query does
const query : string = `
query Entries($size: Int) {
entries(_size: $size) {
data {
_id
_ts
name
message
createdAt
}
}
}
`;
// this section actually runs the request
const response = await fetch(
FAUNA_GRAPHQL_BASE_URL,
{
method: "POST",
headers: {
authorization: `Bearer ${CLIENT_SECRET}`
},
body: JSON.stringify({ query })
}
};
const results = await response.json();
// just return whatever the query did, for simplicity
// in a real-world application, you'd likely be running some business logic here
return new NextResponse({ results });
};
I threw some comments in there that attempt to explain what each section is doing. Straight from the beginning, we're making use of the request
parameter and getting the user's rough location. By reaching into that object and getting the country, we can connect them to the United States region group in Fauna if they're in the States, or if not, then connect them to the European region group.
So in just a few lines of code, we have our cake and eat it too! We've got:
- Fantastic performance, since this is all happening at the edge
- Fine-grained control over locale-based privacy regulation compliance
- The microservices architecture that keeps all of our concerns separate from each other
- No rigamarole to get it working in Next.JS, an easy-to-pickup framework that many of us are already familiar with
- Simple, familiar, GraphQL-based querying
- TypeScript support! (I think I hear @shaundai giggling in the distance)
If you want to learn more about edge functions and Next.JS middleware, you're in luck! This community has been churning out tons of amazingly helpful docs, tutorials, guides, videos, and articles on the topic, so give this list a gander:
If you enjoyed our blog, and want to work on systems and challenges related to globally distributed systems, and serverless databases, Fauna is hiring
Subscribe to Fauna's newsletter
Get latest blog posts, development tips & tricks, and latest learning material delivered right to your inbox.