How to Debug Next.js Applications Running on Cloudflare Workers: Local and Production Techniques
Debug Next.js applications running on Cloudflare Workers by using wrangler dev for local simulation with source maps and console.log statements, or wrangler tail for streaming production logs from the edge.
The ifindev/fullstack-next-cloudflare repository demonstrates a modern full-stack architecture combining Next.js 15 (App Router) with Cloudflare Workers via the @opennextjs/cloudflare integration. Because server-side code executes inside the Worker runtime rather than a local Node.js process, traditional debugging methods like next dev inspection are insufficient for API routes, Server Actions, and database interactions.
Understanding the Debug Architecture
When you run the build process, @opennextjs/cloudflare generates a Worker bundle at .open-next/worker.js and defines asset bindings in wrangler.jsonc. Next.js server-side code—including API routes, Server Actions, and middleware—runs inside the Worker runtime, while React components render on the edge.
The repository provides three distinct development entry points for debugging:
npm run dev(next dev): Starts a standard Next.js dev server locally. Use this only for pure frontend UI work that does not interact with Worker-bound APIs.npm run dev:cf(npx @opennextjs/cloudflare build && wrangler dev): Builds the Cloudflare-compatible bundle and launches Wrangler dev, which runs the Worker locally with full access to D1, R2, and AI bindings.npm run dev:remote(npx @opennextjs/cloudflare build && wrangler dev --remote): Deploys to a temporary remote preview and streams logs. Use this for debugging environment-specific behavior or production-only bindings.
Local Debugging with Wrangler Dev
Local debugging requires the dev:cf script, which respects the bindings defined in wrangler.jsonc (D1 databases, R2 buckets, AI, etc.) and generates source maps that map stack traces back to original TypeScript files.
Using console.log and Source Maps
The Worker runtime forwards console.log, console.error, and console.debug calls to your terminal. When @opennextjs/cloudflare builds the project, it generates .open-next/worker.js.map, enabling Wrangler to display original file paths in stack traces.
In src/modules/auth/auth.route.ts, add logging to trace request flow:
// src/modules/auth/auth.route.ts
export const authRoutes = createRoute({
path: "/login",
method: "GET",
handler: async (c) => {
console.log("Login request received", c.req.url);
// Stack traces will point to this exact line number
},
});
When wrangler dev executes this route, the terminal displays the original TypeScript location:
[2024-03-04 12:34:56] LOG src/modules/auth/auth.route.ts:8:13 Login request received
Setting Breakpoints with debugger Statements
Insert debugger; statements in server-side files to pause execution. Attach the Node inspector by running Wrangler with the inspect flag:
node --inspect-brk $(which wrangler) dev
Then open Chrome at chrome://inspect and attach to the Worker process. Breakpoints will resolve to the original TypeScript source thanks to the source map.
Example breakpoint in R2 upload logic:
// src/lib/r2.ts
export async function uploadFile(key: string, body: Uint8Array) {
console.log("Uploading to R2:", key);
debugger; // Execution pauses here when inspector is attached
await env.next_cf_app_bucket.put(key, body);
}
Debugging Production Deployments
For issues that only appear in the Cloudflare edge environment, use remote debugging and log streaming capabilities.
Streaming Live Logs with wrangler tail
The wrangler tail command streams real-time logs from deployed Workers. Use JSON formatting for structured analysis:
# Stream all logs with full detail
wrangler tail --format=json --level=debug
# Filter for errors only
wrangler tail --level=error
JSON output includes source file references via source maps:
{
"timestamp": "2024-03-04T12:35:00.123Z",
"level": "error",
"msg": "src/modules/auth/auth.route.ts:12:9 Unhandled exception: ...",
"source": "worker"
}
Run npm run dev:remote to deploy a temporary preview and attach Chrome DevTools via the WebSocket URL printed by Wrangler (e.g., ws://127.0.0.1:9229/...). This allows setting breakpoints in original TS files while code executes on Cloudflare's edge.
Common Debugging Scenarios
Apply these patterns to diagnose specific issues in the Next.js Cloudflare stack.
Debugging Server Actions
Server Actions failing silently require explicit error handling and logging. Wrap action bodies in try/catch blocks and run dev:cf to capture stack traces:
// src/modules/todos/actions/create-todo.action.ts
export async function createTodo(data: TodoCreate) {
try {
console.log("Creating todo:", data);
// D1 insertion logic...
} catch (e) {
console.error("Server Action failed:", e);
throw e; // Re-throw to maintain error boundaries
}
}
The source map ensures the stack trace references the original create-todo.action.ts file, not the compiled Worker bundle.
Inspecting D1 Database Queries
Log query results in src/db/index.ts or specific action files to verify database interactions:
// src/modules/todos/actions/get-todos.action.ts
export async function getAllTodos(): Promise<Todo[]> {
const result = await env.next_cf_app.prepare("SELECT * FROM todos").all<Todo>();
console.log("D1 rows returned:", result.results?.length);
console.timeEnd('fetch-todos'); // Performance timing
return result.results ?? [];
}
Use console.time() and console.timeEnd() to profile expensive queries during local development.
Troubleshooting R2 Uploads
Verify bucket bindings and upload responses in src/lib/r2.ts:
// src/lib/r2.ts
export async function uploadFile(key: string, body: Uint8Array) {
console.log("R2 bucket:", env.next_cf_app_bucket.name);
const response = await env.next_cf_app_bucket.put(key, body);
console.log("R2 upload status:", response.httpStatus);
}
Worker logs appear in the terminal during wrangler dev; use wrangler tail for remote debugging in production.
Enabling Verbose AI Logging
For the Cloudflare Workers AI binding (AI), add verbose logging via environment variables in wrangler.jsonc:
{
"vars": {
"DEBUG": "*"
}
}
This surfaces AI inference errors and debug information in the Wrangler console, as implemented in src/services/summarizer.service.ts.
Configuration for Enhanced Debugging
Modify wrangler.jsonc to optimize the debugging experience. The ASSETS binding serves static files from .open-next, while vars injects environment variables into the Worker context:
{
"name": "next-cf-app",
"main": ".open-next/worker.js",
"compatibility_date": "2024-09-23",
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"vars": {
"DEBUG": "*",
"NODE_ENV": "development"
}
}
Ensure next.config.ts loads Cloudflare dev helpers when NODE_ENV=development to maintain compatibility with the local Worker runtime.
Summary
- Use
npm run dev:cffor full-stack debugging with local Worker simulation, D1, R2, and AI bindings. - Leverage source maps generated by
@opennextjs/cloudflarein.open-next/worker.js.mapto see original TypeScript file paths in stack traces. - Insert
debugger;statements and attach the Node inspector withnode --inspect-brkfor breakpoint debugging. - Stream production logs via
wrangler tail --format=jsonto diagnose edge-specific issues after deployment. - Configure
wrangler.jsoncvars likeDEBUG: "*"to enable verbose logging for AI and other bindings.
Frequently Asked Questions
Why doesn't console.log appear when I run next dev?
The standard next dev command runs a local Node.js server, not the Cloudflare Worker runtime. Server Actions, API routes, and middleware execute in the Node environment, so bindings like D1 and R2 are unavailable, and logs from Worker-specific code won't appear. Use npm run dev:cf instead to run wrangler dev, which executes your Next.js server code inside the actual Worker runtime and streams console.log output to your terminal.
How do I debug Server Actions that fail silently on Cloudflare?
Wrap the Server Action body in a try/catch block and explicitly log the error before re-throwing. Run the action via npm run dev:cf and watch the Wrangler console. Because @opennextjs/cloudflare generates source maps, the stack trace will point to the original TypeScript file (e.g., src/modules/todos/actions/create-todo.action.ts:15) rather than the minified Worker bundle, allowing you to identify the exact line causing the failure.
Can I use Chrome DevTools to debug a deployed Worker?
Yes. Run npm run dev:remote to deploy a temporary preview, then use the WebSocket URL printed by Wrangler (typically ws://127.0.0.1:9229/...) to attach Chrome DevTools. You can set breakpoints in your original TypeScript source files while the code executes on Cloudflare's edge infrastructure, enabling debugging of production-specific behavior like real D1 data or R2 buckets.
How do I view logs from a production deployment?
Use the wrangler tail command to stream real-time logs from your live Worker. Add the --format=json flag for structured output and --level=debug for verbose detail. The source maps generated during the build process ensure that production error logs reference original file paths like src/modules/auth/auth.route.ts, making it possible to correlate edge errors with your local source code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →