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

By — min read
<h2>Introduction</h2><p>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 <strong>@opennextjs/cloudflare</strong>, 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.</p><figure style="margin:20px 0"><img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/cbb9e559-baa7-452c-992a-3416041712ad.png" alt="Deploy a Full-Stack Next.js App on Cloudflare Workers: Complete CI/CD Guide Using GitHub Actions" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: www.freecodecamp.org</figcaption></figure><h2 id="prerequisites">What You Need</h2><ul><li>A Next.js 14 project (App Router) with Supabase integration</li><li>Node.js 18+ installed locally</li><li>A Cloudflare account (free tier works)</li><li>A GitHub repository for your project</li><li>Basic familiarity with terminal commands</li></ul><h2>Step-by-Step Deployment Guide</h2><h3 id="step1">Step 1: Install the Cloudflare Adapter</h3><p>Start by adding the <strong>@opennextjs/cloudflare</strong> package to your project. This adapter compiles your Next.js app into a Cloudflare Worker. Run:</p><pre><code>npm install @opennextjs/cloudflare</code></pre><p>Then, create or update your <code>next.config.js</code> to include the adapter:</p><pre><code>const withOpenNext = require('@opennextjs/cloudflare/config'); module.exports = withOpenNext({ // your existing Next.js config });</code></pre><p>This step is crucial—it hooks the build process to generate a worker-compatible bundle.</p><h3 id="step2">Step 2: Wire OpenNext into <code>next dev</code></h3><p>To maintain a smooth local development experience, you need to run the Next.js dev server through OpenNext. Modify your <code>package.json</code> scripts:</p><pre><code>"scripts": { "dev": "opennext dev", "build": "opennext build", "start": "opennext start" }</code></pre><p>Now, <code>npm run dev</code> will use the OpenNext development server, which simulates the Cloudflare Workers environment locally. This ensures your app behaves as expected before deployment.</p><h3 id="step3">Step 3: Local Environment Setup with <code>.dev.vars</code></h3><p>Cloudflare Workers use environment variables via secrets. For local development, create a <code>.dev.vars</code> file at the project root. This file holds local equivalents of your production secrets, such as Supabase URL and anon key. Example:</p><pre><code>SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=your-anon-key </code></pre><p><strong>Important:</strong> Add <code>.dev.vars</code> to your <code>.gitignore</code> to avoid pushing secrets to version control. In your app, you can access these via <code>process.env.SUPABASE_URL</code>.</p><h3 id="step4">Step 4: Deploy Your App from Your Local Machine</h3><p>Before setting up CI/CD, test a manual deployment. First, install the Cloudflare Wrangler CLI:</p><pre><code>npm install -g wrangler</code></pre><p>Log in to your Cloudflare account:</p><pre><code>wrangler login</code></pre><p>Then build and deploy:</p><pre><code>npm run build npx wrangler deploy</code></pre><p>This will create a worker named after your project (you can rename it in <code>wrangler.toml</code>). After successful deployment, Wrangler outputs your worker URL. Visit it to verify your app runs.</p><h3 id="step5">Step 5: Push Your Secrets to the Worker</h3><p>Your Supabase keys and other sensitive data must be stored as Cloudflare Worker secrets, not hardcoded. Use Wrangler to set each secret:</p><pre><code>wrangler secret put SUPABASE_URL wrangler secret put SUPABASE_ANON_KEY</code></pre><p>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.</p><h3 id="step6">Step 6: Set Up Continuous Deployment with GitHub Actions</h3><p>Automate deployments so that every push to your main branch triggers a build and deploy. Create the file <code>.github/workflows/deploy.yml</code> in your repository with the following content:</p><pre><code>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 }} </code></pre><p>You need to create a Cloudflare API token with <strong>Workers Scripts:Edit</strong> permission, and add it as a GitHub secret named <code>CF_API_TOKEN</code>. Also add your Supabase secrets as GitHub secrets (e.g., <code>SUPABASE_URL</code>, <code>SUPABASE_ANON_KEY</code>) so the action can set them on the worker. To do that, extend the workflow to set secrets using <code>wrangler secret put</code> commands, or use environment variables in the Wrangler action. A cleaner approach: use <code>wrangler secret bulk</code> 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.</p><h3 id="step7">Step 7: Daily Workflow – Updating the Project</h3><p>Once CI/CD is running, your daily workflow is simple:</p><ol><li>Pull the latest changes from <code>main</code>.</li><li>Create a feature branch, make changes, and commit.</li><li>Push the branch and open a pull request.</li><li>After merging to <code>main</code>, GitHub Actions automatically builds and deploys to Cloudflare Workers.</li><li>Monitor deployment status via the Actions tab.</li></ol><p>If you need to update secrets, do so manually via <code>wrangler secret put</code> or update GitHub secrets and rerun the workflow.</p><h2>Tips &amp; Best Practices</h2><ul><li><strong>Watch out for Node.js APIs:</strong> Cloudflare Workers use V8 isolates, which lack full Node.js support (e.g., <code>fs</code>, <code>child_process</code>). If your app relies on those, you may need to refactor or use Cloudflare’s compatibility flags.</li><li><strong>Use <code>.dev.vars</code> wisely:</strong> Keep it in sync with your production secrets to avoid surprises.</li><li><strong>Optimize cold starts:</strong> While Workers have near-zero cold starts, complex middleware can still cause delays. Keep middleware lightweight.</li><li><strong>Leverage Cloudflare bindings:</strong> For image optimization, use the IMAGES binding instead of Next.js built-in image optimization to reduce costs.</li><li><strong>Monitor usage:</strong> The free tier is generous, but check your dashboard for request counts and bandwidth to avoid unexpected limits.</li><li><strong>Test locally with wrangler dev:</strong> Before deploying, run <code>npx wrangler dev</code> to simulate the Cloudflare environment and catch issues early.</li></ul><p>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.</p>
Tags: