Skip to content
GitHubXDiscordRSS

Next.js

This guide demonstrates how to deploy a Next.js application to Cloudflare using Alchemy with full-stack capabilities including server actions and API routes.

Start by creating a new Next.js project using Alchemy:

Terminal window
bunx alchemy create my-nextjs-app --template=nextjs
cd my-nextjs-app

Before you can deploy, authenticate with your Cloudflare account:

Terminal window
bun alchemy login

Run the deploy script to build and deploy your Next.js application:

Terminal window
bun run deploy

You’ll get the live URL of your Next.js site:

Terminal window
{
url: "https://website.<your-account>.workers.dev"
}

Work locally using the dev script:

Terminal window
bun run dev

Clean up all Cloudflare resources created by this stack:

Terminal window
bun run destroy

Alchemy requires a locally set password to encrypt Secrets that are stored in state. Be sure to change this.

ALCHEMY_PASSWORD=change-me

The infrastructure setup with KV storage for demonstration:

/// <reference types="@types/node" />
import alchemy from "alchemy";
import { KVNamespace, Nextjs } from "alchemy/cloudflare";
const app = await alchemy("my-nextjs-app");
export const kv = await KVNamespace("kv");
export const website = await Nextjs("website", {
adopt: true,
bindings: { KV: kv },
});
console.log({
url: website.url,
});
await app.finalize();

Type-safe access to Cloudflare bindings:

// Auto-generated Cloudflare binding types.
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
import type { website } from "../alchemy.run.ts";
export type CloudflareEnv = typeof website.Env;
declare global {
type Env = CloudflareEnv;
}
declare module "cloudflare:workers" {
namespace Cloudflare {
export interface Env extends CloudflareEnv {}
}
}

The CLI updated the tsconfig.json to include alchemy.run.ts and register @cloudflare/workers-types + types/env.d.ts globally:

{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
},
"types": ["@cloudflare/workers-types", "./types/env.d.ts"]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"alchemy.run.ts"
],
"exclude": ["node_modules"]
}

Configure Next.js for Cloudflare Workers with OpenNext:

import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
typescript: {
ignoreBuildErrors: true,
},
};
export default nextConfig;
initOpenNextCloudflareForDev();

Configure OpenNext for Cloudflare Workers deployment:

import { defineCloudflareConfig } from "@opennextjs/cloudflare";
export default defineCloudflareConfig({
// Uncomment to enable R2 cache,
// It should be imported as:
// `import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";`
// See https://opennext.js.org/cloudflare/caching for more details
// incrementalCache: r2IncrementalCache,
});

The template includes examples of accessing Cloudflare bindings in both API routes and Server Components.

app/api/kv/route.ts
import { getCloudflareContext } from "@opennextjs/cloudflare";
export const GET = async () => {
const { env } = getCloudflareContext();
const values = await env.KV.list();
return Response.json(values);
};
app/page.tsx
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { revalidatePath } from "next/cache";
export default async function Home() {
const { env } = await getCloudflareContext({ async: true });
const values = await env.KV.list();
return (
<div>
<h1>KV Values</h1>
<pre>{JSON.stringify(values, null, 2)}</pre>
<button onClick={putValue}>Put Value</button>
<button onClick={deleteValue}>Delete Value</button>
</div>
);
}
const putValue = async () => {
"use server";
const { env } = await getCloudflareContext({ async: true });
await env.KV.put(crypto.randomUUID(), "test");
revalidatePath("/");
};
const deleteValue = async () => {
"use server";
const { env } = await getCloudflareContext({ async: true });
const values = await env.KV.list();
await Promise.all(values.keys.map((key) => env.KV.delete(key.name)));
revalidatePath("/");
};

Next.js ISR is supported through R2 caching. Uncomment the cache configuration in open-next.config.ts to enable:

import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
export default defineCloudflareConfig({
incrementalCache: r2IncrementalCache,
});

Next.js Image optimization works automatically with Cloudflare’s image transformation service.

Edge middleware is fully supported and runs on Cloudflare’s edge network for optimal performance.

  • The build process uses OpenNext to transform your Next.js app for Cloudflare Workers
  • All Next.js features including App Router, Server Components, and Server Actions are supported
  • Static assets are automatically uploaded to Cloudflare R2 and served via CDN
  • The wrangler.jsonc file is auto-generated and should be added to .gitignore

If you see warnings about wrangler.jsonc not being ignored, add it to your .gitignore file to prevent it from being committed to your repository.