* fix(image): emit /_next/image URLs to match Next.js
Closes#1513
The default image loader and optimization endpoint switched from the
vinext-specific /_vinext/image path to Next.js's canonical /_next/image.
This unblocks the deploy suite tests that import Next.js's expected
URL shape (/_next/image?url=...&w=...&q=...).
* refactor(image): use IMAGE_OPTIMIZATION_PATH constant at remaining call sites
Replace hardcoded "/_next/image" strings in index.ts, app-rsc-handler.ts,
and the generated worker entry templates in deploy.ts with the
IMAGE_OPTIMIZATION_PATH constant from server/image-optimization, matching
the pattern already used in prod-server.ts. Prevents future drift if the
path ever changes again.
* feat(image): accept both /_next/image and /_vinext/image at the optimizer
Add a VINEXT_IMAGE_OPTIMIZATION_PATH constant and an
isImageOptimizationPath() helper, then route through every match site
(prod-server, dev server passthrough, app RSC handler, generated worker
templates, and shipped example workers). Apps that wire image URLs to
either prefix now hit the same handler; new URLs are still emitted via
IMAGE_OPTIMIZATION_PATH.
* fix(image): preserve inline styles on remote images
Remote next/image renders with explicit width and height delegate through UnpicImage, but that branch dropped the user style prop. This made remote images ignore inline styling such as borderRadius, objectPosition, and transform while local images and fill images preserved it.
The shim now passes style through the remote constrained Unpic boundary and isolates the Unpic type gap where the runtime supports style but the public type omits it. A focused SSR regression test covers the rendered remote image style contract.
* refactor(image): avoid remote style type assertion
The previous fix isolated an assertion around @unpic/react because the runtime forwards style but the public component type omits it.
Keep the existing dependency shape and pass the runtime-supported style prop through a typed spread object instead. The remote image style regression coverage remains unchanged.
Priority images currently only render eager loading and high fetch priority. That misses the resource hint Next.js emits, so the browser cannot discover the optimized image until it reaches the img element.
The image shim now calls ReactDOM.preload with the final optimized image URL, srcset, sizes, and fetch priority metadata for priority and preload images. Focused image component coverage asserts the SSR preload hint and the modern preload prop behaviour.
Co-authored-by: James <james@eli.cx>
Remote Image with fill=true currently routes through Unpic fullWidth, which renders a flowing image without data-nimg="fill" or absolute positioning. That violates Next.js's fill invariant for parent-filling layouts.
Centralize fill styles and render remote fill with the same DOM contract as local fill and getImageProps. Add SSR regression coverage for remote fill.
* fix(image): support images.dangerouslyAllowLocalIP for private-IP rejections
Ports Next.js commit 5452439f3db2a78967178ca4180b27fb48393a19 (PR #91686)
to vinext's next/image shim and server-side image config.
Changes:
- Add isPrivateIp() helper in shims/image-config.ts (IPv4 + IPv6, no deps)
- Update validateRemoteUrl in shims/image.tsx to reject literal private-IP
hostnames unless dangerouslyAllowLocalIP is true
- Wire dangerouslyAllowLocalIP through next-config type, Vite define,
global.d.ts, and server-side ImageConfig
- Add unit tests for isPrivateIp and component-level private-IP guard
Closes#1065
* fix(image): use ipaddr.js for correct private-IP classification
Addresses review feedback on the SSRF guard:
- Replaces the hand-rolled isPrivateIp with ipaddr.js (same library Next.js
uses), eliminating bugs in IPv4/IPv6 range checks
- Fixes incorrect 2001:2f::/32 check (not a real range) and adds all missing
non-unicast ranges: CGNAT, multicast, reserved, benchmarking, teredo,
documentation, discard, NAT64
- Correctly handles ::ffff: IPv4-mapped addresses via isIPv4MappedAddress()
- Adds explanatory comments for best-effort client-side guard and
dangerouslyAllowLocalIP server-side config
- Adds comments in tests explaining the vi.resetModules() pattern
* fix(image): externalize ipaddr.js to prevent SSR crash
ipaddr.js is a CJS UMD module that assigns to `this.ipaddr` at the
bottom of its wrapper. When Vite's SSR module runner loads it with
`noExternal: true`, the UMD wrapper executes with `this = undefined`
(strict mode in ESM context), producing:
TypeError: Cannot set properties of undefined (setting 'ipaddr')
This crashed every page render that imports `next/image` (via the
image-config.ts shim). The image-optimization-parity tests failed
with 500 errors.
Fix: add `ipaddr.js` to `ssr.external` in all 3 Vite environment
configs (top-level, RSC, SSR) so Node's native loader handles the
CJS module correctly.
* fix: next/image only fire onError and onLoad once per src per mount (#990)
Use ref-based dedup to prevent onLoad and onError from firing multiple
times for the same image src across React re-renders. Ported from the
upstream Next.js fix in vercel/next.js#93209.
- Destructure onError from props (previously fell through rest spread)
- Add lastLoadedSrcRef/lastErrorSrcRef to guard against duplicate fires
- Pass onError/onLoad to all 4 render paths (was missing from UnpicImage)
- Add SSR tests covering all render paths and verifying no DOM attribute leaks
The dedup prevents infinite re-render loops when user code calls setState
inside onError/onLoad, matching Next.js behavior.
* fix: add "use client" directive to next/image shim
useRef is a hook requiring client rendering context. Without "use client", RSC executes useRef() and throws "useRef is not a function", breaking create-next-app CI.
* fix: move resolveImageSource above handler closures for readability
* fix(image): strip priority prop before forwarding to UnpicImage to prevent DOM leak
The `priority` prop is a Next.js-specific concept that must never reach the
DOM as an attribute. On the two UnpicImage render paths (remote URL with fill
and remote URL with width+height), `priority={true}` was forwarded directly
to `@unpic/react`'s Image component which did not reliably strip it before
rendering the DOM `<img>`, triggering:
Received `true` for a non-boolean attribute `priority`.
Fix: replace `priority={priority}` with the equivalent HTML semantics —
`loading={priority ? 'eager' : loading ?? 'lazy'}` and
`fetchPriority={priority ? 'high' : undefined}` — matching what the local
image and custom-loader paths already do correctly.
Adds 10 reproduction tests covering both affected render paths.
* test: add 50ms slack to compressed streaming timing assertion to fix CI flakiness
* chore: migrate to vite plus
* Disable typeAware and typeCheck
* Update CI
* Fix CI
* Fix test
* Clean
* Run test with vp
* Try revert
* react: false In test
* Fix test
* Revert "Try revert"
This reverts commit 009da10473.
* Update
* Update
* Try revert ci changes
* revert
* Run vp migrate
* Disable typeAware and typeCheck for now
* Better resolve for test
* Use vp dev instead of vite
* Update expect
* Fix NormalizeManifestModuleId
* Try increase timeout
* Update to use vp
* Try new check
* Bring back npx vp
* Migrate CI
* Make next-intl resolvable
* Update
* Update
* Update
* fix: support onLoadingComplete in modern next/image shim
Add the deprecated-but-still-supported onLoadingComplete prop to the
modern next/image component, matching Next.js behavior where the
callback receives the underlying HTMLImageElement on load.
- Add onLoadingComplete to ImageProps interface and type declarations
- Wire handleLoad into all render paths (local, custom loader, remote)
- Destructure onLoad/onLoadingComplete in getImageProps to prevent leak
* fix: correct callback order and add remote URL test
- Swap onLoad/onLoadingComplete invocation order to match Next.js
(onLoad fires first, then onLoadingComplete)
- Fix stale comment that described legacy { naturalWidth, naturalHeight }
- Add remote URL (UnpicImage) SSR test for full path coverage
* add oxfmt formatter: config, scripts, CI, editor setup, docs
* rebuild lockfile
* fix: add Format to required checks list, remove dead ignore pattern
* run fmt
* add format to agents.md again