· nextjs / cloudflare / workers

Cách deploy Next.js lên Cloudflare Workers (2026)

Hướng dẫn deploy Next.js lên Cloudflare Workers 2026: @opennextjs/cloudflare, wrangler.jsonc, 330 edge city trong 90 giây, và pipeline GitHub Actions CI/CD.

Bởi

1.637 từ · 9 phút đọc

Cloudflare Workers là nơi tốt nhất để chạy Next.js vào năm 2026. Bạn có edge delivery trên ~330 thành phố, không có cold start, gói free hào phóng, và hệ sinh thái Workers (D1, R2, KV) cho mọi thứ cần trạng thái. Deploy propagation toàn cầu mất khoảng 90 giây — không phải vài phút.

Adapter giúp mọi thứ hoạt động là @opennextjs/cloudflare. Cloudflare đồng phát triển nó cùng với OpenNext team. Adapter cũ @cloudflare/next-on-pages đã bị thay thế — nếu bạn đang cài nó, hãy gỡ trước khi theo hướng dẫn này.

Dành cho ai

Developer Next.js muốn đưa sản phẩm lên hạ tầng Cloudflare. Bạn cần thoải mái với terminal và npm/pnpm. Hướng dẫn này bao gồm cả dự án mới lẫn migrate ứng dụng hiện có.

Yêu cầu trước khi bắt đầu

  • Node.js 20 trở lên
  • npm hoặc pnpm
  • Tài khoản Cloudflare (miễn phí tại cloudflare.com)
  • Wrangler CLI 4.x — cài bằng npm install --save-dev wrangler@latest

Tạo mới hoặc chuẩn bị ứng dụng Next.js

Dự án mới: cách nhanh nhất là dùng lệnh create của Cloudflare, tự động scaffold một ứng dụng Next.js đã cấu hình sẵn cho Workers:

npm create cloudflare@latest -- my-next-app --framework=next

Dự án hiện có: bỏ qua phần này và chuyển sang mục tiếp — bạn sẽ thêm adapter thủ công.

Nếu bạn muốn dùng điểm khởi đầu Next.js thuần:

npx create-next-app@latest my-next-app
cd my-next-app

Dùng App Router và TypeScript. Adapter hỗ trợ cả Pages Router, nhưng App Router là lựa chọn hướng tới tương lai.

Cài @opennextjs/cloudflare

npm install @opennextjs/cloudflare@latest
npm install --save-dev wrangler@latest

Tại thời điểm viết bài, phiên bản stable mới nhất là @opennextjs/[email protected] (phát hành tháng 5/2026).

Nếu dự án trước đây dùng @cloudflare/next-on-pages, hãy gỡ hoàn toàn — kể cả các lệnh gọi setupDevPlatform() trong next.config.ts. Hai adapter này xung đột nhau ở build time.

npm uninstall @cloudflare/next-on-pages

Cấu hình wrangler.jsonc

Tạo wrangler.jsonc ở thư mục gốc dự án. Định dạng JSONC hỗ trợ comment inline và được Cloudflare tooling ưa dùng:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "main": ".open-next/worker.js",
  "name": "my-app",
  "compatibility_date": "2024-12-30",
  "compatibility_flags": [
    "nodejs_compat",
    "global_fetch_strictly_public"
  ],
  "assets": {
    "directory": ".open-next/assets",
    "binding": "ASSETS"
  },
  "services": [
    {
      "binding": "WORKER_SELF_REFERENCE",
      "service": "my-app"
    }
  ]
}

Giải thích từng trường:

  • main: trỏ vào file entry của Worker do OpenNext adapter tạo ra
  • compatibility_date: phải từ 2024-09-23 trở đi — ngày này kích hoạt toàn bộ layer tương thích Node.js; 2024-12-30 là lựa chọn ổn định
  • nodejs_compat: flag bắt buộc; thiếu nó thì phần lớn code server của Next.js sẽ lỗi khi runtime
  • global_fetch_strictly_public: cần thiết để fetch hoạt động đúng từ bên trong Worker
  • assets binding: cho phép Workers serve file tĩnh — trước đây chỉ có trên Pages, giờ đã có trên Workers qua binding này
  • WORKER_SELF_REFERENCE service binding: cần cho routing nội bộ của adapter; thiếu binding này sẽ gây lỗi 500 trên workers.dev

Thay "my-app" bằng tên Worker thực của bạn ở cả trường name và giá trị service trong services.

Thêm các file cấu hình hỗ trợ

open-next.config.ts

Adapter cần file config này ở thư mục gốc dự án. Nếu bạn muốn ISR (Incremental Static Regeneration) hoạt động, bạn phải cấu hình thêm R2 incremental cache — không có nó, ISR sẽ fall back về full SSR với mỗi request:

import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";

export default defineCloudflareConfig({
  incrementalCache: r2IncrementalCache,
});

Nếu không dùng ISR, config tối giản không có cache override là đủ để bắt đầu:

import { defineCloudflareConfig } from "@opennextjs/cloudflare";

export default defineCloudflareConfig({});

next.config.ts

Thêm lệnh gọi dev integration để next dev local có thể kết nối với Wrangler bindings:

import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";

initOpenNextCloudflareForDev();

const nextConfig = {
  // cấu hình hiện có của bạn
};

export default nextConfig;

.dev.vars

File này chứa biến môi trường chỉ dùng ở máy local, Wrangler đọc khi chạy chế độ preview:

NEXTJS_ENV=development

Thêm .dev.vars vào .gitignore — file này không được commit.

Scripts trong package.json

Thay thế hoặc thêm vào block scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
    "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"
  }
}

Cũng thêm .open-next/ vào .gitignore:

.open-next/
.dev.vars

Phát triển ở máy local

Hai chế độ, hai mục đích khác nhau:

npm run dev chạy dev server Node.js tiêu chuẩn của Next.js. Hot-reload nhanh, tốt để làm UI. Chế độ này KHÔNG dùng Workers runtime.

npm run preview build ứng dụng qua OpenNext, rồi chạy ở local trong workerd — Workers runtime thực tế của Cloudflare. Khởi động chậm hơn, nhưng sát với production. Luôn dùng preview để kiểm tra ISR, middleware, caching, và các Workers bindings (KV, D1, R2) trước khi deploy.

Sự phân biệt này quan trọng: code chạy tốt với next dev có thể lỗi trong workerd nếu nó phụ thuộc vào Node.js API chưa có ở compatibility date của bạn.

Deploy bằng CLI

npm run deploy

Lệnh này chạy opennextjs-cloudflare build để compile ứng dụng vào .open-next/, sau đó opennextjs-cloudflare deploy gọi Wrangler bên dưới. Wrangler bundle Worker và upload lên mạng lưới Cloudflare. Propagation tới ~330 edge city mất khoảng 90 giây.

Deploy đầu tiên cũng tạo Worker trong tài khoản Cloudflare của bạn. Các lần deploy sau cập nhật trực tiếp.

Kết nối GitHub để tự động deploy

Tạo .github/workflows/deploy.yml:

name: Deploy to Cloudflare Workers

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  verify:
    name: Lint & Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
        env:
          NEXT_PUBLIC_APP_URL: ${{ vars.NEXT_PUBLIC_APP_URL }}

  deploy:
    name: Deploy
    needs: verify
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Hai GitHub secret cần thêm tại Settings → Secrets and variables → Actions:

  • CLOUDFLARE_API_TOKEN: tạo tại Cloudflare Dashboard → My Profile → API Tokens → template “Edit Cloudflare Workers”
  • CLOUDFLARE_ACCOUNT_ID: hiển thị ở sidebar phải của Cloudflare Dashboard khi xem Workers & Pages overview

Với các secret runtime (database URL, API key), dùng Workers Secrets thay vì GitHub env vars — chúng được mã hóa khi lưu trữ và không xuất hiện trong build artifact:

wrangler secret put MY_SECRET

Giới hạn và điểm cần lưu ý

Tính năngTrạng tháiGhi chú
App RouterHỗ trợ đầy đủ
Pages RouterHỗ trợ đầy đủ
Server Components
SSG
SSR
ISRCần R2 bucket + r2IncrementalCache override
Server Actions
Middleware
Image OptimizationCần Cloudflare Images binding; không miễn phí
Partial Prerendering (PPR)
after()
'use cache'
Turbopack
Node.js in Middleware (Next.js 15.2+)Dự kiến trong bản release tương lai
export const runtime = "edge"Xóa khỏi tất cả file route trước khi deploy
Windows local devDùng WSL hoặc Linux CI

Một lưu ý về bundle size: Workers có giới hạn kích thước sau gzip là 3 MiB trên gói free, 10 MiB trên gói trả phí. Con số sau gzip mới tính — một bundle 8 MiB chưa nén thường vừa gói free sau khi nén.

Xóa export const runtime = "edge" khỏi tất cả file route. Đây là lỗi phổ biến nhất khi migrate và lỗi nó tạo ra không phải lúc nào cũng rõ ràng.

Bước tiếp theo

Khi ứng dụng đã chạy trên Workers, hệ sinh thái Cloudflare lấp đầy những phần Next.js cần từ một nền tảng:

  • Cloudflare D1 — SQLite tại edge. Gói free đủ cho hầu hết dự án cá nhân. Thêm D1 binding trong wrangler.jsonc và query với Drizzle hoặc SQL thô qua D1 API.
  • Cloudflare R2 — Object storage tương thích S3. Không phí egress. Dùng cho upload của người dùng, ISR cache (như đã trình bày), hoặc offload static asset.
  • Cloudflare KV — Key-value store độ trễ thấp. Phù hợp cho feature flag, session token, và bộ đếm rate-limit.
  • Neon — serverless Postgres. Khi bạn cần relational DB và giới hạn SQLite của D1 quá chặt, Neon là lựa chọn tự nhiên. Neon là đối tác chính thức của Cloudflare, có gói free, và hoạt động tốt với Drizzle ORM và Prisma.
  • Turso — nếu bạn muốn sự đơn giản của SQLite ở production scale, Turso thêm multi-region replication và branching trên nền libSQL. Nâng cấp tốt từ D1 khi dữ liệu vượt giới hạn free của D1.
  • Upstash — Redis và QStash cho rate-limiting, caching layer, hoặc hàng đợi background job. Phù hợp cho mọi thứ cần pub/sub hoặc deferred work trên Workers.

Nếu bạn đang so sánh Cloudflare Workers với Vercel cho dự án Next.js, Vercel vs Cloudflare Pages 2026 phân tích chi tiết các đánh đổi giữa hai nền tảng.


Nguồn: Cloudflare Workers Next.js guide · OpenNext Cloudflare docs · @opennextjs/cloudflare GitHub · Cloudflare blog: OpenNext partnership · Cloudflare compatibility flags