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@v4and pnpm caching - Caching build artifacts using
actions/cache@v4for.next/cacheand.open-nextdirectories - Installing dependencies with
pnpm install --no-frozen-lockfile - Executing
pnpm run preview:cfwhich invokesnpx @opennextjs/cloudflare previewas defined inpackage.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.ymlto limit preview deployments to PR events only. - Build: Execute
pnpm run preview:cfto generate Cloudflare-compatible workers via OpenNext, caching.next/cacheand.open-nextdirectories for performance. - Database: Run
d1 migrations apply next-cf-app --env previewwithenvironment: previewto sync schema changes to the isolated preview D1 instance. - Deploy: Use
cloudflare/wrangler-action@v3with theenvironment: previewflag and pass application secrets via thesecrets:block. - Notify: Implement PR comments using
actions/github-script@v7to 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →