# How to Set Up Preview Deployments with GitHub Actions for Cloudflare

> Learn to set up preview deployments with GitHub Actions for Cloudflare. Build, migrate, deploy your Next.js app, and get PR URLs automatically.

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

---

**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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.github/workflows/deploy.yml). This ensures preview resources are only consumed when actively reviewing code changes (lines 15-18).

```yaml
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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/package.json) (lines 14-15)

This produces the worker bundle at [`.open-next/worker.js`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.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.

```yaml
- 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.

```yaml
- 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.

```yaml
- 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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.github/workflows/deploy.yml):

```yaml
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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.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`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/.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.