Images that do not expire
If you have built a site on Notion, you have probably seen it: a page that looked fine yesterday now shows broken images. Nothing changed in Notion. The page was cached, and the image URLs inside it expired.
Why it happens
Files uploaded to Notion are stored on S3 and served through signed URLs. The signature is valid for one hour. That is fine for the Notion app, which asks for a fresh URL every time, but a static or cached page keeps the URL it was built with. An hour later, the image is gone.
The common workarounds both cost something. Re-rendering every page every hour means hammering Notion's API. Copying every image to your own storage means a sync job and a second copy of everything.
A redirect instead
notion-fetch takes a smaller route. Pages do not link to the image itself, but to a route on your own site with the block's ID:
<img src="/api/notion/file/2f0a…" alt="" />That route asks Notion for the block, gets a fresh signed URL, and redirects to it. The redirect itself is cached by the CDN for 40 minutes, well within the URL's one-hour life, so Notion is asked at most once per image per 40 minutes, however many people read the page.
// app/api/notion/file/[id]/route.ts
export const GET = createFileHandler({ notion, limit });The handler takes a standard Request and returns a Response, so the same line works in Next.js, Waku, Hono or a Cloudflare Worker.
What you do not need
No image service, no storage bucket, no sync job. Images are still served straight from Notion's CDN, at their original size; resize them in Notion if a photo is too large.