Putting a pre-rendering proxy in front of Base44 with a Cloudflare Worker
How a Cloudflare Worker can route crawlers to a pre-renderer and visitors to your Base44 app: the code, the canonical and redirect fixes, and what to test before switching DNS.
10 min read
A Cloudflare Worker on your custom domain can split traffic: crawler requests for pages go to a pre-renderer, and everything else goes straight to your Base44 app at its base44.app address. Crawlers get fully rendered HTML on every visit, including crawlers Base44 doesn’t pre-render for, and visitors see your app as normal. The routing code is short. The details that matter are rewriting canonical tags and redirects, and knowing which Base44 features depend on a connected custom domain.
This is the do-it-yourself version of the managed option in the options guide.
How the traffic flows
- Your domain’s DNS points at Cloudflare, with the Worker attached as a custom domain.
- A request comes in. The Worker checks the method, the path and the user agent.
- Crawler asking for a page: the Worker fetches rendered HTML from your pre-renderer and returns it.
- Anyone else, or any asset: the Worker forwards the request to
yourapp.base44.appand returns Base44’s response.
The pre-renderer loads your page through the same domain with a headless browser. Its browser user agent doesn’t match the crawler list, so its requests pass straight through to Base44, and there’s no loop.
The Worker
A minimal version, with placeholders for your app and renderer. It follows the same structure as the Worker Encited publishes for Base44.
// worker.js
const BASE44_ORIGIN = 'https://yourapp.base44.app' // your published Base44 URL
const PUBLIC_HOST = 'yourdomain.com'
const RENDERER = 'https://renderer.example.com/render?url=' // your pre-renderer
const BOTS = /googlebot|bingbot|gptbot|oai-searchbot|chatgpt-user|claudebot|claude-user|perplexitybot|perplexity-user|applebot|duckduckbot|bravebot|meta-externalagent|amazonbot|ccbot|facebookexternalhit|twitterbot|linkedinbot|slackbot|discordbot|whatsapp/i
const ASSET = /\.(js|css|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|map|json|xml|txt)$/i
export default {
async fetch(req, env) {
const url = new URL(req.url)
const ua = req.headers.get('user-agent') || ''
if (req.method === 'GET' && !ASSET.test(url.pathname) && BOTS.test(ua)) {
const rendered = await fetch(RENDERER + encodeURIComponent(url.toString()), {
headers: { authorization: `Bearer ${env.RENDERER_TOKEN}` },
})
if (rendered.ok) {
return new Response(rendered.body, {
headers: { 'content-type': 'text/html; charset=utf-8', 'x-prerendered': '1' },
})
}
// If the renderer fails, fall through and serve Base44's own response.
}
return forward(req, url)
},
}
async function forward(req, url) {
const upstream = new URL(url.pathname + url.search, BASE44_ORIGIN)
const resp = await fetch(upstream, {
method: req.method,
headers: req.headers,
body: ['GET', 'HEAD'].includes(req.method) ? undefined : req.body,
redirect: 'manual',
})
// Keep visitors on your domain when Base44 redirects.
const loc = resp.headers.get('location')
if (loc) {
const headers = new Headers(resp.headers)
headers.set('location', loc.replaceAll(new URL(BASE44_ORIGIN).host, PUBLIC_HOST))
return new Response(resp.body, { status: resp.status, headers })
}
return resp
}
Store the renderer token as a Worker secret (wrangler secret put RENDERER_TOKEN), and deploy with wrangler deploy.
Fix the canonical tags
Base44 writes canonical and og:url tags for the address that served the page. Proxied from yourapp.base44.app, those tags point at base44.app, and Google may index that address instead of yours. Either have the renderer rewrite them, or rewrite in the Worker with Cloudflare’s HTMLRewriter:
const fixHost = (el, attr) => {
const v = el.getAttribute(attr)
if (v) el.setAttribute(attr, v.replace(new URL(BASE44_ORIGIN).host, PUBLIC_HOST))
}
const rewriter = new HTMLRewriter()
.on('link[rel="canonical"]', { element: (el) => fixHost(el, 'href') })
.on('meta[property="og:url"]', { element: (el) => fixHost(el, 'content') })
.on('meta[name="twitter:url"]', { element: (el) => fixHost(el, 'content') })
// return rewriter.transform(rendered) for crawler responses
Then check the upstream’s headers: if the base44.app address sends x-robots-tag: noindex, crawlers must never see that header on your domain.
What depends on a connected custom domain
With this setup your domain points at Cloudflare, so it isn’t connected as a custom domain inside Base44. Base44 ties some features to a connected, verified custom domain:
- Emailing people who haven’t signed up to your app with SendEmail.
- Sending from a custom email domain.
- Base44’s own custom-domain SEO features, such as its managed files on your domain.
Check which of these your app uses. Your Worker can serve robots.txt and a sitemap itself if needed.
The renderer is the hard part
The Worker is the easy half. The renderer needs a pool of headless browsers, waiting until each page’s data has loaded, caching, invalidation when you publish, retries, and monitoring. That’s where most of the cost and maintenance goes, and why a managed service is usually cheaper once everything is counted. A managed service such as Encited supplies the renderer, the post-processing and crawl logs, and its Base44 setup gives you this Worker ready-made, or a DNS-only setup with no Worker at all.
What to do next
- Decide whether you’ll run a renderer or use a managed one.
- Deploy the Worker to a test subdomain and run the end-to-end tests.
- Check canonicals and headers with a crawler user agent before moving your main domain.
Frequently asked questions
- Can I put Cloudflare in front of a Base44 custom domain?
- Not with Base44's own custom domain connection, which needs its DNS records set to DNS only. A Worker proxy instead serves your domain from Cloudflare and forwards visitors to your app's base44.app address, so the domain isn't connected in Base44 itself.
- What do I lose by proxying a Base44 app through a Worker?
- Features that depend on a custom domain being connected and verified in Base44, such as emailing people who haven't signed up to your app and sending from a custom email domain. Check which of those your app uses before switching.
- Do I have to rewrite canonical tags when proxying Base44?
- Yes. Pages served from your base44.app address declare that address in canonical and og:url tags. The proxy or renderer must rewrite them to your custom domain, or Google may index the base44.app version.
- Is there a ready-made Worker for Base44 pre-rendering?
- Encited publishes a Cloudflare Worker for Base44 in its documentation, which forwards visitors to your base44.app URL and sends crawler requests to its renderer. It also offers a DNS-only setup with no Worker.
Read next
Keep going
-
How to prerender a Base44 app: every option compared
Compare the ways to prerender a Base44 app: Encited, Prerender.io, a self-hosted Puppeteer proxy and exporting to server rendering, and why warming doesn't work.
-
Pre-rendering Base44 apps: the complete guide
Does Base44 pre-render pages for Google and AI crawlers? Only from a cache. How Base44 pre-rendering works, where it leaves gaps, and every way to prerender a Base44 app.
-
Which crawlers Base44 pre-renders for, and which it skips
We tested 33 user agents against live Base44 pages. Googlebot, Bingbot, GPTBot, ClaudeBot and PerplexityBot get renders. Bravebot, Meta and Search Console's tester don't.
-
Pre-rendering vs server rendering for a Base44 app
When pre-rendering is enough for a Base44 app and when it's worth exporting to a server-rendered framework like Next.js or TanStack Start. Costs, effort and SEO.