Cloudflare Pages
Cloudflare Pages has two kinds of project, and they connect differently.
| Your project | How to tell | Use |
|---|---|---|
| Connected to Git (Cloudflare builds it) | Settings → Builds shows a repository and Deploy hooks | A deploy hook, below |
Direct upload (uploaded by wrangler pages deploy, often from CI) |
No Deploy hooks menu | GitHub Actions, or read when visited (below) |
Connected to Git: a deploy hook
Section titled “Connected to Git: a deploy hook”- Settings → Variables and secrets → Add, for Production:
JOOGOOS_FEED_URLas Text.JOOGOOS_FEED_KEYas Secret.
- Read the feed during the build (same code as Netlify).
- Settings → Builds → Deploy hooks → Add deploy hook: name
Joogoos, branchmain. Copy the address (https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/…). - The owner pastes it in Joogoos: Business → SEO → Where they are published → Build hook → Save.
- Retry the latest deployment once: Pages reads variables and secrets only from the next deployment.
Any project: read the feed when visited
Section titled “Any project: read the feed when visited”A Pages Function can serve the blog itself, reading the feed when a page is visited and keeping it five minutes. There is no build hook and no rebuild: a new article appears within five minutes. This is how joogoos.com’s own blog works.
- Add
JOOGOOS_FEED_KEYas a Secret (andJOOGOOS_FEED_URLas Text) under Settings → Variables and secrets. - Add
functions/blog/[[path]].js(below), and deploy once. - Open
/blog. After the first read, the card in Joogoos turns green.
export async function onRequestGet({ request, env }) { const res = await fetch(env.JOOGOOS_FEED_URL, { headers: { authorization: `Bearer ${env.JOOGOOS_FEED_KEY}` }, cf: { cacheTtl: 300, cacheEverything: true }, }); const feed = res.ok ? await res.json() : { entries: [], redirects: [], gone: [] }; const path = new URL(request.url).pathname.replace(/\/+$/, ''); const moved = feed.redirects.find((r) => r.from === path); if (moved) return Response.redirect(new URL(moved.to, request.url), 301); if (path === '/blog') return new Response(renderList(feed.entries), { headers: { 'content-type': 'text/html' } }); const entry = feed.entries.find((e) => e.path === path); if (entry) return new Response(renderArticle(entry), { headers: { 'content-type': 'text/html' } }); return new Response('Not found', { status: feed.gone.some((g) => g.path === path) ? 410 : 404 });}renderList and renderArticle are yours: escape every field, and render entry.body with a Markdown renderer. A function’s responses do not get the headers in _headers, so set your security headers on them too.
Keep the last good copy. Put every good feed into the Cache API under a fixed key and serve it whenever a read fails, so the blog never says “no articles” while Joogoos is unreachable or a key is being replaced:
const LAST_GOOD = 'https://your-site.example/__joogoos-feed-last-good';async function feedOf(env) { const cache = caches.default; const old = async () => (await cache.match(LAST_GOOD))?.json() ?? null; try { const res = await fetch(env.JOOGOOS_FEED_URL, { headers: { authorization: `Bearer ${env.JOOGOOS_FEED_KEY}` }, cf: { cacheTtl: 300, cacheEverything: true }, }); if (!res.ok) return old(); const feed = await res.json(); await cache.put(LAST_GOOD, new Response(JSON.stringify(feed), { headers: { 'cache-control': 'max-age=2592000' } })); return feed; } catch { return old(); }}And serve /blog/sitemap.xml from the feed’s entries (each url and modifiedAt), listed in robots.txt.