nextjs - 💡(How to fix) Fix next/image: a single 0-byte file in .next/cache/images/ permanently poisons the disk-LRU singleton

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Error: LRUCache: calculateSize returned 0, but size must be > 0. Items with size 0 would never be evicted, causing unbounded cache growth. at ignore-listed frames ⨯ unhandledRejection: Error: LRUCache: calculateSize returned 0 ... ⨯ Failed to write image to cache <some-key>

Root Cause

  1. initCacheEntries in next/dist/server/image-optimizer.js:179 pushes { key, size: 0 } for it.
  2. getOrInitDiskLRU in next/dist/server/lib/disk-lru-cache.external.js:43-47 replays the entry with lru.set(key, 0).
  3. LRUCache.set in next/dist/server/lib/lru-cache.js:91-99 throws because size <= 0.
  4. The throw escapes the async IIFE, leaving the module-scoped _diskLRUPromise rejected. Every future await getOrInitDiskLRU(...) re-throws — the cache is poisoned for the rest of the process.
  5. The cache key shown in Failed to write image to cache <key> is the current write's key, not the corrupt entry's. This makes the bug hard to diagnose — the corrupt key never appears in any log line.

Fix Action

Fix / Workaround

Workaround for affected users

Code Example

npx create-next-app@canary lru-repro --typescript --app --turbopack --use-npm
   cd lru-repro

---

# next dev (this issue):
   mkdir -p .next/dev/cache/images/anyKey
   : > .next/dev/cache/images/anyKey/14400.1.a.b.avif

   # next start / production builds use a different path:
   #   .next/cache/images/anyKey/14400.1.a.b.avif

---

# Add a tiny PNG to public/ so we have a raster source:
   node -e "require('fs').writeFileSync('public/pixel.png', Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=','base64'))"

   npm run dev &
   sleep 8
   curl -sS -o /dev/null "http://localhost:3000/_next/image?url=%2Fpixel.png&w=256&q=75"

---

Error: LRUCache: calculateSize returned 0, but size must be > 0.
Items with size 0 would never be evicted, causing unbounded cache growth.
    at ignore-listed frames
⨯ unhandledRejection: Error: LRUCache: calculateSize returned 0 ...
Failed to write image to cache <some-key>

---

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Pro
  Available memory (MB): 16160
  Available CPU cores: 12
Binaries:
  Node: 24.13.1
  npm: 11.10.1
  Yarn: 4.12.0
  pnpm: N/A
Relevant Packages:
  next: 16.2.4
  eslint-config-next: N/A
  react: 19.2.5
  react-dom: 19.2.5
  typescript: 6.0.3
Next.js Config:
  output: N/A

---

// image-optimizer.js — initCacheEntries
   const { expireAt, buffer } = await readFromCacheDir(cacheDir, cacheKey);
   if (buffer.byteLength === 0) continue;
   entries.push({ key: cacheKey, size: buffer.byteLength, expireAt });

---

# Stop the dev server, then:
find .next/cache/images -size 0 -delete
# Restart dev server.
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/MohamedEslam04/next-image-lru-repro

To Reproduce

  1. Fresh Next.js app with App Router + Turbopack:

    npx create-next-app@canary lru-repro --typescript --app --turbopack --use-npm
    cd lru-repro
  2. Plant a 0-byte file in the image cache. This is exactly what next dev leaves behind when killed mid-write on Windows (Ctrl-C, terminal restart, OOM, antivirus interrupt) — fs.promises.writeFile creates the inode before flushing bytes.

    # next dev (this issue):
    mkdir -p .next/dev/cache/images/anyKey
    : > .next/dev/cache/images/anyKey/14400.1.a.b.avif
    
    # next start / production builds use a different path:
    #   .next/cache/images/anyKey/14400.1.a.b.avif
  3. Start dev and trigger image optimization (use a raster source — SVG needs dangerouslyAllowSVG). The disk-LRU singleton initializes lazily on the first image request, so the 0-byte file must be present before this step. If it isn't, restart next dev after planting and re-curl.

    # Add a tiny PNG to public/ so we have a raster source:
    node -e "require('fs').writeFileSync('public/pixel.png', Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=','base64'))"
    
    npm run dev &
    sleep 8
    curl -sS -o /dev/null "http://localhost:3000/_next/image?url=%2Fpixel.png&w=256&q=75"

Observe the dev server stdout:

Error: LRUCache: calculateSize returned 0, but size must be > 0.
Items with size 0 would never be evicted, causing unbounded cache growth.
    at ignore-listed frames
⨯ unhandledRejection: Error: LRUCache: calculateSize returned 0 ...
⨯ Failed to write image to cache <some-key>

The error fires on every subsequent image-optimization request, indefinitely. Pages still render, but the disk LRU is permanently broken for the lifetime of the process.

Current vs. Expected behavior

Current: A single 0-byte file under .next/cache/images/ causes:

  1. initCacheEntries in next/dist/server/image-optimizer.js:179 pushes { key, size: 0 } for it.
  2. getOrInitDiskLRU in next/dist/server/lib/disk-lru-cache.external.js:43-47 replays the entry with lru.set(key, 0).
  3. LRUCache.set in next/dist/server/lib/lru-cache.js:91-99 throws because size <= 0.
  4. The throw escapes the async IIFE, leaving the module-scoped _diskLRUPromise rejected. Every future await getOrInitDiskLRU(...) re-throws — the cache is poisoned for the rest of the process.
  5. The cache key shown in Failed to write image to cache <key> is the current write's key, not the corrupt entry's. This makes the bug hard to diagnose — the corrupt key never appears in any log line.

Expected: A corrupt/empty file in the cache directory should be silently ignored during init (or atomic writes should prevent it from ever existing). One bad entry should never permanently disable image optimization.

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Pro
  Available memory (MB): 16160
  Available CPU cores: 12
Binaries:
  Node: 24.13.1
  npm: 11.10.1
  Yarn: 4.12.0
  pnpm: N/A
Relevant Packages:
  next: 16.2.4
  eslint-config-next: N/A
  react: 19.2.5
  react-dom: 19.2.5
  typescript: 6.0.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Image (next/image)

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

Where the bug lives

FileLinesRole
next/dist/server/image-optimizer.js179-196initCacheEntries reads every file's byte length and pushes it as entry.size — including 0-byte files.
next/dist/server/lib/disk-lru-cache.external.js30-52getOrInitDiskLRU replays all entries into the LRU. Module-scoped _diskLRUPromise caches the rejection.
next/dist/server/lib/lru-cache.js91-99LRUCache.set throws unconditionally for size <= 0.

Suggested fixes (any one resolves it)

  1. Skip 0-byte entries during init (smallest change):

    // image-optimizer.js — initCacheEntries
    const { expireAt, buffer } = await readFromCacheDir(cacheDir, cacheKey);
    if (buffer.byteLength === 0) continue;
    entries.push({ key: cacheKey, size: buffer.byteLength, expireAt });
  2. Atomic writes in writeToCacheDir — write to <filename>.tmp, fsync, then rename. Prevents 0-byte files from ever existing on disk.

  3. Don't poison the singleton in getOrInitDiskLRU — if lru.set throws for any one entry during replay, log + continue rather than reject the whole init promise.

  4. Graceful skip in LRUCache.set — for size <= 0, warn (not throw) and return false (matching the existing behavior for size > maxSize at line 100-103).

Workaround for affected users

# Stop the dev server, then:
find .next/cache/images -size 0 -delete
# Restart dev server.

Provenance

The disk image cache was introduced in vercel/next.js@39eb8e0 (feat(next/image): add lru disk cache and images.maximumDiskCacheSize). All affected code paths above were added in that commit.

How I hit this

[email protected] + bun run dev on Windows 11. A previous next dev was killed by Ctrl-C mid-image-optimization, leaving a 0-byte AVIF under .next/dev/cache/images/<key>/. From the next next dev onwards, every image request logged the LRU error. Setting images.unoptimized: true silenced it; downgrading to [email protected] (pre-disk-cache) avoided it entirely. After removing the 0-byte file, the error stopped immediately.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING