How to Set Up Preview Deployments with GitHub Actions for Cloudflare

Configure a GitHub Actions workflow that triggers on pull requests to build your Next.js application with OpenNext, apply D1 migrations to a preview database, deploy to Cloudflare Workers, and automatically comment the preview URL on your PR.

The repository ifindev/fullstack-next-cloudflare demonstrates a production-ready implementation of preview deployments with GitHub Actions for Cloudflare. This setup automatically provisions isolated preview environments for every pull request, allowing teams to test changes against live Cloudflare infrastructure—including D1 databases and R2 storage—before merging to production.

Configuring the Workflow Trigger

The pipeline activates exclusively for pull request events using a conditional job definition in .github/workflows/deploy.yml. This ensures preview resources are only consumed when actively reviewing code changes (lines 15-18).

deploy-preview:
  if: github.event_name == 'pull_request'

Building the Application with OpenNext

The workflow uses pnpm and OpenNext to generate a Cloudflare-compatible worker bundle. The build process follows these steps:

  • Checking out code with actions/checkout@v4
  • Setting up pnpm via pnpm/action-setup@v2
  • Configuring Node.js with actions/setup-node@v4 and pnpm caching
  • Caching build artifacts using actions/cache@v4 for .next/cache and .open-next directories
  • Installing dependencies with pnpm install --no-frozen-lockfile
  • Executing pnpm run preview:cf which invokes npx @opennextjs/cloudflare preview as defined in package.json (lines 14-15)

This produces the worker bundle at .open-next/worker.js required for Cloudflare deployment.

Running D1 Migrations Against Preview

Before deployment, the workflow applies database migrations to the preview D1 instance to ensure schema consistency.

- name: Run database migrations (Preview)
  uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    environment: preview
    command: d1 migrations apply next-cf-app --env preview

(lines 62-68). The environment: preview flag targets the preview database binding defined in wrangler.jsonc, isolating schema changes from production data.

Deploying to Cloudflare Preview

The deployment step passes environment secrets to the worker while targeting the preview environment.

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

(lines 70-76). This deploys the worker to the preview environment while injecting required authentication and storage credentials through the secrets: block.

Posting the Preview URL to Pull Requests

The final step uses actions/github-script@v7 to comment on the PR with the deployment URL.

- name: Comment PR with preview URL
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: '🚀 Preview deployed! Check it out at: https://next-cf-app-preview.your-subdomain.workers.dev'
      })

(lines 87-95). This provides immediate visibility for reviewers to access the live preview environment.

Complete Workflow Configuration

Here is the full deploy-preview job from .github/workflows/deploy.yml:

deploy-preview:
  name: Deploy Preview
  if: github.event_name == 'pull_request'
  runs-on: ubuntu-latest
  environment: preview
  steps:
    - uses: actions/checkout@v4
    - uses: pnpm/action-setup@v2
      with: { version: ${{ env.PNPM_VERSION }} }
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: pnpm
    - uses: actions/cache@v4
      with:
        path: |
          .next/cache
          .open-next
        key: ${{ runner.os }}-opennext-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.[jt]s', '**/*.[jt]sx', '**/*.ts', '**/*.tsx') }}
    - run: pnpm install --no-frozen-lockfile
    - run: pnpm run preview:cf
      env:
        BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
        GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
        GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
    - uses: cloudflare/wrangler-action@v3
      with:
        apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        environment: preview
        command: d1 migrations apply next-cf-app --env preview
    - uses: cloudflare/wrangler-action@v3
      with:
        apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        environment: preview
        secrets: |
          BETTER_AUTH_SECRET
          GOOGLE_CLIENT_ID
          GOOGLE_CLIENT_SECRET
          CLOUDFLARE_R2_URL
    - uses: actions/github-script@v7
      with:
        script: |
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: '🚀 Preview deployed! Check it out at: https://next-cf-app-preview.your-subdomain.workers.dev'
          })

Summary

  • Trigger: Use if: github.event_name == 'pull_request' in .github/workflows/deploy.yml to limit preview deployments to PR events only.
  • Build: Execute pnpm run preview:cf to generate Cloudflare-compatible workers via OpenNext, caching .next/cache and .open-next directories for performance.
  • Database: Run d1 migrations apply next-cf-app --env preview with environment: preview to sync schema changes to the isolated preview D1 instance.
  • Deploy: Use cloudflare/wrangler-action@v3 with the environment: preview flag and pass application secrets via the secrets: block.
  • Notify: Implement PR comments using actions/github-script@v7 to share the live preview URL with reviewers immediately after deployment.

Frequently Asked Questions

How do I configure secrets for Cloudflare preview deployments?

Store CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as repository secrets in GitHub. The workflow injects these into the cloudflare/wrangler-action steps for authentication. Additional application secrets like BETTER_AUTH_SECRET and GOOGLE_CLIENT_ID are passed through the secrets: block in the deploy step (lines 70-76) to ensure they are available as environment variables in the preview worker.

What is the purpose of the preview:cf script in package.json?

The preview:cf script executes npx @opennextjs/cloudflare preview, which builds your Next.js application into a Cloudflare Worker-compatible bundle stored at .open-next/worker.js. This OpenNext build process handles framework-specific adaptations required for the Cloudflare edge runtime, converting Next.js server components and API routes into Worker-compatible formats.

How does the workflow handle database migrations for preview environments?

The workflow runs d1 migrations apply next-cf-app --env preview using cloudflare/wrangler-action@v3 with the environment: preview flag explicitly set. This targets the preview-specific D1 database binding defined in wrangler.jsonc, ensuring schema changes are applied to an isolated preview database before the worker deployment completes, preventing pollution of production data.

Can I modify the preview URL format or domain?

Yes. The preview URL is determined by your Cloudflare Workers configuration in wrangler.jsonc and your account's subdomain settings. To customize the URL shown in PR comments, modify the body parameter in the actions/github-script step (lines 87-95) to match your actual preview subdomain pattern. Alternatively, you can capture the deployment URL output from the Wrangler action and inject it dynamically into the comment body.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →