---
title: Cloudflare Workers
description: "Proxy /docs requests through a Cloudflare Worker to your Jamdesk documentation site. Covers Worker setup, route patterns, and caching configuration."
---

A [Cloudflare Worker](https://workers.cloudflare.com/) intercepts requests at `/docs` on your domain, rewrites them to your Jamdesk subdomain, and returns the response -- all at the edge with no origin server required. You can scaffold the Worker automatically with `npx jamdesk deploy cloudflare` or set it up manually below.

## How It Works

The Worker forwards requests to your Jamdesk subdomain while passing along the original domain in the `X-Jamdesk-Forwarded-Host` header. Jamdesk uses this header to:

1. **Verify your domain** - Ensures only authorized domains can serve your docs
2. **Apply correct configuration** - Uses your domain's settings from the dashboard

This means the Worker is a **one-time setup**. If you later change your custom domain or configuration in the Jamdesk dashboard, the Worker doesn't need to be updated - all routing decisions are made server-side.

## Prerequisites

- A Cloudflare account with your domain configured
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) v3.0+ installed
- Your Jamdesk subdomain (found in dashboard settings)

## Quick Setup with CLI

The fastest way to set up your Cloudflare Worker:

```bash
npx jamdesk deploy cloudflare
```

This interactive command will:
1. Check that wrangler 3.0+ is installed
2. Verify your Cloudflare account and show available domains
3. Auto-detect your project slug from `docs.json`
4. Let you select your target domain from your Cloudflare zones
5. Generate all required files
6. Optionally deploy to Cloudflare

### Account Selection

The CLI verifies you're logged into the correct Cloudflare account before proceeding. If you have access to multiple Cloudflare accounts (common for agencies or teams), you'll be prompted to choose:

``` text
Step 2: Verifying Cloudflare account...
✓ Logged in as: you@example.com
? Select the Cloudflare account to use:
❯ Your Personal Account
  Company Team Account
  [ Switch to different login ]
```

After selecting an account, only domains from that account will be available for zone selection:

``` text
✓ Using account: Company Team Account
✓ Domains: 3 available for Workers routing
```

<Note>
Make sure you select the Cloudflare account that has the domain you want to deploy to. Each account has its own set of domains, and you can only create Workers routes for domains in the selected account.
</Note>

### CI/Scripted Deployment

For CI/scripting, use flags to skip prompts:

```bash
jamdesk deploy cloudflare --slug myproject --domain example.com --skip-deploy --yes
```

| Option | Description |
|--------|-------------|
| `--slug` | Jamdesk project slug (skip auto-detection) |
| `--domain` | Target domain (e.g., yoursite.com) |
| `--path` | Path prefix (default: /docs) |
| `--output-dir` | Output directory (default: cloudflare-worker/) |
| `--skip-deploy` | Generate files only, don't deploy |
| `--force` | Overwrite existing directory |
| `--yes` | Skip all prompts (CI mode) |

If you prefer manual setup, continue with the steps below.

---

## Manual Setup

### Step 1: Create a Worker

Create a new directory for your Worker and initialize it:

```bash
mkdir docs-proxy && cd docs-proxy
npm init -y
```

### Step 2: Add the Worker Code

Create `index.js` with the following code:

```javascript index.js
const JAMDESK_HOST = "YOUR_SLUG.jamdesk.app";

// Paths to proxy to Jamdesk
const PROXY_PATHS = [
  "/docs",    // Documentation pages
  "/_next/",  // Next.js static assets
  "/_jd/",    // Jamdesk assets (fonts, images, branding, analytics)
];

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const path = url.pathname;

    // Check if this path should be proxied
    const shouldProxy = PROXY_PATHS.some(prefix =>
      path === prefix || path.startsWith(prefix.endsWith("/") ? prefix : prefix + "/")
    );

    if (!shouldProxy) {
      return fetch(request);
    }

    // Rewrite the request to Jamdesk
    const proxyUrl = new URL(request.url);
    proxyUrl.hostname = JAMDESK_HOST;

    // Clone headers and add proxy headers
    const headers = new Headers(request.headers);
    headers.set("Host", JAMDESK_HOST);
    headers.set("X-Forwarded-Host", url.hostname);
    headers.set("X-Forwarded-Proto", "https");
    // Required for domain verification
    headers.set("X-Jamdesk-Forwarded-Host", url.hostname);

    // Don't follow redirects; let the browser handle them so the URL updates.
    // Without this, redirects happen internally and the browser URL doesn't change,
    // which causes the sidebar to mis-highlight the active page.
    const proxyRequest = new Request(proxyUrl, {
      method: request.method,
      headers,
      body: request.body,
      redirect: "manual",
    });

    // Cache all content types at Cloudflare edge (CF doesn't cache HTML by default).
    // Cache duration is controlled by upstream Cache-Control headers.
    // Never cache redirects or errors; they must always hit origin.
    return fetch(proxyRequest, {
      cf: {
        cacheEverything: true,
        cacheTtlByStatus: { "300-399": 0, "500-599": 0 },
      },
    });
  },
};
```

<Note>
Replace `YOUR_SLUG` with your actual Jamdesk subdomain (e.g., `acme` if your docs are at `acme.jamdesk.app`).
</Note>

<Warning>
The `X-Jamdesk-Forwarded-Host` header is **required**. It enables:
- Domain verification (your domain must be registered in the dashboard)
- Correct URL generation for links and assets
- Proper configuration from your dashboard settings

Without this header, requests will be rejected with a 403 error.
</Warning>

### Step 3: Configure wrangler.toml

Create `wrangler.toml` to configure your Worker:

```toml wrangler.toml
name = "docs-proxy"
main = "index.js"
compatibility_date = "2024-01-01"

# Single catch-all route; the worker handles path filtering internally
routes = [
  { pattern = "yoursite.com/*", zone_name = "yoursite.com" },
]
```

<Tip>
If your site also serves traffic on `www.yoursite.com`, add a second route so the Worker handles both:

```toml
routes = [
  { pattern = "yoursite.com/*", zone_name = "yoursite.com" },
  { pattern = "www.yoursite.com/*", zone_name = "yoursite.com" },
]
```
</Tip>

### Step 4: Deploy

Deploy your Worker to Cloudflare:

```bash
npx wrangler deploy
```

### Step 5: Verify

Visit `https://yoursite.com/docs` to confirm your documentation is being served correctly.

## Alternative: Service Worker Syntax

If you prefer the older Service Worker syntax (compatible with older Workers):

```javascript index.js
const JAMDESK_HOST = "YOUR_SLUG.jamdesk.app";

// Paths to proxy to Jamdesk
const PROXY_PATHS = [
  "/docs",    // Documentation pages
  "/_next/",  // Next.js static assets
  "/_jd/",    // Jamdesk assets (fonts, images, branding, analytics)
];

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);
  const path = url.pathname;

  // Check if this path should be proxied
  const shouldProxy = PROXY_PATHS.some(prefix =>
    path === prefix || path.startsWith(prefix.endsWith("/") ? prefix : prefix + "/")
  );

  if (!shouldProxy) {
    return fetch(request);
  }

  const proxyUrl = new URL(request.url);
  proxyUrl.hostname = JAMDESK_HOST;

  const headers = new Headers(request.headers);
  headers.set("Host", JAMDESK_HOST);
  headers.set("X-Forwarded-Host", url.hostname);
  headers.set("X-Forwarded-Proto", "https");
  // Required for domain verification
  headers.set("X-Jamdesk-Forwarded-Host", url.hostname);

  // Don't follow redirects — let the browser handle them so the URL updates
  return fetch(new Request(proxyUrl, {
    method: request.method,
    headers,
    body: request.body,
    redirect: "manual",
  }), {
    cf: {
      cacheEverything: true,
      cacheTtlByStatus: { "300-399": 0, "500-599": 0 },
    },
  });
}
```

## Troubleshooting

<Accordion title="No domains found in this account">
The CLI shows your available domains before zone selection. If you see "No domains found":
1. Verify you're logged into the correct Cloudflare account
2. Check that your domain is added and active in the Cloudflare dashboard
3. Run the CLI again and select "No" when asked to continue with the current account to switch accounts
</Accordion>

<Accordion title="Wrong Cloudflare account">
If you have multiple Cloudflare accounts:
1. Run `jamdesk deploy cloudflare`
2. When prompted to select an account, choose the one with your domain
3. If you need a completely different login, select **"Switch to different login"**
4. The CLI will log you out and prompt you to log in with the correct credentials
</Accordion>

<Accordion title="Zone not found during deployment">
This error means the selected zone doesn't match your Cloudflare account. Either:
- You selected a zone that belongs to a different account
- The zone was removed from Cloudflare

Fix: Re-run the CLI and select the correct zone from the list, or switch to the account that owns the zone.
</Accordion>

<Accordion title="404 errors on documentation pages">
Ensure your route pattern uses a catch-all: `yoursite.com/*` (not just `yoursite.com/docs*`). The Worker's internal `shouldProxy()` function handles path filtering.
</Accordion>

<Accordion title="Assets not loading correctly">
Two common causes:

1. **Worker not running.** Ensure your DNS record is set to **Proxied** (orange cloud) in Cloudflare. Workers only run on proxied records.
2. **Missing `X-Forwarded-Host` header.** The Worker must set this header so Jamdesk generates correct asset URLs.
</Accordion>

<Accordion title="403 Domain not authorized error">
If you see "Domain is not authorized to serve this content":

1. Verify your domain is registered in the Jamdesk dashboard
2. Complete DNS verification (TXT record) for your domain
3. Ensure the `X-Jamdesk-Forwarded-Host` header is set in your Worker code
4. Check that your domain maps to the correct project

The domain must be verified before the Worker can serve documentation.
</Accordion>

<Accordion title="Worker not triggering on root (apex) domain">
Workers only run on **proxied** (orange cloud) DNS records. If your A record is set to "DNS only" (gray cloud), requests go straight to the origin and skip the Worker entirely.

**Fix:** Toggle the A record to proxied (orange cloud) in Cloudflare DNS. Same applies to subdomains: any record with a Worker route must be proxied.
</Accordion>

<Accordion title="Domain verification stuck on Pending">
Jamdesk verifies ownership by reading your DNS record values directly. Cloudflare's proxy (orange cloud) masks these values, so verification can't complete.

**Fix:**
1. Set the DNS record to **DNS only** (gray cloud)
2. Wait for verification to complete (status changes to **active** in the dashboard)
3. Switch back to **Proxied** (orange cloud) so the Worker runs

Short version: **gray cloud** to verify → **orange cloud** to serve.
</Accordion>

<Accordion title="How caching works">
The Worker enables Cloudflare edge caching for all content types, including HTML. Cache duration is controlled by the upstream response headers: HTML is cached for 5 minutes, while static assets like images and fonts are cached much longer.

After a deploy, updated content will appear within 5 minutes without any action needed. If you need to force-refresh immediately, use Cloudflare's **Purge Cache** feature in your dashboard (Caching → Configuration → Purge Everything).
</Accordion>

<Accordion title="Wrangler version too old">
The CLI requires wrangler 3.0+. Update with:

```bash
npm install -g wrangler@latest
```
</Accordion>

## What's Next?

<Columns cols={3}>
  <Card title="Deployment Overview" icon="cloud-arrow-up" href="/deploy/overview">
    Compare hosting options
  </Card>
  <Card title="Subpath Hosting" icon="folder-tree" href="/deploy/subpath-hosting">
    Serve docs at /docs
  </Card>
  <Card title="Custom Domains" icon="globe" href="/deploy/custom-domains">
    Verify DNS and troubleshoot
  </Card>
</Columns>
