Cloudflare for SaaS + Worker: Optimal IP Routing and Edge Cache Proxy for Vercel
7 min read
2
Problem Background
Vercel delivers an excellent development and deployment experience, but the default Anycast IPs assigned to free/basic plan users have poor routing quality in mainland China, resulting in high latency, packet loss, or outright connection blocks.
The conventional workaround is routing your domain through Cloudflare's proxy (orange cloud), but this only addresses the blocking issue — the Anycast IPs allocated by CF's free tier still offer no connectivity guarantee within China. Attempting to directly modify CF DNS records — pointing your domain to a China-optimized CF node via an A or CNAME record — runs into the following technical barriers:
- Error 1000 (Looped DNS Resolution): Vercel's underlying infrastructure also runs on Cloudflare's enterprise network. When traffic enters through a custom CF node and targets a Vercel-hosted domain, the CF edge detects a cross-account proxy request and triggers its protection mechanism, returning Error 1000.
- Authorization Restrictions: Cloudflare prohibits accessing resources hosted on third-party platforms (such as Vercel) through optimized nodes without explicit SaaS authorization.
This solution uses Cloudflare for SaaS (Custom Hostnames) combined with Worker route mapping to bypass these network-layer restrictions, while also providing edge caching and centralized multi-site management via Workers.
Architecture Overview & Prerequisites
Request flow:
User Request → Optimized CF Node → SaaS Fallback Origin → Worker Intercept → Rewrite Host → Vercel Origin (with cache) → Response
Required resources:
| Resource | Description | Example |
|---|---|---|
| Primary Domain | Your public-facing business domain | app.domain.com |
| Auxiliary Domain | Any CF-managed domain, serves as the routing hub | proxy.dev |
| Vercel Project | Keep only the system-generated .vercel.app domain |
your-project.vercel.app |
⚠️ Critical prerequisite: You must remove the primary domain binding from your Vercel project's Domains settings. Leaving it causes SNI hijacking that creates internal CF routing conflicts, rendering the entire setup non-functional.
Deployment Steps
Step 1: Build the Fallback Origin
The auxiliary domain acts as the internal traffic relay hub. First, configure a fallback origin by adding an A record to the auxiliary domain's (proxy.dev) DNS management page:
| Field | Value | Notes |
|---|---|---|
| Name | origin |
Full address: origin.proxy.dev |
| IPv4 Address | 192.0.2.1 |
RFC 5737 reserved IP, placeholder only — actual requests are intercepted by the Worker |
| Proxy Status | ✅ Proxied (orange cloud) | Must be enabled, otherwise the Worker cannot intercept |
Step 2: Write and Deploy the Routing Worker
The Worker's core responsibilities: intercept all requests to the fallback origin → look up the corresponding Vercel origin in the route map → rewrite the Host header and fetch from origin → enable edge caching via the cf parameter.
Create a Worker in the CF dashboard and write the following code:
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const requestHost = url.hostname;
// Domain map: external domains -> Vercel origin domains
const routeMap = {
"app.domain.com": "your-project.vercel.app",
"www.app.domain.com": "your-project.vercel.app",
"blog.domain.com": "your-project.vercel.app",
};
const targetHostname = routeMap[requestHost];
if (!targetHostname) {
return new Response("Forbidden: Domain not configured in routing map.", { status: 403 });
}
// Rewrite URL
url.hostname = targetHostname;
// Clone request, keep original method/headers
const newRequest = new Request(url.toString(), request);
newRequest.headers.set('Host', targetHostname);
const isGet = request.method === 'GET' || request.method === 'HEAD';
const isNextStatic = url.pathname.startsWith('/_next/static/') || url.pathname.startsWith('/_next/image');
const isPublicAsset = url.pathname.startsWith('/favicon') || url.pathname.startsWith('/robots.txt') || url.pathname.startsWith('/sitemap');
// Cache only static asset GETs; never cache HTML/POST/Server Actions
if (isGet && (isNextStatic || isPublicAsset)) {
return fetch(newRequest, {
cf: {
cacheTtl: 3600,
cacheEverything: true,
},
});
}
// Don't cache other requests to avoid stale actionId/HTML
return fetch(newRequest, {
cf: {
cacheEverything: false,
},
});
},
};After deploying, go to Worker Settings → Triggers → Routes and add the interception rule:
- Route:
origin.proxy.dev/* - Zone: Auxiliary domain (
proxy.dev)
Step 3: Configure Cloudflare for SaaS
To allow CF edge nodes to accept traffic from the primary domain into the fallback origin, complete the SaaS authorization. Navigate to SSL/TLS → Custom Hostnames on your auxiliary domain:
- Set Fallback Origin: Enter
origin.proxy.devand wait for the status to show "Active". - Add Custom Hostname: Enter the primary domain (e.g.,
app.domain.com). - TXT Verification: The system generates two TXT records (certificate validation and hostname validation). Add both records at your primary domain's DNS provider. After a few minutes, confirm that both certificate status and hostname status show "Active".
Step 4: DNS Optimization via Double-CNAME
Why not CNAME directly to the optimized domain? CF has validation logic restrictions for domains within the same account. Pointing the primary domain's CNAME directly to a third-party optimized domain will cause the SaaS custom hostname status to become invalid. An intermediate relay through the auxiliary domain (double-CNAME) is required.
① Add bridge record on auxiliary domain:
In proxy.dev's DNS, add a CNAME record:
- Name:
cnd(full address:cnd.proxy.dev) - Target: Third-party optimized node pool address (e.g.,
cloudflare.182682.xyz) - Proxy Status: 🌐 DNS Only (gray cloud) — must disable proxying
② Add traffic routing record on primary domain:
In your primary domain's DNS, set the business records (@ and www) to CNAME to cnd.proxy.dev:
- Proxy Status: 🌐 DNS Only (gray cloud) — also disable proxying
Step 5: Complete Worker Trigger Routes (Fix Error 522)
⚠️ Easy-to-miss critical step! When a request carrying the primary domain Host enters the auxiliary domain's Zone, if that primary domain is not explicitly declared in the Worker's trigger routes, the Worker will not execute. Traffic then flows directly to the placeholder IP (
192.0.2.1) bound to the fallback origin, causing a connection timeout (Error 522).
In Worker Settings → Triggers → Routes, add a route for each business domain:
| Route | Zone |
|---|---|
app.domain.com/* |
Auxiliary domain (proxy.dev) |
www.app.domain.com/* |
Auxiliary domain (proxy.dev) |
blog.domain.com/* |
Auxiliary domain (proxy.dev) |
System Expansion SOP
This architecture is designed as a centralized routing hub — the underlying network is built only once. For each new Vercel project going forward, simply repeat this four-step standard procedure:
| Step | Action | Location |
|---|---|---|
| 1. Update route map | Add "new-domain": "new-vercel-domain" to routeMap |
CF Worker code |
| 2. Add SaaS hostname | Register the new domain, complete TXT record verification | Auxiliary domain → Custom Hostnames |
| 3. Configure DNS (gray cloud) | New domain CNAME to cnd.proxy.dev (DNS only) |
Primary domain DNS provider |
| 4. Add Worker trigger | Add route new-domain/*, attach to auxiliary domain Zone |
CF Worker Triggers |
Next Step: Eliminating the Single Point of Failure
In this setup, the bridge CNAME (cnd.proxy.dev) points to a third-party-maintained optimized node pool — the only link in the chain outside your control. This introduces a single point of failure (SPOF) risk.
Cloudflare Worker Cron Watchdog: SPOF Fix and Automatic DNS Failover for the Vercel Proxy Architecture implements automated monitoring via CF Worker Cron Triggers, automatically switching to CF official IPs as a fallback when the third-party pool fails. Recommended as a companion deployment to this guide.