CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-04-21Advanced

Running Next.js on Cloudflare Workers in Production with Claude Code — Every Build Crisis, Cache Bug, and Automation Pattern We Solved

Running Next.js on Cloudflare Workers is not the same as Vercel. The 62 MiB bundle limit, ASSETS binding quirks, and edge cache personalization conflicts are real production hazards. Here's how Claude Code helped us solve each one.

Claude Code196Next.js7Cloudflare Workers14production111troubleshooting87performance optimization

Premium Article

"The deploy is failing. I have no idea why."

If you've tried running a Next.js application on Cloudflare Workers in production, you've probably been there. The platform's unique runtime constraints — no fs, a 62 MiB bundle ceiling, no self-referencing fetch() calls — diverge sharply from what the broader Next.js ecosystem assumes.

I've been running this site (Claude Lab) on Cloudflare Workers for several months now. What follows is an honest account of every production crisis we hit, how Claude Code helped work through them, and the specific patterns we arrived at after the debugging was done. The goal is to spare you from repeating the same mistakes.


What Makes Cloudflare Workers Different for Next.js

Deploying to Vercel or Netlify and deploying to Cloudflare Workers are fundamentally different operations. Cloudflare Workers run inside V8 isolates — not Node.js. That means:

  • No Node.js APIs: fs, path, child_process, and similar modules are unavailable or shimmed
  • 62 MiB bundle size hard limit: Your compiled worker, including static assets traversal, cannot exceed this
  • No self-referencing fetch: You cannot call fetch('https://your-own-domain.com/...') from within a Worker — it will timeout
  • Cold starts are fast, but execution budget is limited

OpenNext (@opennextjs/cloudflare) bridges the gap between Next.js expectations and Workers reality. But "it works" and "it works reliably at scale" have meaningful distance between them. Claude Code helped us close that gap, not by generating boilerplate, but by reasoning about platform-specific constraints.


Crisis 1: The 62 MiB Bundle Size Limit Killed Every Deploy

This was our first wall, and the most disruptive.

One day, wrangler deploy started failing with this:

Error: Script startup exceeded CPU time limit.
Worker exceeded memory limit after assets traversal.
⛔ Total asset size exceeds the 62 MiB limit.

The cause became clear after checking file sizes: our src/generated/articles.json had grown to 58 MB because it contained the full HTML content of every article.

Debugging with Claude Code

We described the problem to Claude Code, and the first thing it asked was to check the file size:

# First command Claude Code ran
ls -lh src/generated/articles.json
# -rw-r--r-- 1 user staff 58M Apr 10 src/generated/articles.json

Then it proposed a solution we hadn't considered: separate the concerns entirely.

"Keep only metadata in articles.json. Write each article's HTML to individual files under public/content/articles/{locale}/{category}/{slug}.html. Then read those files at request time via the Workers ASSETS binding. This keeps the bundle lean while making full article content available."

This is what we now call Content Split Architecture.

Implementation: Refactoring generate-content.mjs

// scripts/generate-content.mjs — Content Split Architecture
 
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
import fs from 'fs/promises'
import path from 'path'
import matter from 'gray-matter'
 
const CONTENT_OUTPUT_DIR = 'public/content/articles'
 
async function processArticle(locale, category, slug, mdxSource) {
  const { data: frontmatter, content } = matter(mdxSource)
 
  // Convert MDX content to HTML
  const vfile = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypeStringify)
    .process(content)
 
  const html = String(vfile)
 
  // Write HTML to individual file
  const outputDir = path.join(CONTENT_OUTPUT_DIR, locale, category)
  await fs.mkdir(outputDir, { recursive: true })
  await fs.writeFile(
    path.join(outputDir, `${slug}.html`),
    html,
    'utf-8'
  )
 
  // Return only metadata for articles.json
  return {
    slug,
    category,
    title: frontmatter.title,
    description: frontmatter.description,
    date: frontmatter.date,
    level: frontmatter.level,
    premium: frontmatter.premium ?? false,
    tags: frontmatter.tags ?? [],
    highlights: frontmatter.highlights ?? [],
    author: frontmatter.author,
  }
}

After this change, articles.json went from 58 MB to 1.2 MB.

Reading HTML at Request Time via ASSETS Binding

Since fs.readFile doesn't work in Workers, we read article HTML through the ASSETS binding:

// src/lib/content.ts
 
import { getCloudflareContext } from '@opennextjs/cloudflare'
 
// In-memory cache for the Worker instance lifetime
const contentCache = new Map<string, string>()
 
export async function getArticleContent(
  locale: string,
  category: string,
  slug: string
): Promise<string | null> {
  const cacheKey = `${locale}/${category}/${slug}`
 
  if (contentCache.has(cacheKey)) {
    return contentCache.get(cacheKey)\!
  }
 
  try {
    const { env } = await getCloudflareContext()
 
    if (\!env?.ASSETS) {
      // Local dev may not have ASSETS binding configured
      console.warn('[getArticleContent] ASSETS not available')
      return null
    }
 
    // ⚠️ DO NOT use fetch('https://your-domain.com/...')
    // Self-referencing fetches timeout in Workers.
    // Use ASSETS.fetch() with a dummy hostname instead.
    const response = await env.ASSETS.fetch(
      new Request(`https://dummy.internal/content/articles/${cacheKey}.html`)
    )
 
    if (\!response.ok) return null
 
    const html = await response.text()
    contentCache.set(cacheKey, html)
    return html
  } catch (error) {
    console.error('[getArticleContent]', error)
    return null
  }
}

The self-fetch trap (worth repeating): Never call fetch('https://claudelab.net/content/...') from within a Worker. It routes through the real network, hits Cloudflare's edge, and either loops back or times out. The ASSETS binding is the correct path.


Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A step-by-step Content Split implementation that keeps articles.json metadata-only and the Worker bundle under the 62 MiB limit
The cache-worker.js design that bypasses the edge cache for premium_token / article_purchases cookies, plus DEPLOY_VERSION invalidation
An in-memory ASSETS-binding cache for article HTML, with measured before/after latency inside getArticleContent
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Claude Code2026-06-17
It Says '500' in the Browser but curl Returns 200 — When a Next.js Error Boundary Misreads a ChunkLoadError
A production Next.js article page shows '500 Internal Server Error' in the browser, yet curl returns 200. The culprit was not the server but a ChunkLoadError surfacing through error.tsx. Here is the diagnosis and a fix with built-in auto-reload.
Claude Code2026-05-28
The Morning I Hit Cloudflare Workers' 62 MiB Limit, and the Content Split Architecture I Rebuilt for 5,000 Articles
One morning, an indie developer running 4 AI tech blogs on Cloudflare Workers + Next.js woke up to a deployment that had stopped because articles.json grew large enough to push the Worker bundle past the 62 MiB limit. This is the concrete implementation walkthrough of splitting metadata from HTML, with the build-time and bundle-size numbers observed across the first month.
Claude Code2026-04-06
Claude Code × Next.js 15 App Router Production: RSC, Server Actions, Auth, Testing & Deployment
The practical guide to production Next.js 15 App Router development with Claude Code. Covers RSC architecture decisions, Server Actions patterns, Auth.js v5, Vitest testing, and Cloudflare Workers deployment with practical code examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →