3747
Networking

Deploy a Full-Stack Next.js App on Cloudflare Workers: Complete CI/CD Guide Using GitHub Actions

Posted by u/Buconos · 2026-05-02 06:06:31

Introduction

When building modern web applications with Next.js 14 (App Router) and Supabase for authentication and Postgres, the default deployment platform is often Vercel. And for good reason—it offers an excellent developer experience. However, after running the same project on both platforms for about a week, I discovered that Cloudflare Workers can deliver lower latency (especially TTFB) and a more flexible free tier. Historically, deploying Next.js on Cloudflare was tricky—earlier solutions like Cloudflare Pages struggled with full Next.js features, and tools like next-on-pages often lagged behind. That changed with @opennextjs/cloudflare, which compiles a standard Next.js application into a Cloudflare Worker, supporting SSR, ISR, middleware, and the Image component without requiring major code changes. This guide walks you through the exact steps I used to deploy my full-stack Next.js + Supabase application to Cloudflare Workers, with automated CI/CD via GitHub Actions. It’s the runbook I wish I had when I started.

Deploy a Full-Stack Next.js App on Cloudflare Workers: Complete CI/CD Guide Using GitHub Actions
Source: www.freecodecamp.org

What You Need

  • A Next.js 14 project (App Router) with Supabase integration
  • Node.js 18+ installed locally
  • A Cloudflare account (free tier works)
  • A GitHub repository for your project
  • Basic familiarity with terminal commands

Step-by-Step Deployment Guide

Step 1: Install the Cloudflare Adapter

Start by adding the @opennextjs/cloudflare package to your project. This adapter compiles your Next.js app into a Cloudflare Worker. Run:

npm install @opennextjs/cloudflare

Then, create or update your next.config.js to include the adapter:

const withOpenNext = require('@opennextjs/cloudflare/config');

module.exports = withOpenNext({
  // your existing Next.js config
});

This step is crucial—it hooks the build process to generate a worker-compatible bundle.

Step 2: Wire OpenNext into next dev

To maintain a smooth local development experience, you need to run the Next.js dev server through OpenNext. Modify your package.json scripts:

"scripts": {
  "dev": "opennext dev",
  "build": "opennext build",
  "start": "opennext start"
}

Now, npm run dev will use the OpenNext development server, which simulates the Cloudflare Workers environment locally. This ensures your app behaves as expected before deployment.

Step 3: Local Environment Setup with .dev.vars

Cloudflare Workers use environment variables via secrets. For local development, create a .dev.vars file at the project root. This file holds local equivalents of your production secrets, such as Supabase URL and anon key. Example:

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key

Important: Add .dev.vars to your .gitignore to avoid pushing secrets to version control. In your app, you can access these via process.env.SUPABASE_URL.

Step 4: Deploy Your App from Your Local Machine

Before setting up CI/CD, test a manual deployment. First, install the Cloudflare Wrangler CLI:

npm install -g wrangler

Log in to your Cloudflare account:

wrangler login

Then build and deploy:

npm run build
npx wrangler deploy

This will create a worker named after your project (you can rename it in wrangler.toml). After successful deployment, Wrangler outputs your worker URL. Visit it to verify your app runs.

Step 5: Push Your Secrets to the Worker

Your Supabase keys and other sensitive data must be stored as Cloudflare Worker secrets, not hardcoded. Use Wrangler to set each secret:

wrangler secret put SUPABASE_URL
wrangler secret put SUPABASE_ANON_KEY

You’ll be prompted to enter the value. Repeat for all secrets your app needs. These are encrypted and only accessible to the worker at runtime.

Step 6: Set Up Continuous Deployment with GitHub Actions

Automate deployments so that every push to your main branch triggers a build and deploy. Create the file .github/workflows/deploy.yml in your repository with the following content:

name: Deploy to Cloudflare Workers

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Deploy to Cloudflare Workers
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}

You need to create a Cloudflare API token with Workers Scripts:Edit permission, and add it as a GitHub secret named CF_API_TOKEN. Also add your Supabase secrets as GitHub secrets (e.g., SUPABASE_URL, SUPABASE_ANON_KEY) so the action can set them on the worker. To do that, extend the workflow to set secrets using wrangler secret put commands, or use environment variables in the Wrangler action. A cleaner approach: use wrangler secret bulk with a JSON file. For brevity, the above action assumes secrets are already set manually once. After initial setup, each push will automatically deploy the latest code.

Step 7: Daily Workflow – Updating the Project

Once CI/CD is running, your daily workflow is simple:

  1. Pull the latest changes from main.
  2. Create a feature branch, make changes, and commit.
  3. Push the branch and open a pull request.
  4. After merging to main, GitHub Actions automatically builds and deploys to Cloudflare Workers.
  5. Monitor deployment status via the Actions tab.

If you need to update secrets, do so manually via wrangler secret put or update GitHub secrets and rerun the workflow.

Tips & Best Practices

  • Watch out for Node.js APIs: Cloudflare Workers use V8 isolates, which lack full Node.js support (e.g., fs, child_process). If your app relies on those, you may need to refactor or use Cloudflare’s compatibility flags.
  • Use .dev.vars wisely: Keep it in sync with your production secrets to avoid surprises.
  • Optimize cold starts: While Workers have near-zero cold starts, complex middleware can still cause delays. Keep middleware lightweight.
  • Leverage Cloudflare bindings: For image optimization, use the IMAGES binding instead of Next.js built-in image optimization to reduce costs.
  • Monitor usage: The free tier is generous, but check your dashboard for request counts and bandwidth to avoid unexpected limits.
  • Test locally with wrangler dev: Before deploying, run npx wrangler dev to simulate the Cloudflare environment and catch issues early.

Deploying Next.js on Cloudflare Workers is now as smooth as on Vercel, thanks to OpenNext. With this guide, you can enjoy lower latency, minimal cold starts, and cost-effective scaling—all with automated CI/CD via GitHub Actions.