FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

next/image: aborting a cold transform permanently hangs that URL for every client (self-hosted next start) · Issue #97489 · vercel/next.js · GitHub

/ next.js Public

next/image: aborting a cold transform permanently hangs that URL for every client (self-hosted next start) #97489

Description

Link to the code that reproduces this issue

https://github.com/Neeptossss/next-image-abort-hang

To Reproduce

git clone https://github.com/Neeptossss/next-image-abort-hang
cd next-image-abort-hang
npm install
./repro.sh

repro.sh generates its own source image, builds the app, and starts and stops next start itself. It needs nothing but Node, npm and curl.

Each phase sweeps 84 cold cache keys (14 widths x 6 qualities), aborting each transform mid-flight with curl -m 0.02 and then requesting the very same URL as a fresh client with a 6 s timeout. A sweep is necessary because the trigger is a race. the disconnect has to land while the server is still streaming the source file so a single attempt rarely lands. Widths are never reused, since an already-successful transform is served from the disk cache and does not re-enter the code path under test.

Output on next@16.3.1:

=== 1. stock next: sweep 84 cold /public transforms, aborting each mid-flight ===
   => 6 of 84 URLs are now permanently unresponsive: 32:50 1080:50 48:75 1920:75 828:80 384:90

=== 2. control: external images (remotePatterns) get the same treatment ===
   => 0 of 84 external URLs unresponsive (this path never touches the client socket)

=== 3. it is process state, not disk state: restart, retry the dead URLs ===
      w=32    q=50  after restart -> 200
      w=1080  q=50  after restart -> 200
      w=48    q=75  after restart -> 200
      w=1920  q=75  after restart -> 200
      w=828   q=80  after restart -> 200
      w=384   q=90  after restart -> 200

=== 4. negative control: drop 'socket: _req.socket', same sweep ===
   => 0 of 84 URLs unresponsive

=== summary ===
   stock next, local images:    6 / 84 permanently hung
   stock next, external images: 0 / 84
   patched, local images:       0 / 84

=== server.log (nothing is logged for any of this) ===
      ▲ Next.js 16.3.1
      - Local:         http://localhost:3000
      ✓ Ready in 60ms
      ✓ Running next.config.js took 7ms

Current vs. Expected behavior

Current: after a single client aborts one cold /_next/image request, every subsequent request for that exact transform URL hangs indefinitely, for all clients, until the process restarts. Other widths and qualities of the same image keep working. Nothing is logged at any level.

Expected: a client aborting its own request affects only that request. The shared transform either completes or fails, and the coalesced cache key is released either way.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 27.0.0
  Available memory (MB): 24576
  Available CPU cores: 14
Binaries:
  Node: 22.19.0
  npm: 10.9.3
  pnpm: 10.33.0
Relevant Packages:
  next: 16.3.1
  react: 19.2.0
  react-dom: 19.2.0
Next.js Config:
  output: standalone (production), N/A in the reproduction

Which area(s) are affected?

Image (next/image)

Which stage(s) are affected?

next start (local), self-hosted production

Additional context

This was found in production, not in a test. A live storefront runs output: standalone behind nginx. Two trust badges on every product page, loading="lazy", are rendered at a fixed 40 px with sizes="40px". Four cache keys were wedged:

source w=32 w=48 w=64 w=96 w=128
logo-a.webp 200 hang hang 200 200
logo-b.webp 200 hang hang 200 200
other images on the same page 200 200 200 200 200

Two properties of this made it unusually hard to diagnose, and both come straight from the cache key being hash(href, width, quality, mimeType):

  • It only affected some visitors. sizes="40px" resolves to w=48 at DPR 1 and w=64 at DPR 1.5, but to w=96 at DPR 2. The badges were invisible on ordinary desktop monitors and perfectly fine on every retina laptop and phone including the ones used to check whether the site was broken.
  • curl said everything was fine. mimeType is part of the key, so the JPEG variant is a different entry from the WebP one. Any request without Accept: image/webp curl, uptime monitors, most crawlers got a healthy 200 from the intact JPEG key, while every real browser hung on the WebP key and rendered nothing. nginx returned 504 after 60 s; the Node process logged nothing at all.

Lazy loading is what makes this reachable at scale in the wild: a visitor who navigates away before a below-the-fold image enters the viewport aborts exactly the kind of cold transform this needs. The keys that died were the two most requested small widths of the two images present on every product page.

Relationship to existing reports. This is the same root cause as #96538, and #96542 proposes removing socket: _req.socket, which is also what step 4 of this reproduction isolates. What this report adds:

  • an independent occurrence, in production rather than in CI, with the visitor-facing symptoms above;
  • confirmation that it still reproduces on 16.3.1;
  • a reproduction with a negative control, showing the fix candidate actually closes it (6/84 -> 0/84) rather than only showing the symptom;
  • confirmation that external images are not affected, which scopes the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


    Back | FazBrowse Home | New Git URL