@wular/pnext 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,601 @@
1
+ // ---------------------------------------------------------------------------
2
+ // /_next/image optimizer endpoint (COMPAT).
3
+ //
4
+ // Next-parity implementation of the image optimizer: parameter validation with
5
+ // Next's exact 400 messages, local/remote source fetching, magic-byte content
6
+ // detection, animated/bypass passthrough, sharp resize + Accept-negotiated
7
+ // re-encode, Next's disk-cache layout with MISS/HIT/STALE semantics and
8
+ // stale-while-revalidate refresh, and Next's exact response headers
9
+ // (Cache-Control, Vary, ETag/304, Content-Disposition, CSP, X-Nextjs-Cache).
10
+ //
11
+ // Served from the request interceptor registered in ../register-image.ts.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import path from 'node:path';
15
+ import type { ResolvedConfig } from '../../config';
16
+ import { getNextConfig } from '../next/config-loader';
17
+ import { getImagesConfig, type ResolvedImagesConfig } from '../next/image/config';
18
+ import { hasLocalMatch, hasRemoteMatch } from '../next/image/patterns';
19
+ import { cacheHandlerGet, cacheHandlerSet, hasCacheHandler } from '../cache/handler';
20
+ import {
21
+ ANIMATABLE_TYPES,
22
+ AVIF,
23
+ BYPASS_TYPES,
24
+ JPEG,
25
+ PNG,
26
+ WEBP,
27
+ detectContentType,
28
+ extensionForType,
29
+ isAnimated,
30
+ typeForExtension,
31
+ } from './detect';
32
+ import {
33
+ IMAGE_CACHE_VERSION,
34
+ getHash,
35
+ imageCacheDir,
36
+ readCacheEntry,
37
+ writeCacheEntry,
38
+ type ImageCacheEntry,
39
+ } from './cache';
40
+ import {
41
+ ImageError,
42
+ fetchExternalImage,
43
+ fetchInternalImage,
44
+ readPublicFile,
45
+ type ImageUpstream,
46
+ } from './source';
47
+
48
+ type XCacheHeader = 'MISS' | 'HIT' | 'STALE';
49
+
50
+ interface ImageParams {
51
+ href: string;
52
+ isAbsolute: boolean;
53
+ isStatic: boolean;
54
+ width: number;
55
+ quality: number;
56
+ mimeType: string;
57
+ }
58
+
59
+ interface GeneratedImage {
60
+ buffer: Buffer;
61
+ contentType: string;
62
+ maxAge: number;
63
+ etag: string;
64
+ upstreamEtag: string;
65
+ }
66
+
67
+ // -- sharp ------------------------------------------------------------------
68
+
69
+ interface SharpPipeline {
70
+ timeout(options: { seconds: number }): SharpPipeline;
71
+ rotate(): SharpPipeline;
72
+ resize(
73
+ width: number | null,
74
+ height?: number | null,
75
+ options?: { withoutEnlargement?: boolean },
76
+ ): SharpPipeline;
77
+ webp(options: { quality: number }): SharpPipeline;
78
+ avif(options: { quality: number; effort?: number }): SharpPipeline;
79
+ png(options: { quality: number }): SharpPipeline;
80
+ jpeg(options: { quality: number; mozjpeg?: boolean }): SharpPipeline;
81
+ toBuffer(): Promise<Buffer>;
82
+ }
83
+
84
+ type SharpModule = (input: Buffer | Uint8Array) => SharpPipeline;
85
+
86
+ let sharpPromise: Promise<SharpModule | undefined> | undefined;
87
+ function loadSharp(): Promise<SharpModule | undefined> {
88
+ if (!sharpPromise) {
89
+ sharpPromise = import('sharp')
90
+ .then(m => ((m as { default?: unknown }).default ?? m) as SharpModule)
91
+ .catch(() => undefined);
92
+ }
93
+ return sharpPromise;
94
+ }
95
+
96
+ // -- helpers ----------------------------------------------------------------
97
+
98
+ const warnedOnce = new Set<string>();
99
+ function warnOnce(message: string): void {
100
+ if (warnedOnce.has(message)) return;
101
+ warnedOnce.add(message);
102
+ console.warn(message);
103
+ }
104
+
105
+ function badRequest(message: string): Response {
106
+ return new Response(message, { status: 400, headers: { 'content-type': 'text/plain' } });
107
+ }
108
+
109
+ function errorResponse(error: unknown): Response {
110
+ if (error instanceof ImageError) {
111
+ return new Response(error.message, {
112
+ status: error.statusCode,
113
+ headers: { 'content-type': 'text/plain' },
114
+ });
115
+ }
116
+ console.error('image optimizer failed', error);
117
+ return new Response('Internal Server Error', { status: 500 });
118
+ }
119
+
120
+ function getMaxAge(cacheControl: string | null | undefined): number {
121
+ if (!cacheControl) return 0;
122
+ for (const directive of cacheControl.split(',')) {
123
+ const [key = '', rawValue] = directive.trim().split('=', 2);
124
+ if (key.toLowerCase() !== 's-maxage' && key.toLowerCase() !== 'max-age') continue;
125
+ let value = (rawValue ?? '').toLowerCase();
126
+ if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
127
+ const n = parseInt(value, 10);
128
+ if (!isNaN(n)) return n;
129
+ }
130
+ return 0;
131
+ }
132
+
133
+ /** First configured format the Accept header explicitly names, or '' (keep source format). */
134
+ function getSupportedMimeType(formats: string[], accept: string): string {
135
+ for (const format of formats) {
136
+ if (accept.includes(format)) return format;
137
+ }
138
+ return '';
139
+ }
140
+
141
+ function getFileNameWithExtension(url: string, contentType: string | null): string {
142
+ const [urlWithoutQueryParams = url] = url.split('?', 1);
143
+ const fileNameWithExtension = urlWithoutQueryParams.split('/').pop();
144
+ if (!contentType || !fileNameWithExtension) return 'image.bin';
145
+ const [fileName = 'image'] = fileNameWithExtension.split('.', 1);
146
+ const extension = extensionForType(contentType) ?? 'bin';
147
+ return `${fileName}.${extension}`;
148
+ }
149
+
150
+ function contentDispositionHeader(type: string, fileName: string): string {
151
+ if (/^[\x20-\x7e]*$/.test(fileName) && !fileName.includes('"')) {
152
+ return `${type}; filename="${fileName}"`;
153
+ }
154
+ return `${type}; filename*=UTF-8''${encodeURIComponent(fileName)}`;
155
+ }
156
+
157
+ // -- validation -------------------------------------------------------------
158
+
159
+ function validateParams(
160
+ request: Request,
161
+ searchParams: URLSearchParams,
162
+ config: ResolvedImagesConfig,
163
+ basePath: string,
164
+ ): ImageParams | { errorMessage: string } {
165
+ const url = searchParams.get('url');
166
+ const w = searchParams.get('w');
167
+ const q = searchParams.get('q');
168
+
169
+ if (!url) return { errorMessage: '"url" parameter is required' };
170
+ if (url.length > 3072) return { errorMessage: '"url" parameter is too long' };
171
+ if (url.startsWith('//')) {
172
+ return { errorMessage: '"url" parameter cannot be a protocol-relative URL (//)' };
173
+ }
174
+
175
+ let href: string;
176
+ let isAbsolute: boolean;
177
+ if (url.startsWith('/')) {
178
+ href = url;
179
+ isAbsolute = false;
180
+ let pathname = url.split('?', 1)[0] ?? url;
181
+ try {
182
+ pathname = decodeURIComponent(pathname);
183
+ } catch {
184
+ // keep the raw pathname; the regex below still guards the raw form
185
+ }
186
+ if (/\/_next\/image($|\/)/.test(pathname)) {
187
+ return { errorMessage: '"url" parameter cannot be recursive' };
188
+ }
189
+ if (!hasLocalMatch(config.localPatterns, new URL(url, 'http://n'))) {
190
+ return { errorMessage: '"url" parameter is not allowed' };
191
+ }
192
+ } else {
193
+ let parsed: URL;
194
+ try {
195
+ parsed = new URL(url);
196
+ } catch {
197
+ return { errorMessage: '"url" parameter is invalid' };
198
+ }
199
+ href = parsed.toString();
200
+ isAbsolute = true;
201
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
202
+ return { errorMessage: '"url" parameter is invalid' };
203
+ }
204
+ if (!hasRemoteMatch(config.domains, config.remotePatterns, parsed)) {
205
+ return { errorMessage: '"url" parameter is not allowed' };
206
+ }
207
+ }
208
+
209
+ if (!w) return { errorMessage: '"w" parameter (width) is required' };
210
+ if (!/^[0-9]+$/.test(w)) {
211
+ return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
212
+ }
213
+ if (!q) return { errorMessage: '"q" parameter (quality) is required' };
214
+ if (!/^[0-9]+$/.test(q)) {
215
+ return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
216
+ }
217
+
218
+ const width = parseInt(w, 10);
219
+ if (width <= 0 || isNaN(width)) {
220
+ return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
221
+ }
222
+ const sizes = [...config.deviceSizes, ...config.imageSizes];
223
+ if (!sizes.includes(width)) {
224
+ return { errorMessage: `"w" parameter (width) of ${width} is not allowed` };
225
+ }
226
+
227
+ const quality = parseInt(q, 10);
228
+ if (isNaN(quality) || quality < 1 || quality > 100) {
229
+ return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
230
+ }
231
+ if (config.qualities && !config.qualities.includes(quality)) {
232
+ return { errorMessage: `"q" parameter (quality) of ${q} is not allowed` };
233
+ }
234
+
235
+ const mimeType = getSupportedMimeType(config.formats, request.headers.get('accept') ?? '');
236
+ const isStatic =
237
+ url.startsWith(`${basePath}/_next/static/media`) ||
238
+ url.startsWith(`${basePath}/_next/static/immutable/media`);
239
+
240
+ return { href, isAbsolute, isStatic, width, quality, mimeType };
241
+ }
242
+
243
+ // -- optimization -----------------------------------------------------------
244
+
245
+ async function optimizeImage(
246
+ buffer: Buffer,
247
+ contentType: string,
248
+ width: number,
249
+ quality: number,
250
+ ): Promise<Buffer> {
251
+ const sharp = await loadSharp();
252
+ if (!sharp) throw new Error('sharp is not available');
253
+ let transformer = sharp(buffer)
254
+ .timeout({ seconds: 7 })
255
+ .rotate()
256
+ .resize(width, undefined, { withoutEnlargement: true });
257
+ if (contentType === AVIF) {
258
+ transformer = transformer.avif({ quality: Math.max(quality - 20, 1), effort: 3 });
259
+ } else if (contentType === WEBP) {
260
+ transformer = transformer.webp({ quality });
261
+ } else if (contentType === PNG) {
262
+ transformer = transformer.png({ quality });
263
+ } else if (contentType === JPEG) {
264
+ transformer = transformer.jpeg({ quality, mozjpeg: true });
265
+ }
266
+ return transformer.toBuffer();
267
+ }
268
+
269
+ async function generateImage(
270
+ upstream: ImageUpstream,
271
+ params: ImageParams,
272
+ config: ResolvedImagesConfig,
273
+ ): Promise<GeneratedImage> {
274
+ const { href, width, quality, mimeType } = params;
275
+ const upstreamBuffer = upstream.buffer;
276
+ const upstreamEtag = upstream.etag;
277
+ const maxAge = Math.max(config.minimumCacheTTL, getMaxAge(upstream.cacheControl));
278
+
279
+ const upstreamType = detectContentType(upstreamBuffer);
280
+ if (!upstreamType || !upstreamType.startsWith('image/') || upstreamType.includes(',')) {
281
+ console.error("The requested resource isn't a valid image for", href, 'received', upstreamType);
282
+ throw new ImageError(400, "The requested resource isn't a valid image.");
283
+ }
284
+ if (upstreamType.startsWith('image/svg') && !config.dangerouslyAllowSVG) {
285
+ console.error(
286
+ `The requested resource "${href}" has type "${upstreamType}" but dangerouslyAllowSVG is disabled. Consider adding the "unoptimized" property to the <Image>.`,
287
+ );
288
+ throw new ImageError(400, '"url" parameter is valid but image type is not allowed');
289
+ }
290
+ if (ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)) {
291
+ warnOnce(
292
+ `The requested resource "${href}" is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the <Image>.`,
293
+ );
294
+ return {
295
+ buffer: upstreamBuffer,
296
+ contentType: upstreamType,
297
+ maxAge,
298
+ etag: upstreamEtag,
299
+ upstreamEtag,
300
+ };
301
+ }
302
+ if (BYPASS_TYPES.includes(upstreamType)) {
303
+ return {
304
+ buffer: upstreamBuffer,
305
+ contentType: upstreamType,
306
+ maxAge,
307
+ etag: upstreamEtag,
308
+ upstreamEtag,
309
+ };
310
+ }
311
+
312
+ let contentType: string;
313
+ if (mimeType) {
314
+ contentType = mimeType;
315
+ } else if (
316
+ extensionForType(upstreamType) &&
317
+ upstreamType !== WEBP &&
318
+ upstreamType !== AVIF
319
+ ) {
320
+ contentType = upstreamType;
321
+ } else {
322
+ contentType = JPEG;
323
+ }
324
+
325
+ try {
326
+ const optimizedBuffer = await optimizeImage(upstreamBuffer, contentType, width, quality);
327
+ return {
328
+ buffer: optimizedBuffer,
329
+ contentType,
330
+ maxAge,
331
+ etag: getHash([optimizedBuffer]),
332
+ upstreamEtag,
333
+ };
334
+ } catch {
335
+ // If optimization fails, fall back to the original image bytes.
336
+ return {
337
+ buffer: upstreamBuffer,
338
+ contentType: upstreamType,
339
+ maxAge: config.minimumCacheTTL,
340
+ etag: upstreamEtag,
341
+ upstreamEtag,
342
+ };
343
+ }
344
+ }
345
+
346
+ // -- cache orchestration ----------------------------------------------------
347
+
348
+ interface OptimizerImagesConfig extends ResolvedImagesConfig {
349
+ maximumDiskCacheSize: number | undefined;
350
+ }
351
+
352
+ function resolveOptimizerConfig(): OptimizerImagesConfig {
353
+ const base = getImagesConfig();
354
+ const raw = (getNextConfig().images ?? {}) as Record<string, unknown>;
355
+ return {
356
+ ...base,
357
+ maximumDiskCacheSize:
358
+ typeof raw.maximumDiskCacheSize === 'number' ? raw.maximumDiskCacheSize : undefined,
359
+ };
360
+ }
361
+
362
+ function diskCacheEnabled(config: OptimizerImagesConfig): boolean {
363
+ if (config.customCacheHandler && hasCacheHandler()) return false;
364
+ if (config.maximumDiskCacheSize === 0) return false;
365
+ const experimental = (getNextConfig().experimental ?? {}) as Record<string, unknown>;
366
+ return experimental.isrFlushToDisk !== false;
367
+ }
368
+
369
+ async function fetchUpstream(
370
+ params: ImageParams,
371
+ config: OptimizerImagesConfig,
372
+ coreConfig: ResolvedConfig,
373
+ origin: string,
374
+ ): Promise<ImageUpstream> {
375
+ if (params.isAbsolute) {
376
+ return fetchExternalImage(
377
+ params.href,
378
+ config.dangerouslyAllowLocalIP,
379
+ config.maximumResponseBody,
380
+ config.maximumRedirects,
381
+ );
382
+ }
383
+ const publicFile = await readPublicFile(
384
+ [
385
+ path.join(coreConfig.outPath, 'public'),
386
+ coreConfig.publicPath,
387
+ path.join(coreConfig.root, 'public'),
388
+ ],
389
+ params.href,
390
+ );
391
+ if (publicFile) {
392
+ return {
393
+ buffer: publicFile,
394
+ contentType: null,
395
+ cacheControl: null,
396
+ etag: getHash([publicFile]),
397
+ };
398
+ }
399
+ return fetchInternalImage(params.href, origin, config.maximumResponseBody);
400
+ }
401
+
402
+ async function generateAndStore(
403
+ cacheKey: string,
404
+ params: ImageParams,
405
+ config: OptimizerImagesConfig,
406
+ coreConfig: ResolvedConfig,
407
+ origin: string,
408
+ ): Promise<ImageCacheEntry & { contentType: string }> {
409
+ const upstream = await fetchUpstream(params, config, coreConfig, origin);
410
+ const image = await generateImage(upstream, params, config);
411
+ const entry: ImageCacheEntry & { contentType: string } = {
412
+ buffer: image.buffer,
413
+ etag: image.etag,
414
+ upstreamEtag: image.upstreamEtag,
415
+ extension: extensionForType(image.contentType) ?? 'bin',
416
+ maxAge: image.maxAge,
417
+ expireAt: Math.max(image.maxAge, config.minimumCacheTTL) * 1000 + Date.now(),
418
+ contentType: image.contentType,
419
+ };
420
+ if (config.customCacheHandler && hasCacheHandler()) {
421
+ const revalidate = Math.max(image.maxAge, config.minimumCacheTTL);
422
+ await cacheHandlerSet(
423
+ cacheKey,
424
+ {
425
+ kind: 'IMAGE',
426
+ buffer: entry.buffer.toString('base64'),
427
+ etag: entry.etag,
428
+ upstreamEtag: entry.upstreamEtag,
429
+ extension: entry.extension,
430
+ revalidate,
431
+ },
432
+ { kind: 'IMAGE' },
433
+ );
434
+ } else if (diskCacheEnabled(config)) {
435
+ await writeCacheEntry(
436
+ imageCacheDir(coreConfig.root),
437
+ cacheKey,
438
+ entry,
439
+ config.maximumDiskCacheSize,
440
+ );
441
+ }
442
+ return entry;
443
+ }
444
+
445
+ // Concurrent requests for the same (url, w, q, format) share one upstream
446
+ // fetch + optimization; every waiter reports MISS, matching Next.
447
+ const inFlight = new Map<string, Promise<ImageCacheEntry & { contentType: string }>>();
448
+ // One background stale-refresh per key at a time.
449
+ const refreshing = new Set<string>();
450
+
451
+ async function readCache(
452
+ cacheKey: string,
453
+ config: OptimizerImagesConfig,
454
+ coreConfig: ResolvedConfig,
455
+ ): Promise<(ImageCacheEntry & { isStale: boolean }) | undefined> {
456
+ if (config.customCacheHandler && hasCacheHandler()) {
457
+ const cached = (await cacheHandlerGet(cacheKey, { kind: 'IMAGE' })) as
458
+ | { value?: Record<string, unknown>; lastModified?: number }
459
+ | undefined;
460
+ const value = cached?.value;
461
+ if (value?.kind !== 'IMAGE' || typeof value.buffer !== 'string') return undefined;
462
+ const revalidate =
463
+ typeof value.revalidate === 'number' ? value.revalidate : config.minimumCacheTTL;
464
+ const lastModified = cached?.lastModified ?? Date.now();
465
+ const expireAt = Math.max(revalidate, config.minimumCacheTTL) * 1000 + lastModified;
466
+ return {
467
+ buffer: Buffer.from(value.buffer, 'base64'),
468
+ etag: typeof value.etag === 'string' ? value.etag : getHash([value.buffer]),
469
+ upstreamEtag: typeof value.upstreamEtag === 'string' ? value.upstreamEtag : '',
470
+ extension: typeof value.extension === 'string' ? value.extension : 'bin',
471
+ maxAge: revalidate,
472
+ expireAt,
473
+ isStale: expireAt < Date.now(),
474
+ };
475
+ }
476
+ if (!diskCacheEnabled(config)) return undefined;
477
+ const entry = await readCacheEntry(imageCacheDir(coreConfig.root), cacheKey);
478
+ if (!entry) return undefined;
479
+ return { ...entry, isStale: Date.now() > entry.expireAt };
480
+ }
481
+
482
+ // -- response ---------------------------------------------------------------
483
+
484
+ function buildResponse(
485
+ request: Request,
486
+ params: ImageParams,
487
+ config: OptimizerImagesConfig,
488
+ buffer: Buffer,
489
+ contentType: string,
490
+ etag: string,
491
+ maxAge: number,
492
+ xCache: XCacheHeader,
493
+ ): Response {
494
+ const headers = new Headers();
495
+ headers.set('Vary', 'Accept');
496
+ headers.set(
497
+ 'Cache-Control',
498
+ params.isStatic
499
+ ? 'public, max-age=315360000, immutable'
500
+ : `public, max-age=${maxAge}, must-revalidate`,
501
+ );
502
+ headers.set('ETag', etag);
503
+
504
+ const ifNoneMatch = request.headers.get('if-none-match');
505
+ if (ifNoneMatch && (ifNoneMatch === etag || ifNoneMatch === `"${etag}"`)) {
506
+ headers.set('X-Nextjs-Cache', xCache);
507
+ return new Response(null, { status: 304, headers });
508
+ }
509
+
510
+ headers.set('Content-Type', contentType);
511
+ const fileName = getFileNameWithExtension(params.href, contentType);
512
+ headers.set(
513
+ 'Content-Disposition',
514
+ contentDispositionHeader(config.contentDispositionType, fileName),
515
+ );
516
+ headers.set('Content-Security-Policy', config.contentSecurityPolicy);
517
+ headers.set('X-Nextjs-Cache', xCache);
518
+ headers.set('Content-Length', String(buffer.byteLength));
519
+
520
+ if (request.method === 'HEAD') return new Response(null, { status: 200, headers });
521
+ return new Response(new Uint8Array(buffer), { status: 200, headers });
522
+ }
523
+
524
+ // -- entry point ------------------------------------------------------------
525
+
526
+ export async function serveImageOptimizer(
527
+ request: Request,
528
+ coreConfig: ResolvedConfig,
529
+ ): Promise<Response> {
530
+ const config = resolveOptimizerConfig();
531
+ const url = new URL(request.url);
532
+ const basePath = '';
533
+
534
+ const paramsResult = validateParams(request, url.searchParams, config, basePath);
535
+ if ('errorMessage' in paramsResult) return badRequest(paramsResult.errorMessage);
536
+ const params = paramsResult;
537
+
538
+ const cacheKey = getHash([
539
+ IMAGE_CACHE_VERSION,
540
+ params.href,
541
+ params.width,
542
+ params.quality,
543
+ params.mimeType,
544
+ ]);
545
+ const origin = url.origin;
546
+
547
+ try {
548
+ const cached = await readCache(cacheKey, config, coreConfig);
549
+ if (cached && !cached.isStale) {
550
+ return buildResponse(
551
+ request,
552
+ params,
553
+ config,
554
+ cached.buffer,
555
+ typeForExtension(cached.extension) ?? 'application/octet-stream',
556
+ cached.etag,
557
+ cached.maxAge,
558
+ 'HIT',
559
+ );
560
+ }
561
+ if (cached) {
562
+ // Serve the stale bytes now, refresh in the background.
563
+ if (!refreshing.has(cacheKey)) {
564
+ refreshing.add(cacheKey);
565
+ void generateAndStore(cacheKey, params, config, coreConfig, origin)
566
+ .catch(() => undefined)
567
+ .finally(() => refreshing.delete(cacheKey));
568
+ }
569
+ return buildResponse(
570
+ request,
571
+ params,
572
+ config,
573
+ cached.buffer,
574
+ typeForExtension(cached.extension) ?? 'application/octet-stream',
575
+ cached.etag,
576
+ cached.maxAge,
577
+ 'STALE',
578
+ );
579
+ }
580
+
581
+ let pending = inFlight.get(cacheKey);
582
+ if (!pending) {
583
+ pending = generateAndStore(cacheKey, params, config, coreConfig, origin);
584
+ inFlight.set(cacheKey, pending);
585
+ void pending.catch(() => undefined).finally(() => inFlight.delete(cacheKey));
586
+ }
587
+ const entry = await pending;
588
+ return buildResponse(
589
+ request,
590
+ params,
591
+ config,
592
+ entry.buffer,
593
+ entry.contentType,
594
+ entry.etag,
595
+ entry.maxAge,
596
+ 'MISS',
597
+ );
598
+ } catch (error) {
599
+ return errorResponse(error);
600
+ }
601
+ }