Engineering Originally on dev.to

Using Cloudflare Pages Functions as a Reverse Proxy for SSR (Free Tier)

Cloudflare Pages redirects can't proxy externally. Pages Functions can. Here's how we serve SSR pages for free.

P
Pablo Inigo · Founder & Engineer
2 min read
CDN edge node routing requests as reverse proxy to origin server

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. Cachings-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.

Cloudflare Serverless Web Development SEO
Share
P
Pablo Inigo

Founder & Engineer

Building MisuJob — an AI-powered job matching platform processing 1M+ tech job listings daily.

Engineering updates

Technical deep dives delivered to your inbox.

Find your next role with AI

Upload your CV. Get matched to 50,000+ jobs. Auto-apply to the best fits.

Get Started Free

User

Dashboard Profile Subscription