Cloudflare Pages _redirects can’t proxy to external domains. But Pages Functions can. Here’s how we serve SSR pages from our backend through Cloudflare’s edge — for free.
The Problem
Our static site lives on Cloudflare Pages (misujob.com). Our SSR job pages live on a backend API (api.misujob.com). We needed /jobs-public/* to serve SSR HTML from the backend, while keeping everything else static.
Cloudflare Pages _redirects supports proxying, but only to the same domain. No external proxying.
The Solution: Pages Functions
// functions/jobs-public/[[path]].ts
export const onRequest: PagesFunction = async (context) => {
const url = new URL(context.request.url);
const path = url.pathname.replace('/jobs-public', '');
const backendUrl = `https://api.misujob.com/jobs/${path}${url.search}`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 20000);
const response = await fetch(backendUrl, {
headers: {
'Accept': 'text/html',
'User-Agent': context.request.headers.get('User-Agent') || 'Proxy',
},
redirect: 'manual', // Preserve 301s for SEO
signal: controller.signal,
});
clearTimeout(timeout);
// Rewrite redirect URLs from backend to frontend domain
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('Location')
?.replace('api.misujob.com/jobs/', 'misujob.com/jobs-public/');
return new Response(null, {
status: response.status,
headers: { 'Location': location, 'Cache-Control': '...' }
});
}
return new Response(await response.text(), {
status: response.status,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'public, max-age=300, s-maxage=600',
},
});
};
Key Details
1. redirect: 'manual' — Critical for SEO. Our backend returns 301 redirects for canonical URLs. If Cloudflare follows them internally, Google sees 200 for non-canonical URLs.
2. 20-second timeout — CF Workers have a 30-second limit. We abort at 20 to return a proper error page instead of CF’s generic timeout.
3. URL rewriting — Backend redirects point to api.misujob.com/jobs/..., but users need to see misujob.com/jobs-public/....
4. Caching — s-maxage=600 means Cloudflare’s edge caches for 10 minutes. Most requests never hit the backend.
Cost
Zero. Cloudflare Pages includes 100,000 function invocations/day on the free tier. We use about 5,000/day.
This serves all SSR pages at MisuJob — 1M+ job listings, 175+ category pages.
Using CF Pages Functions for something creative? Share it below.

