Remix: Set Stale-While-Revalidate Cache Control to Improve Performance


3 min read

Deploying web applications globally often presents a unique set of challenges, particularly when it comes to maintaining fast server responses across different continents. One effective strategy to enhance performance in such scenarios is leveraging caching mechanisms like Cloudflare's CDN along with appropriate HTTP caching headers.

Here’s a practical guide on how to implement Stale-While-Revalidate cache control in a Remix application to optimize content delivery.

Setup Overview

In my current architecture, the flow is structured as follows:

Users (Global) -> Cloudflare (Global) -> App (N. Virginia) -> Supabase (N. Virginia)

Using Cloudflare helps to speed up requests significantly, especially for users located far from the main server.

Cache Control Strategies

1. Real-Time Data

For real-time data, where freshness is crucial, I use the following cache headers:

   "Cache-Control": "public, max-age=0, must-revalidate",
   "CDN-Cache-Control": "public, s-maxage=1, stale-while-revalidate=60"

This configuration ensures that the data stays fresh by minimizing latency, as the CDN can serve stale content for up to 60 seconds while it revalidates the content in the background.

2. Daily Updated Data

For data that updates daily:

   "Cache-Control": "public, max-age=0, must-revalidate",
   "CDN-Cache-Control": "public, s-maxage=3600, stale-while-revalidate=82800"

Here, the CDN caches the data for an hour (s-maxage=3600) and can serve stale data for just under a day while fetching new content (stale-while-revalidate=82800).

3. Static Data

For static content that rarely changes:

   "Cache-Control": "public, max-age=0, must-revalidate",
   "CDN-Cache-Control": "public, s-maxage=60, stale-while-revalidate=2678400"

This setup allows caching for 1 minute, with a long stale period of 31 days, suitable for static resources like images or CSS files.

Implementing in Remix

To implement these caching strategies in Remix (version 2.9 with Single Fetch enabled), here's how you can set up a loader function:

export async function loader({ params, response }: LoaderFunctionArgs) {
    // Validate parameters and fetch data here

    // Set cache headers for real-time data
    const realtimeCaches: { [key: string]: string } = {
        "Cache-Control": "public, max-age=0, must-revalidate",
        "CDN-Cache-Control": "public, s-maxage=1, stale-while-revalidate=60",

    // Apply the cache settings to the response
    for (const key of Object.keys(realtimeCaches)) {
        response.headers.append(key, realtimeCaches[key]);

    // Return the fetched data
    return response;

In this example, headers are set directly on the response object to control how Cloudflare and browsers cache the data. You can easily modify this setup to apply different caching strategies based on the type of data being handled by the loader.


Implementing strategic caching in your Remix applications can significantly improve performance for end-users, especially when dealing with global traffic. By carefully setting HTTP caching headers, you can control how data is cached and revalidated, ensuring that users receive data as quickly as possible while reducing server load and response times.