Deploying a Full-Stack Next.js App to Cloudflare Workers with GitHub Actions CI/CD: A Step-by-Step Guide
By • min read
<h2 id="overview">Overview</h2>
<p>Every developer wants a fast, cost-effective deployment for their Next.js projects. While Vercel remains the go-to platform for many, <strong>Cloudflare Workers</strong> offers a compelling alternative with near-zero cold starts, a generous free tier, and edge distribution across 300+ locations. With the arrival of <strong>@opennextjs/cloudflare</strong>, deploying a standard Next.js 14 app (App Router) with SSR, ISR, middleware, and the Image component is finally straightforward — no more fighting with workarounds.</p><figure style="margin:20px 0"><img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/cbb9e559-baa7-452c-992a-3416041712ad.png" alt="Deploying a Full-Stack Next.js App to Cloudflare Workers with GitHub Actions CI/CD: A Step-by-Step Guide" 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>
<p>In this tutorial, we will walk through deploying a full-stack Next.js + Supabase application to Cloudflare Workers, complete with a continuous deployment pipeline using GitHub Actions. By the end, you’ll have your own runbook for a smooth, repeatable deployment process.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>Before you begin, ensure you have the following ready:</p>
<ul>
<li>A Next.js 14 project using the App Router (with or without Supabase).</li>
<li>Node.js 18+ installed locally.</li>
<li>A Cloudflare account (free tier works).</li>
<li>Your Cloudflare API token with <strong>Workers</strong> and <strong>Pages</strong> permissions.</li>
<li>A GitHub repository for your project.</li>
<li>Supabase project credentials (if using Supabase).</li>
</ul>
<p>Familiarity with basic Git, command line, and GitHub Actions is helpful but not required; we will cover every command.</p>
<h3 id="the-stack">The Stack</h3>
<p>We’re using Next.js 14 (App Router) for the frontend, Supabase for authentication and Postgres database, <strong>@opennextjs/cloudflare</strong> as the adapter, and Cloudflare Workers as the runtime. CI/CD is handled by GitHub Actions.</p>
<h2 id="step-by-step">Step-by-Step Instructions</h2>
<h3 id="step1">Step 1 — Install the Cloudflare Adapter</h3>
<p>Inside your Next.js project root, install the required package:</p>
<pre><code>npm install @opennextjs/cloudflare</code></pre>
<p>This adapter compiles your Next.js application into a format compatible with Cloudflare Workers. It supports SSR, ISR, middleware, and — through the <code>IMAGES</code> binding — image optimization.</p>
<h3 id="step2">Step 2 — Wire OpenNext into <code>next dev</code></h3>
<p>To use the same runtime during local development, add a custom script in your <code>package.json</code>:</p>
<pre><code>"scripts": {
"dev": "opennextjs-cloudflare",
"build": "opennextjs-cloudflare build",
"start": "wrangler pages dev"
}</code></pre>
<p>This replaces the standard Next.js dev server with OpenNext’s local Cloudflare Workers simulation. Run <code>npm run dev</code> to test locally.</p>
<h3 id="step3">Step 3 — Local Environment Setup with <code>.dev.vars</code></h3>
<p>In the root of your project, create a file named <code>.dev.vars</code> to store local environment variables (e.g., Supabase URL and anon key). Format:</p>
<pre><code>SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
</code></pre>
<p>Add <code>.dev.vars</code> to your <code>.gitignore</code> so secrets never leak.</p>
<h3 id="step4">Step 4 — Deploy Your App from Your Local Machine</h3>
<p>First, build the worker bundle:</p>
<pre><code>npm run build</code></pre>
<p>Then deploy using Wrangler (Cloudflare’s CLI tool). Ensure you have logged in:</p>
<pre><code>npx wrangler login
npx wrangler pages deploy .open-next --project-name my-next-app</code></pre>
<p>This uploads the compiled output to Cloudflare Pages (which runs on Workers). You’ll get a preview URL like <code>https://my-next-app.pages.dev</code>.</p>
<h3 id="step5">Step 5 — Push Your Secrets to the Worker</h3>
<p>Production secrets (Supabase credentials, etc.) must be added separately via Wrangler:</p>
<pre><code>npx wrangler pages secret put SUPABASE_URL
npx wrangler pages secret put SUPABASE_ANON_KEY</code></pre>
<p>You’ll be prompted to enter the values. These are now encrypted and available to your Worker at runtime.</p>
<h3 id="step6">Step 6 — Set Up Continuous Deployment with GitHub Actions</h3>
<p>Create a file <code>.github/workflows/deploy.yml</code> in your repository:</p>
<pre><code>name: Deploy to Cloudflare Workers
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
command: pages deploy .open-next --project-name my-next-app
</code></pre>
<p>Add your Cloudflare API token as a secret named <code>CF_API_TOKEN</code> in your GitHub repository settings. Now every push to <code>main</code> triggers a build and deploy.</p>
<h3 id="step7">Step 7 — Updating the Project (The Daily Workflow)</h3>
<p>Your daily routine becomes:</p>
<ol>
<li>Make changes locally.</li>
<li>Test with <code>npm run dev</code> (uses OpenNext simulation).</li>
<li>Commit and push to <code>main</code>.</li>
<li>GitHub Actions builds and deploys automatically.</li>
<li>Check deployment logs in the Actions tab.</li>
</ol>
<p>For environment variable changes, update them via <code>wrangler pages secret put</code> or in the Cloudflare dashboard, then redeploy.</p>
<h2 id="common-mistakes">Common Mistakes</h2>
<ul>
<li><strong>Forgetting to add <code>.dev.vars</code> to <code>.gitignore</code></strong>: This exposes sensitive data. Always ignore it.</li>
<li><strong>Using Node.js native modules</strong>: Cloudflare Workers run on V8 isolates, not Node.js. Avoid <code>fs</code>, <code>crypto</code> (use Web Crypto API), and <code>child_process</code>.</li>
<li><strong>Missing API token permissions</strong>: Your Cloudflare token must have <strong>Workers</strong> and <strong>Pages</strong> write permissions for the deploy action to succeed.</li>
<li><strong>Not running <code>opennextjs-cloudflare build</code> before deploy</strong>: The standard <code>next build</code> won’t produce a Worker‑compatible bundle.</li>
<li><strong>Using relative paths for environment variables inside <code>wrangler.toml</code></strong>: Prefer Wrangler CLI commands or secrets dashboard to set variables for Workers.</li>
</ul>
<h2 id="summary">Summary</h2>
<p>We have deployed a full-stack Next.js + Supabase application to Cloudflare Workers using the <strong>@opennextjs/cloudflare</strong> adapter, with a GitHub Actions CI/CD pipeline. The process is simple: install the adapter, adjust your dev script, configure local variables, build and deploy manually once, then let automation handle future releases.</p>
<p>The benefits are tangible: lower latency thanks to Cloudflare’s global edge, near‑zero cold starts, and a free tier that can handle millions of requests per month. The trade-off — a different runtime environment with limited Node.js APIs — is manageable for most modern Next.js apps.</p>
<p>Now you have a reproducible runbook. Deploy with confidence and explore the edge.</p>