惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LangChain Blog
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
The Cloudflare Blog
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
Martin Fowler
Martin Fowler
小众软件
小众软件
量子位
月光博客
月光博客
P
Proofpoint News Feed
IT之家
IT之家
腾讯CDC
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
雷峰网
雷峰网
V
Visual Studio Blog

Netlify Developers

AI model comparison using Netlify's AI Gateway | Netlify Developers Build an AI agent to automatically process and summarize form submissions | Netlify Developers Build an AI agent to automatically generate relevant images for blog posts | Netlify Developers Prerendering SPA content pages to enable AI agent access | Netlify Developers Identify image optimization opportunities with Netlify Observability | Netlify Developers Fix top 404 page not found errors with Netlify Observability | Netlify Developers Introducing AI Gateway: built-in AI model access on Netlify | Netlify Developers Introducing Observability: your project insights on Netlify | Netlify Developers From first publish to first update with AI agents | Netlify Developers How to use Agent Runners on Netlify | Netlify Developers Add forms to your project with AI + Netlify | Netlify Developers Track Real User Metrics | Netlify Developers Lighthouse performance scores on Netlify | Netlify Developers Review your AI project before you ship | Netlify Developers Using environment variables in AI Projects on Netlify | Netlify Developers Build prototypes and MVPs fast with AI + Netlify | Netlify Developers Pause auto-publishing on Netlify | Netlify Developers Run serverless functions, storage, and more natively in Nuxt dev | Netlify Developers Netlify Office Hours: The AX Arcade challenge | Netlify Developers Netlify Office Hours: Prompting with style | Netlify Developers Netlify Office Hours: RAG Apps with OpenAI + Netlify DB | Netlify Developers How to build a RAG application with Neon, Netlify & OpenAI | Netlify Developers Netlify Office Hours: Netlify DB (powered by Neon) | Netlify Developers Netlify Office Hours: Netlify MCP Server | Netlify Developers Netlify Office Hours: Netlify Vite Plugin | Netlify Developers Building MCPs with Netlify | Netlify Developers Deploying sites from your AI tool | Netlify Developers Adding your domain using Netlify API | Netlify Developers Build a context driven chat bot with Netlify Blob and OpenAI | Netlify Developers Simplify deployments with Netlify’s branch-matching environment variables | Netlify Developers
How to make edge rendering fast | Netlify Developers
2024-04-30 · via Netlify Developers

After some initial hype around edge rendering, the mood in the front-end ecosystem has muted down somewhat after the surprising discovery that most datasets that people want to render on top of, sits in a database that runs in a single region and that’s not going to magically get distributed all over the world. This post will show an approach to fast edge rendering on top of normal centralized APIs by using smart caching and cache invalidation.

#TL;DR

Edge rendering can be fast if combined with edge API caching. This is a simple guide to using Remix with edge rendering and Netlify Functions as a backend for the front-end that uses stale-while-revalidate to keep API responses fast at the edge.

To illustrate the concept, we’re going to build a really simple Hacker News clone with Remix, just showing the main front-page list of stories and rendering them on the edge as a simple unstyled list.

By following these steps below, you’ll get your own example built and deployed to Netlify using Remix and configured to use Netlify’s advanced caching controls.

#Creating a Remix application

Let’s start by creating a new Remix application with edge rendering:

npx create-remix@latest --template netlify/remix-template

You’ll get prompted how you want to run your Remix project. Pick “Netlify Edge Functions” to do edge rendering.

Now you can open the project in your code editor of choice and start the local dev server by running netlify dev.

#Creating a small backend for the front-end

Your new Remix app will render in Netlify’s edge functions, and we’ll want to fetch the list of front-page stories from the Hackernews API.

Like many other API’s, the Hackernews API runs from a datacenter on the east coast of the US. Here’s a quick view of global latency to the top stories endpoint:

Hacker News API global latency

To make matters worse, the top stories endpoint we need to get our list of stories just returns a list of IDs.

[ 40192204, 40190542, 40192359, 40191047, 40187656, ...]

To render a list of each story, we’ll have to do an extra API request for each story.

If we implement this directly in the loader of our Remix app, we would be much, much better off running the remix rendering in us-east-1 where the roundtrips to the Hackernews API are short, versus globally distributed where the latency to the API would really hurt performance since each roundtrip for a story could have hundreds of milliseconds of latency.

So we’re going to take another approach and create a Netlify Function that acts as the backend, this function we can deploy in US East 1 so the many roundtrips to the API has as low latency as possible.

We’ll implement this function by creating a new file, /netlify/functions/stories.ts with the following content:

export default async () => {

const resp = await fetch("https://hacker-news.firebaseio.com/v0/topstories.json");

const ids = await resp.json();

const stories = await Promise.all(

ids.slice(0, 100).map(async (id) => {

const story = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);

return story.json();

})

);

return new Response(JSON.stringify(stories), {

headers: {

"content-type": "application/json",

},

});

};

This will give us an endpoint for our site on the route /.netlify/functions/stories that returns the most recent stories in a format ready to render from:

[{"by":"CommieBobDole","descendants":46,"id":40190542,"kids":[40190798,40192389,40191773,40190714,40191293,40191418,40190648,40191343,40190948],"score":251,"time":1714328186,"title":"A small lathe built in a Japanese prison camp","type":"story","url":"<http://www.lathes.co.uk/bradley-pow-lathe/"},{"by":"_bramses","descendants":2,"id":40192359,"kids":[40192565],"score":26,"time":1714341420,"title":"Personal> computing paves the way for personal library science","type":"story","url":"<https://www.bramadams.dev/issue-55/>"}, ...]

#Remix Edge Rendering

Now lets use this endpoint from our Remix app through a loader. Change the app/routes/_index.tsx to the following:

import type { MetaFunction, LoaderFunction } from "@netlify/remix-runtime";

import { useLoaderData } from "@remix-run/react";

export const meta: MetaFunction = () => {

return [{ title: "Edge Rendered HN" }, { name: "description", content: "Demo of Edge Rendering with Remix" }];

};

export const loader: LoaderFunction = async ({ request }) => {

return (await fetch(new URL(`/.netlify/functions/stories`, request.url))).json();

};

interface Story {

id: string;

title: string;

}

export default function Index() {

const stories = useLoaderData() as Story[];

return (

<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>

<h1>Hackernews</h1>

<ul>

{stories.map((story) => (

<li key={story.id}>{story.title}</li>

))}

</ul>

</div>

);

}

If you run this locally with netlify dev you’ll see we now render the list of stories on the Hackernews front page.

However, we didn’t actually make edge rendering any faster than just running the whole Remix app in us-east-1 close to where the API sits.

Let’s fix that.

#Making the edge fast

We’re going to do this with one simple addition to our stories function: adding a netlify-cdn-cache-control header with a stale-while-revalidate instruction.

Change the netlify/functions/stories.ts file to the following:

export default async () => {

const resp = await fetch("https://hacker-news.firebaseio.com/v0/topstories.json");

const ids = await resp.json();

const stories = await Promise.all(

ids.slice(0, 100).map(async (id) => {

const story = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);

return story.json();

})

);

return new Response(JSON.stringify(stories), {

headers: {

"content-type": "application/json",

"netlify-cdn-cache-control": "public, max-age=0, stale-while-revalidate=86400",

},

});

};

This is an instruction targeted to Netlify’s CDN, telling it to cache the response of the API request, serve the cached version to any visitor, but automatically revalidate the cached object in the background by doing one new request to /.netlify/functions/stories.

Once the site has warmed up globally this means each edge cache will always return a cached response of the latest stories to the Remix loader, but then update the version in the cache, giving us really fast edge rendering with mostly fresh content.

#Deploying to Netlify

To deploy this, just run:

netlify deploy --build -p

Here’s the version I deployed as part of building this demo (note that this is a demo site that might not be getting any traffic, so there’s a good chance you’ll get a slow first load from any given region. Refresh the page and you’ll see the API caching kick in):

https://remix-hn-edge-demo.netlify.app/

You can also explore the code in its repo: https://github.com/netlify-labs/remix-edge-api-caching

#Further Thoughts

This is a very simple demo, where the performance benefits are not as good as what we would get by just setting a cache header on the remix response.

For more complex apps where rendering on the fly is actually needed (vs pure static output), and we don’t have just one API request per page, this can be a really powerful technique to make edge rendering fast by caching logical groups of API requests together.

Netlify also provide other abstractions that make API caching even more powerful. One of them is Cache Tags and custom cache purges.

We could add a Cache-Tag: hn header to the response from stories and have a process trigger a cache purge when a new story makes front-page, and for more complex examples we can do this with whichever granularity we need.

By default Netlify will purge all the cached responses globally when we deploy a new version of the site. This makes a lot of sense when we’re caching full HTML pages that will include links to CSS and JS files, images, and other pages that might change between deploys and break in unpredictable ways for our users unless we can guarantee Atomic Deploys.

For our API endpoints, however, we might want to sidestep that. We can do that by adding a Netlify-Cache-Id: hn header to the response from stories.ts instead of the Cache-Tag header. Now the stories responses only gets a purge if we explicitly purge the hn cache tag.

The primitives makes it really simple to create a globally cached backend for the front-end that works incredibly well with edge rendered applications.

#More resources