# How to Configure Custom Domains for Cloudflare Workers Deployment in Next.js 15

> Configure custom domains for Cloudflare Workers deployment in Next.js. Register your domain, bind routes in wrangler.jsonc, and set R2 URL secrets for branded asset serving.

- Repository: [Muhammad Arifin/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare)
- Tags: how-to-guide
- Published: 2026-03-04

---

**To configure custom domains for Cloudflare Workers deployment, register your domain in the Cloudflare Dashboard, bind it via the `routes` array in `wrangler.jsonc`, and set the `CLOUDFLARE_R2_URL` secret to serve R2 assets from your branded URL.**

This guide explains how to configure custom domains for Cloudflare Workers deployment using the `ifindev/fullstack-next-cloudflare` repository—a Next.js 15 application built for Cloudflare's edge runtime. By default, Wrangler deploys your worker to a `*.workers.dev` subdomain, but production environments require custom hostnames for both the application entry point and publicly accessible R2 storage.

## Step 1: Register the Custom Domain in Cloudflare

Before modifying code, register your domain in the Cloudflare Dashboard. Navigate to **Workers & Pages** → select your worker → **Custom Domains** → **Add Domain** (e.g., `app.my-domain.com`). Cloudflare automatically generates a DNS CNAME record pointing to `<worker-name>.<subdomain>.workers.dev`. Add this record to your zone file and wait for DNS propagation, which typically completes within minutes.

## Step 2: Bind Routes in `wrangler.jsonc`

The `wrangler.jsonc` configuration file controls how Cloudflare routes incoming requests to your worker. To bind your custom domain, add a `routes` array that matches your hostname pattern. According to the source code in `ifindev/fullstack-next-cloudflare`, this entry tells the edge network to invoke your worker for all requests hitting your custom domain.

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "next-cf-app",
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-03-01",
  "routes": [
    "app.my-domain.com/*"
  ],
  "assets": {
    "binding": "ASSETS",
    "directory": ".open-next/assets"
  },
  "r2_buckets": [
    {
      "bucket_name": "next-cf-app-bucket",
      "binding": "next_cf_app_bucket"
    }
  ]
}

```

The `routes` key supports multiple entries or wildcard patterns, allowing you to serve the worker from both `app.my-domain.com/*` and `api.my-domain.com/*` simultaneously.

## Step 3: Configure R2 Custom Domains and Environment Secrets

Static assets stored in R2 require a separate custom domain configuration. In the Cloudflare Dashboard, navigate to **R2** → **Custom Domains** and create a dedicated hostname (e.g., `files.my-domain.com`) for your bucket. Then, expose this domain to your application via the `CLOUDFLARE_R2_URL` secret.

In [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts), the `uploadToR2` function constructs public URLs by reading this secret from the environment at lines 49–50:

```typescript
// src/lib/r2.ts
export async function uploadToR2(file: File, folder = "uploads"): Promise<UploadResult> {
  const { env } = await getCloudflareContext();
  
  const key = `${folder}/${Date.now()}_${Math.random().toString(36).substring(2)}.${file.name.split(".").pop()}`;
  
  await env.next_cf_app_bucket.put(key, await file.arrayBuffer(), {
    httpMetadata: { contentType: file.type }
  });

  // Uses the custom domain stored in CLOUDFLARE_R2_URL
  const publicUrl = `https://${(env as any).CLOUDFLARE_R2_URL}/${key}`;
  
  return { success: true, url: publicUrl, key };
}

```

Set the secret locally using Wrangler:

```bash
echo "files.my-domain.com" | wrangler secret put CLOUDFLARE_R2_URL

```

For production, the GitHub Actions workflow in [`.github/workflows/deploy.yml`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.github/workflows/deploy.yml) injects this secret automatically during deployment.

## Step 4: Deploy via CLI or GitHub Actions

Once `wrangler.jsonc` and secrets are configured, deploy your worker. For local deployments, run:

```bash
pnpm run deploy

```

For continuous deployment, the repository includes a GitHub Actions workflow that pushes the worker and injects the required secrets. The `Deploy to Production` job passes `CLOUDFLARE_R2_URL` along with authentication credentials:

```yaml

# .github/workflows/deploy.yml

- name: Deploy to Production
  uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    secrets: |
      BETTER_AUTH_SECRET
      GOOGLE_CLIENT_ID
      GOOGLE_CLIENT_SECRET
      CLOUDFLARE_R2_URL

```

After deployment, verify connectivity by curling your custom domain endpoint (e.g., `curl -f https://app.my-domain.com/api/todos`).

## Summary

- **Dashboard Configuration**: Register the custom domain under Workers → Custom Domains and add the provided CNAME record to your DNS zone.
- **Route Binding**: Add your hostname to the `routes` array in `wrangler.jsonc` to map incoming requests to the worker.
- **R2 Asset Domain**: Configure a separate custom domain for R2 in the Cloudflare Dashboard, then store it in the `CLOUDFLARE_R2_URL` secret referenced by [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts).
- **Secret Management**: Use `wrangler secret put` locally or the `secrets:` block in [`.github/workflows/deploy.yml`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.github/workflows/deploy.yml) for CI/CD injection.
- **Verification**: Confirm deployment success by accessing both the application root and uploaded R2 assets via your custom URLs.

## Frequently Asked Questions

### How long does DNS propagation take after adding a custom domain?

DNS propagation typically completes within five minutes when using Cloudflare's nameservers, though it can take up to 24 hours depending on TTL settings and regional DNS caches. Verify propagation by running `dig app.my-domain.com` and confirming the CNAME points to your `workers.dev` subdomain.

### Can I use multiple custom domains for the same Cloudflare Worker?

Yes. The `routes` field in `wrangler.jsonc` accepts an array of patterns, allowing you to bind multiple hostnames (e.g., `app.my-domain.com/*` and `www.my-domain.com/*`) to a single worker deployment. Each domain must be registered individually in the Cloudflare Dashboard.

### Why does my R2 bucket need a separate custom domain from the worker?

R2 custom domains provide a dedicated hostname for object storage (e.g., `files.my-domain.com`), separating asset delivery from application logic. This isolation improves cacheability, allows direct browser access to objects, and prevents CORS issues when loading media from a different origin than your Next.js app.

### Where should I store the `CLOUDFLARE_R2_URL` secret for local development?

For local development, store the secret using `wrangler secret put CLOUDFLARE_R2_URL` or define it in a `.dev.vars` file in your project root. The `getCloudflareContext` function used in [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) reads from these local secrets during `wrangler dev` sessions, mimicking the production environment where the value is injected by the GitHub Actions workflow.