- Vrealmatic
- Next.js
- Cloudflare
NextJS on Cloudflare Wiki
Initialize, write and deploy NextJS applications optimized for running on Cloudflare.

- Next.js is an open-source web development framework created by the private company Vercel providing React-based web applications with server-side rendering and static rendering, see more at Next.JS page.
- Cloudflare is one of the biggest network service for the purposes of increasing the security and performance of web sites and services. It also provides a cloud service to host files and applications.
Initialize a NextJs Worker app for Cloudflare:
npm create cloudflare@latest -- my-next-app --framework=next --platform=workers- You're in an existing git repository. Do you want to use git for version control? - choose Yes
- Do you want to deploy your application? choose Yes or No based on your preference.
Run the app on localhost options:
npm run devnpx wrangler dev
Deploy / Redeploy (from Ubuntu WSL on Windows)
NOTE: If you need to deploy applications to more Cloudflare accounts, use Deploying with a token.
- Be sure you are authentizated / use proper token
npx wrangler whoami - Build the app for Cloudflare Worker
npx @opennextjs/cloudflare build npx wrangler deploy
Check real-time logs on deployed ver through wrangler
wrangler tailCreating D1 database and connecting it
Either throgh Cloudflare UI, or through wrangler in terminal - In project root folder terminal, run
npx wrangler d1 create my-next-app-dbD1 Typing
npx wrangler types --env-interface CloudflareEnvConnect DB
- In wrangler.jsonc, add
"d1_databases": [ { "binding": "my_db", "database_name": "my-next-app-db", "database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "remote": true } ] - Then in any script you need the db, or in a lib
import { getCloudflareContext } from "@opennextjs/cloudflare"; type Env = { my_db: D1Database; // binding name in wrangler.jsonc }; export function getD1() { const { env } = getCloudflareContext() as unknown as { env: Env }; const db = env.my_db; }
Integration R2 Storage Database to NextJS application
R2 Storage Database can be configured in CloudFlare UI: Storage & Databases / R2 Object Storage. Alternatively, you can create R2 storage database also through cli, see Create new buckets.
R2 Storage can be used for holding any type of files, from common web media up to cache jsons.
Config wrangler.jsonc
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "cache"
}
],Sample R2 Library src/lib/r2.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
type Env = { NEXT_INC_CACHE_R2_BUCKET: R2Bucket; };
const r2CacheKey : string = 'vrealmatic_page/v3/';
async function getBucket(): Promise<R2Bucket> {
const ctx = getCloudflareContext() as { env: Env };
const bucket = ctx.env.NEXT_INC_CACHE_R2_BUCKET;
if (!bucket) throw new Error("R2 binding NEXT_INC_CACHE_R2_BUCKET is missing in Cloudflare context env.");
return bucket;
}
export async function r2GetJson<T>(key: string): Promise<T | null> {
const bucket = await getBucket();
const obj = await bucket.get(r2CacheKey+key);
if (!obj) return null;
return await obj.json<T>();
}
export async function r2PutJson(key: string, value: unknown): Promise<void> {
const bucket = await getBucket();
await bucket.put((r2CacheKey+key), JSON.stringify(value), { httpMetadata: { contentType: "application/json" } });
}Use Sample for saving cache data to R2
type R2Cached<T> = {
ts: number; // stored at (ms)
data: T;
};
const PAGE_CTX_TTL_MS = 86_400_000; // 24h
const inFlight = new Map<string, Promise<PageContext>>();
async function getRequestedAndAlternates_TTL(urlPath: string): Promise<PageContext> {
const existing = inFlight.get(urlPath);
if (existing) return existing;
const p = (async () => {
try {
const now = Date.now();
// 1) try cache
const cached = await r2GetJson<R2Cached<PageContext>>(urlPath);
if ( cached && (now - cached.ts) < PAGE_CTX_TTL_MS ) return cached.data;
// 2) MISS/STALE => DB
const data: PageContext = await GetPageByUrlAndAlternates(urlPath);
// 3) Write / update to cache, if the page exists
if(data.page) await r2PutJson(urlPath, { ts: now, data } satisfies R2Cached<PageContext>);
return data;
} finally {
inFlight.delete(urlPath);
}
})();
inFlight.set(urlPath, p);
return p;
}
export function getPageContext(slug?: string[]) {
const baseUrlPath = (!slug || slug.length === 0) ? "" : slug.join("/");
return getPageContextCache(baseUrlPath);
}
export const getPageContextCache = cache(getRequestedAndAlternates_TTL);R2 Bucket can be private as well as public. Public bucket has its own domain / subdomain you can get the files through. Files at private bucket can be attached through worker app that includes binding at the bucket.
Incremental cache for NextJS on Cloudflare
- At
wrangler.jsonc, add bindings to R2 bucket and Durable Object + migration, such as:"r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", "bucket_name": "inc-cache" }, //... ],"durable_objects": { "bindings": [ { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler" } ] },"migrations": [ { "tag": "v1", "new_sqlite_classes": ["DOQueueHandler"] }, ], - At
open-next.config.ts, attach incremenatl cache configuration, such as:import { defineCloudflareConfig } from "@opennextjs/cloudflare"; import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache"; import { withRegionalCache } from "@opennextjs/cloudflare/overrides/incremental-cache/regional-cache"; import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue"; export default defineCloudflareConfig({ // See https://opennext.js.org/cloudflare/caching for more details incrementalCache: withRegionalCache(r2IncrementalCache, { mode: "long-lived" }), queue: doQueue, }); - Expand your
worker-entry.mjsfor DOimport openNextWorker from "./.open-next/worker.js"; export { DOQueueHandler } from "./.open-next/worker.js"; export default { async fetch(request, env, ctx) { const url = new URL(request.url); if (url.pathname === "/en" || url.pathname === "/en/") { url.pathname = "/"; return Response.redirect(url.href, 308); } if (url.hostname === "xxx.com") { url.hostname = "www.xxx.com"; url.protocol = "https:"; return Response.redirect(url.href, 308); } if (url.hostname.endsWith(".workers.dev")) { url.hostname = "www.xxx.com"; url.protocol = "https:"; return Response.redirect(url.href, 308); } return withEdgeCache(request, ctx, (req) => openNextWorker.fetch(req, env, ctx)); }, }; - Build & Deploy
This mechanism uses default doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue". DO is created automatically. There is nothing next to do above the steps described above.
This mechanism eliminates observability errors of type "Failed to revalidate stale page ... FatalError: Dummy queue is not implemented".


