Last active
April 30, 2025 13:34
-
-
Save mizchi/ed353e19685d96b8405edec715496915 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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