Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active April 30, 2025 13:34
Show Gist options
  • Save mizchi/ed353e19685d96b8405edec715496915 to your computer and use it in GitHub Desktop.
Save mizchi/ed353e19685d96b8405edec715496915 to your computer and use it in GitHub Desktop.
/* wrangler dev main.ts --remote */
import { parse } from "cache-control-parser";
async function handleSwrTime(
request: Request,
env: Env,
ctx: ExecutionContext
) {
// mock response
const createNewResponse = () => {
const res = new Response(Date.now().toString(), {
status: 200,
headers: {
"content-type": "text/html",
"cache-control": "no-cache, no-store, must-revalidate",
"cdn-cache-control": "max-age=14400, stale-while-revalidate=10",
},
});
return res;
};
const cache = caches.default;
const cachedResponse = await cache.match(request);
if (cachedResponse) {
if (shouldRevalidate(cachedResponse)) {
console.log("Cache: REVALIDATE");
ctx.waitUntil(
(async () => {
const res = createNewResponse();
await cache.put(request, res);
})()
);
const headers = new Headers(cachedResponse.headers);
headers.set("x-cache-status", "stale-while-revalidate");
headers.set("Access-Control-Expose-Headers", "x-cache-status");
const revalidatingResponse = new Response(cachedResponse.body, {
status: cachedResponse.status,
headers: headers,
});
return revalidatingResponse;
}
console.log("Cache hit");
return cachedResponse;
}
const res = createNewResponse();
console.log("Cache miss");
ctx.waitUntil(
(async () => {
console.log("Cache: PUT");
await cache.put(request, res.clone());
})()
);
return res;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
if (request.url.endsWith("/swr/time")) {
return handleSwrTime(request, env, ctx);
}
return env.ASSETS.fetch(request);
},
} satisfies ExportedHandler<Env>;
function shouldRevalidate(cachedResponse: Response): boolean {
const ageHeader = cachedResponse.headers.get("age");
const cacheControlHeader = cachedResponse.headers.get("cdn-cache-control");
if (!ageHeader || !cacheControlHeader) {
return false;
}
const parsed = parse(cacheControlHeader);
const staleWhileRevalidate = parsed["stale-while-revalidate"];
if (typeof staleWhileRevalidate === "undefined") {
return false;
}
const age = Number(ageHeader);
if (Number.isNaN(age)) {
return false;
}
return age > staleWhileRevalidate;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="module">
const insert = (parent, tag, props) => {
const element = document.createElement(tag);
Object.assign(element, props);
parent.appendChild(element);
return element;
};
const isRevalidatingRequest = (res) => {
return res.headers.get('x-cache-status') === 'stale-while-revalidate';
};
const res = await fetch('/swr/time');
const text = await res.text();
// update
const displayText = `${text} ${isRevalidatingRequest(res) ? 'revalidating' : 'fresh'}`;
const root = document.getElementById('root');
root.textContent = displayText;
if (isRevalidatingRequest(res)) {
const dialog = document.createElement('dialog');
document.body.appendChild(dialog);
insert(dialog, 'p', {
textContent: 'This page is revalidating.',
});
const reloadButton = insert(dialog, 'button', {
textContent: 'Reload',
disabled: true,
onclick: () => window.location.reload(),
});
dialog.showModal();
// Exponential backoff for revalidation
let counter = 0;
const checkRevalidate = async () => {
const newRes = await fetch('/swr/time');
if (isRevalidatingRequest(newRes)) {
counter++;
reloadButton.textContent = `(Attempting ${counter})`;
const nextExponentialTime = Math.pow(2, counter) * 1000;
setTimeout(checkRevalidate, nextExponentialTime);
} else {
reloadButton.disabled = false;
// Done
return;
}
}
setTimeout(checkRevalidate, 3000);
}
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment