◉CLAUDE LABJP
●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 38 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 38 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-10-22Advanced

I Recounted the Bytes: Why Keeping Article Bodies Out of My Worker Bundle Still Holds Up at About 900 Articles

On my own Next.js site running on Cloudflare Workers, I recounted the bytes of a bundle that carries article bodies versus one with metadata only. Here are the headroom, a breakdown I had not expected, and a script that keeps counting.

Cloudflare Workers17Next.js10Bundle SizeMDX3Measurement

✦ Premium Article

One morning I tried to say, out loud, how many bytes src/generated/articles.json is on my own site, and I could not.

I'm an indie developer, and I run a site built on Next.js and deployed to Cloudflare Workers. Articles are written in MDX, converted to HTML before the build, and served as static assets. Only metadata such as the title and description goes into the JSON. I had a feeling that this split was the safe one, but I could not back that feeling with a number.

This post is the recount, done on the files in my repository. The short version: with bodies inside the JSON it would be about 28.5 MiB, and with metadata only it is about 0.93 MiB. What surprised me, though, was not the headroom but what the metadata itself is made of.

Checking the limit first

Cloudflare's documentation says a Worker may be up to 64 MiB uncompressed. There is no compressed-size limit, and the gzip figure Wrangler prints is for reference only. For static assets, a single file may be up to 25 MiB, and a version may hold 20,000 files on the free plan or 100,000 on the paid plan.

You will still find older advice quoting "3 MB / 10 MB compressed." That was the number in my head too, until I opened the current page. Limits change, so I check the value before I start counting.

The count: bundle the same articles two ways

The question is simple: how many bytes if bodies are in the JSON, and how many if they are not? You can approximate it without running the build, just by reading front matter and body from the MDX files.

#!/usr/bin/env python3
"""Count the headroom when article bodies are inside JSON versus metadata only.
Usage: python3 bundle_guard.py <repo> [new articles per month]"""
import json, os, re, sys
 
LIMIT = 64 * 1024 * 1024        # Workers uncompressed bundle limit (per the docs)
META_ALERT = 2 * 1024 * 1024    # my own line: revisit the design above this
 
def read_articles(root, locale):
    base = os.path.join(root, "content", "articles", locale)
    for cat in sorted(os.listdir(base)):
        d = os.path.join(base, cat)
        if not os.path.isdir(d):
            continue
        for name in sorted(os.listdir(d)):
            if not name.endswith(".mdx"):
                continue
            text = open(os.path.join(d, name), encoding="utf-8").read()
            m = re.match(r"^---\n(.*?)\n---\n(.*)$", text, re.S)
            if not m:                      # report broken front matter instead of silently skipping
                print("skip:", locale, cat, name, file=sys.stderr)
                continue
            yield cat, name[:-4], m.group(1), m.group(2)
 
def field(fm, key):
    m = re.search(rf'^{key}:\s*"?(.*?)"?\s*$', fm, re.M)
    return m.group(1) if m else ""
 
def main(root, per_month=40):
    meta_total = full_total = pairs = 0
    for loc in ("ja", "en"):
        meta, full = [], []
        for cat, slug, fm, body in read_articles(root, loc):
            row = {k: field(fm, k) for k in ("title", "level", "date", "description")}
            row.update(slug=slug, category=cat)
            meta.append(row)
            full.append({**row, "content": body})
        sep = (",", ":")
        meta_total += len(json.dumps(meta, separators=sep, ensure_ascii=False).encode())
        full_total += len(json.dumps(full, separators=sep, ensure_ascii=False).encode())
        pairs = max(pairs, len(meta))
    per_pair_full, per_pair_meta = full_total / pairs, meta_total / pairs
    print(f"articles per language: {pairs}")
    print(f"metadata only: {meta_total/1024/1024:.2f} MiB / with bodies: {full_total/1024/1024:.2f} MiB")
    print(f"headroom with bodies: ~{(LIMIT-full_total)/per_pair_full:.0f} pairs = {(LIMIT-full_total)/per_pair_full/per_month:.1f} months")
    print(f"headroom, metadata only: ~{(LIMIT-meta_total)/per_pair_meta:.0f} pairs")
    return 1 if meta_total > META_ALERT else 0
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1], float(sys.argv[2]) if len(sys.argv) > 2 else 40))

Run against my repository (917 articles per language, Japanese and English), the output looked like this. The labels are English here, but the numbers are what the script printed.

articles per language: 917
metadata only: 0.93 MiB / with bodies: 28.54 MiB
headroom with bodies: ~1140 pairs = 28.5 months
headroom, metadata only: ~62334 pairs

A "pair" is one Japanese article plus its English twin. At my recent pace of roughly 40 articles a month, even the body-in-JSON layout would last over two years on paper.

✦

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
✦You can estimate, from files already on your disk, how fast a JSON that holds your content grows toward the limit in your own Next.js site
✦You can catch the size-related failure where the build passes and only the deploy fails, months before you reach the limit
✦You can count which fields make your metadata JSON heavy and decide what to trim first, with numbers behind the order
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-10-20
Counting the 267 Broken Links My Build Was Silently Stripping — and Why I Skipped Fuzzy Auto-Repair
An MDX build was quietly removing broken internal links. Here is the audit script that lists them, why I decided against slug-similarity repair, and how to set a baseline so the count never grows.
⟐ Claude Code2026-08-19
Fixing the Code Doesn't Evict a Broken Page From the Edge Cache
I shipped a fix and the broken page kept serving. The problem was not the code but the edge cache, which had stored a broken HTML response because the store decision looked only at the status code. Here is the integrity guard I now run before every cache write, and the thresholds I had to tune in production.
⟐ Claude Code2026-05-03
Claude × Stripe Meter Events — Building a Usage-Based SaaS
A production-ready architecture and full implementation for billing Claude API token consumption through Stripe Meter Events — covering idempotency, usage caps, and a hybrid base + overage pricing model.