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

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Bryan Robinson's Blog

Does our technology still work for us? Product pricing, dev bad habits, and the role of the pit of success Astro Server Island for latest Bluesky post (heavily cached!) Type-safe environment variables in Astro 5.0 New Website, but really is it? Introducing the Hygraph Astro Content Loader Integrating Astro.js Starlight Documentation into a Next.js Project Using Proxies Jamstack is meaningless 😱 Book Release: Eleventy by Example – Learn 11ty with 5 in-depth projects 11ty Second 11ty: Creating Template Filters 11ty Second 11ty: Global Data files (JS and JSON) 11ty second 11ty: The Render Plugin Part 1 Help needed: Netlify Frontend environment variables with Astro.js Quick experiment with the Slinkity 11ty plugin Creating a dynamic color converter with 11ty Serverless Using 11ty JavaScript Data files to mix Markdown and CMS content into one collection How to show your template code in 11ty blog posts New City, New Job, New Content Using Nunjucks Climbing the 11ty Performance leaderboard with Cloudinary, critical CSS and more Three JAMstack movements to watch in 2020 Create a Codepen promo watermark with no additional HTML, CSS or JS 3 underused CSS features to learn for 2020 Use CSS Subgrid to layout full-width content stripes in an article template Adapt client-side JavaScript for use in 11ty (Eleventy) data files CSS Gap creates a bright future for margins in Flex as well as Grid Create your first CSS Custom Properties (Variables) Use CSS Grid to create a self-centering full-width element Creating an 11ty Plugin - SVG Embed Tool Now offering design and code reviews at PeerReviews.dev Routing contact-form emails to different addresses with Netlify, Zapier and SendGrid Create an Eleventy (11ty) theme based on a free HTML template Client work and the JAMstack Grid vs. Flex: A Tale of a "Simple" Promo Space Using Eleventy The Tech Barrier to Entry What Can We Learn from CERN Let Practical CSS Grid - Launching My First Course Build Trust on the Web incorporating User Worries with your User Stories 2019 The Year of Markup-First Development Refactoring CSS into a Sass mixin Starting a new journey with Code Contemporary Dynamic Static Sites with Netlify and iOS Shortcuts Top 3 uses for the ::before and ::after CSS pseudo elements How To: Use CSS Grid to Mix and Match Design Patterns Use CSS ::before and ::after for simple, spicy image overlays Modern CSS: Four Things Every Developer and Designer Should Know About CSS 3 Strategies for Getting Started with CSS Grid CSS Tip: Use rotate() and skew() together to introduce some clean punk rock to your CSS The 5 Stages of Grid Love How To: A CSS-Only Mobile Off Canvas Navigation How To: Use CSS Grid Layout to Make a Simple, Fluid Card Grid Make a More Flexible Cover Screen with CSS Grid Can CSS Grid open up interesting CMS Layout options? Firefox 52 to Introduce New Box-Alignment Values Falling Forward — Rethinking Progressive Enhancement, Graceful Degradation and Developer Morality Start Exploring the Magic of CSS Grid Layout I Converted My Blog to CSS Grid Layout and Regret Nothing Feature Queries are on the Rise CSS Shapes — Let the Text Flow Around You Flexbox -- Let Memorializing Prince and Print vs. The Web I went to Italy and noticed UX fails How to Get Designers to Contribute in Open Source The True Gift of Your Former Code
Netlify Durable Cache: Caching for a third-party world
2024-10-31 · via Bryan Robinson's Blog

Since its release, a lot has been said about how Netlify’s Durable Cache works wonders for performance based on the benefits of how caching and CDNs work in general.

Keeping your content and data stored close to your users and not making expensive database calls will of course always result in faster websites, but we also live in a world of APIs: the composable web.

In the composable web, our performance is only as good as our weakest API… or at least it COULD be good. It could also be bad.

In this tutorial, we’ll take a look at setting up Netlify’s Durable Cache on a third-party API with (artificially) slow returns. We’ll use a proxied dev.to API, throttle it a bit, and see the effects of durable caching on the responses. We’ll also build a function to clear specific authors from the cache to get the most up-to-date information without a full rebuild.

Want to skip ahead and just examine the code? No problem, take a look at the GitHub repository to see what’s happening. Or see the final demo to see the endpoints.

Requirements

Setup

In order to follow along with the project, you’ll need a blank project set up in Netlify. To do that, let’s create a simple directory and initialize a package.json:

mkdir netlify-durable-cache 
cd netlify-durable-cache
npm init -y
// Install functions for the typescript types (and more later)
npm install @netlify/functions 

This will give us the basic structure for the project and let us initialize a git repository to connect to Netlify.

Create the GitHub repository and run netlify init in the directory to connect it to a project in Netlify (necessary for functions to work properly with netlify dev local environments.

Writing our function

Now that the project is setup, let’s construct a few functions. To start, let’s write a function that will have no caching as a baseline. This will be the basis for the cached function, as well.

Create a file at /netlify/functions/demo/index.mts.

While we could run this at the default endpoint structure for Netlify functions, it’s much cleaner to set up a redirect for these. Luckily, we can export a config object from our function file and set up the redirect AND any URL parameters we want to pass.

import { Config } from "@netlify/functions";

export const config: Config = {
  path: "/api/demo/:username"
};

From here, let’s set up the meat of the function. We want to reach out to the proxied API and return a list of articles for a given username from Dev.to. Let’s write a function we can use in our handler for this:

async function getArticles(username: string) {
  // Artificial slow API
  await new Promise(resolve => setTimeout(resolve, 2000));

  // API that returns articles from a proxy API to Dev.to
  const response = await fetch(`https://www.federatethis.com/api/devto/articles/${username}`);
  const data = await response.json();
  return data;
}

One slightly non-standard piece here: I’ve added a Promise that we’re going to await it’s resolution. This will pause our API call for 2 seconds. Not ideal for the real world, but for testing caching, this is very beneficial.

Once we have this function, we can use it in our exported event handler.

import { Config, Context } from "@netlify/functions";

async function getArticles(username: string) {
  // Artificial slow API
  await new Promise(resolve => setTimeout(resolve, 2000));

  // API that returns articles from a proxy API to Dev.to
  const response = await fetch(`https://www.federatethis.com/api/devto/articles/${username}`);
  const data = await response.json();
  return data;
}

export default async (req: Request, context: Context) => {
  const { username } = context.params;
  const articles = await getArticles(username);
  
  const response = new Response(JSON.stringify({
    articles,
    username
  }));
  return response;
};

export const config: Config = {
  path: "/api/demo/:username"
};

Our handler will use the context object that Netlify creates for us. This will allow us to use the params to get our username from the URL. Then, we can pass that to our getArticles function and return a response with the articles and the username.

If you’re using the Lambda compatibility mode, the context object won’t contain the custom Netlify properties, so you won’t have access to the URL parameters and will need to deconstruct the URL to get the path.

In your terminal, run netlify dev and your server will spin up. You can head to localhost:8888/api/demo/brob to get a list of my posts (or input your own username for a list of your dev.to posts.

Now, this works, but... our API is soooooo slow! Each request is taking at least 2.3 seconds. This API is untrustworthy and should be cached aggressively to make this a great experience for most of our users.

Enter Netlify’s Durable Cache and the Netlify cdn-cache-control NPM package.

Setting up caching

As you may have guessed, the first step here is to install the package I just mentioned. While you can write these headers by hand… well, noboday has time for all that. Netlify provides some great defaults out of the box with the package, so let’s install it and use it!

npm install cdn-cache-control

Once the package is installed we can make full use of it. In our default function export, we can create new cache headers using the package and pass that to our response. This will tell the browser important information, but will also clue Netlify’s CDN into the plan, as well.

import { Config, Context } from "@netlify/functions";
import { CacheHeaders } from "cdn-cache-control";

async function getArticles(username: string) {
  // Artificial slow API
  await new Promise(resolve => setTimeout(resolve, 2000));

  // API that returns articles from a proxy API to Dev.to
  const response = await fetch(`https://www.federatethis.com/api/devto/articles/${username}`);
  const data = await response.json();
  return data;
}

export default async (req: Request, context: Context) => {
  const { username } = context.params;
  const articles = await getArticles(username);
  
  const headers = new CacheHeaders();

  const response = new Response(JSON.stringify({
    articles,
    username
  }), {
    headers
  });
  return response;
};

export const config: Config = {
  path: "/api/demo/:username"
};

With a few tweaks, we now have multiple headers being added to our response. Let’s take a look at that:

'netlify-cdn-cache-control' => {
    name: 'Netlify-CDN-Cache-Control',
    value: 'public,s-maxage=31536000,must-revalidate,durable'
  },
  'cache-control' => {
    name: 'Cache-Control',
    value: 'public,max-age=0,must-revalidate'
  }
},

The package is adding both standards-compliant headers for the browser with the cache-control header, but also the netlify-cdn-cache-control header to communicate with the CDN.

The CDN cache is being set to a year, and is set to durable.

While I’ll colloquially refer to this as “the CDN’s cache”, what’s really happening is a little deeper. Setting the header to durable informs Netlify’s CDN that it needs to store it in their new shared cache called the “durable cache”. This is shared by all CDN nodes allowing for even faster response times for the nodes to cache the content locally.
This article in the documentation provides a great overview of the durable cache mechanism. If you just want to refer to it as your “CDN’s cache”, though, I won’t tell anyone.

The browser cache is being set to revalidate and always send the request. We could have a deeper conversation here about the potential for adding a client-side cache to this as well, but the CDN response is incredibly fast, so the gains are a fun philosophical debate, but maybe not worth it for this article. The CDN response is now cached for a year (or until cleared), so when the browser sends the request, it can respond immediately.

In order to truly see the cache in action, you’ll want to deploy it to Netlify.

Once deployed, hit the endpoint at the URL Netlify provides. The first response will take approximately 2-3 seconds. This will fire the function, retrieve the data, store it in that CDN node’s local cache and store it in the durable cache. Every response after will then be served from the original node cache for you AND from the durable cache for others. Each request takes approximately 40-70 milliseconds (at least in my experience). That’s notable savings from a problematic API.

But, as they say, there are 2 big problems in programming: naming things and invalidating caches. If this is stored for a year, how do we invalidate and create fresh content on the CDN?

Let’s purge the cache!

Purging cache tags

To make the cache purgeable, we need to ensure it’s named. Both standard headers and Netlify’s headers provide a mechanism for tagging caches with a variety of cache tags. This gives you an easy way to purge specific tags at any given time.

Let’s modify the function to use this functionality in the headers it’s sending.

// ... 

  const headers = new CacheHeaders().tag("articles", "articles-" + username);

// ...

Our CacheHeaders() method is fully chainable meaning you can continue to chain new methods on to it allowing new functionality to surface. In this case, we can use the .tag() method and pass it as many tags as we want. For this example, let’s tag our caches with both articles and articles-<username>. In this case, if you are following along, this would tag the cache with articles and articles-brob.

Both tags may be something you want to purge at some point. The specific articles-brob tag will clear the cache for just the endpoint that specified the username as brob. The general articles tag will clear the cache for all instances of the tag articles which will be all the endpoints in our demo. Usually, we’ll want to clear just one cache, but there are definitely reasons for doing a bigger clearing.

Setting up a cache purging serverless function

In order to clear a given tag, we need a function to clear it. Let’s keep this separate and make it a utility function for our site.

Set up a new file at /netlify/functions/purge-cache/index.mts. Once created, we’ll set up a config to locate this function at /api/purge-cace/:tag and write a function that uses Netlify’s purgeCache method from the @netlify/functions package (not JUST for the types after all).

import { Context, Config } from "@netlify/functions";
import { purgeCache } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const { tag } = context.params;
  // if no tag, no need for further action
  if (!tag) {
    return;
  }

  console.log("Purging cache for ", tag);

  await purgeCache({
    tags: [tag],
  });

  return new Response("Purged!", { status: 202 })
};

export const config: Config = {
    path: "/api/purge-cache/:tag"
  };

That function will take the tag you enter and use the default method to tell Netlify’s caches to be purged. It shouldn’t be this easy, but it is. You’ll also notice that it takes an array, so you could relatively simply change this function to clear an array of tags instead of just a single tag. Consider that your home work.

Now, deploy the function.

Deploys ALSO invalidate caches
Keep in mind that when a new build happens, Netlify will invalidate the caches, as well. If you want to see the purge in action, you’ll need to recreate your cache by visiting the original endpoint.

If you visit /api/purge-cache/articles-brob it will clear the specific cache for my articles. The next time you visit your original endpoint, it should take the full load again (assuming no one else hits your endpoint before you!).

This function could be used in conjunction with webhooks or other communication methods to help keep things fresh at just the right time instead of continuously updating at a predetermined time.

Conclusion

In this tutorial, we've explored how to leverage Netlify's Durable Cache to significantly improve the performance of third-party API calls. We started with a slow, unreliable API and transformed it into a lightning-fast, cached response system. Here's a quick recap:

  • Set up a serverless function to proxy API calls to Dev.to (with an artificial delay)
  • Implemented Netlify's Durable Cache using the cdn-cache-control package
  • Added cache tags for granular cache management
  • Created a cache purging function for on-demand cache invalidation

By setting this up, we've created a robust caching strategy that can drastically reduce load times from seconds to milliseconds. This approach not only improves user experience but also reduces the load on third-party services and our own infrastructure.

Remember, while caching is powerful, it's important to balance freshness with performance. The cache purging function we created allows for this balance, enabling you to update content when needed without sacrificing the speed benefits of caching.

As you build your own projects, consider how you can apply these caching strategies to optimize your third-party integrations and create faster, more responsive web applications.