Skip to content

fix(build): honor inline next config for static export - #2543

Merged
james-elicx merged 10 commits into
mainfrom
codex/fix-inline-next-config-export
Jul 6, 2026
Merged

fix(build): honor inline next config for static export#2543
james-elicx merged 10 commits into
mainfrom
codex/fix-inline-next-config-export

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • expose vinext({ nextConfig }) through Vite plugin metadata for CLI/deploy config discovery
  • resolve inline object/function config before output-mode decisions and pass the resolved config into prerender execution
  • keep Cloudflare deploy config resolution inside the selected CLOUDFLARE_ENV
  • support promised/nested Vite plugin composition when discovering inline config

Root cause

vinext build and vinext-cloudflare deploy decided whether to use static export from next.config.* before reading the Vite plugin's inline nextConfig. Even after selecting prerendering, the prerender runner reloaded disk config, so inline-only output: "export" did not receive export output/error semantics.

Validation

  • vp test run tests/deploy-prerender-config.test.ts tests/prerender-config.test.ts (23 tests)
  • vp check packages/cloudflare/src/deploy.ts packages/vinext/src/cli.ts packages/vinext/src/config/next-config.ts packages/vinext/src/index.ts tests/deploy-prerender-config.test.ts tests/prerender-config.test.ts
  • vp run @vinext/cloudflare#build
  • vp run vinext#build
  • manual real CLI reproduction with inline nextConfig.output = "export"; confirmed export-mode log and HTML at dist/client/index.html and dist/client/old-school.html
  • two independent reviews: no findings

Closes #2542

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared b70fdc0 against base fd1cc3d using alternating same-runner rounds.

1 improved · 2 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) Next.js 185.6 KB 185.6 KB ⚫ 0.0%
Client bundle size (gzip) vinext 126.3 KB 126.3 KB ⚫ +0.0%
Client entry size (gzip) vinext 120.3 KB 120.3 KB ⚫ +0.0%
Dev server cold start Next.js 2.46 s 2.37 s 🟢 -3.6%
Dev server cold start vinext 2.57 s 2.71 s 🔴 +5.1%
Production build time Next.js 4.33 s 4.33 s ⚫ -0.0%
Production build time vinext 2.98 s 3.08 s 🔴 +3.4%
RSC entry closure size (gzip) vinext 97.5 KB 97.5 KB ⚫ +0.0%
Server bundle size (gzip) vinext 164.0 KB 164.0 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

1 similar comment
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Reviewed the full diff, ran the targeted tests (tests/deploy-prerender-config.test.ts + tests/prerender-config.test.ts, 23 passing), and typechecked/linted all four changed source files (clean). The core change is correct: both production prerender entrypoints (cli.ts:699, deploy.ts:877) now pass nextConfigOverride, so inline-only output: "export" is honored, and the deploy path resolves inline config inside the selected CLOUDFLARE_ENV.

Correctness — looks good

  • findVinextNextConfigInPlugins correctly skips null/undefined (if (nextConfig) return nextConfig), so the always-present __vinextNextConfig: options.nextConfig ?? null property doesn't accidentally match when no inline config is set.
  • loadBuildViteConfigMetadata is now called once in buildApp() (line 494) and the duplicate later call was removed — no double loadConfigFromFile.
  • deploy.ts wraps nextConfig resolution in withCloudflareEnv(buildEnv, ...), matching the existing pattern used for cache/prerender/routeRoot config.
  • shouldLoadVinextPrerenderConfig now reads the inline-aware nextConfig.output, so export mode short-circuits vinext prerender config loading as intended.
  • Both prerender call sites are covered — no other runPrerender invocations exist that would need the override.

Non-blocking observations

  1. Redundant disk config read in run-prerender.ts:188. When nextConfigOverride is supplied (now the case for every production prerender), resolveNextConfig(await loadNextConfig(root), root) still runs on line 188, then is fully spread-over by the override. For inline-only apps with no next.config.* on disk this is harmless (loadNextConfig returns null), but it's wasted work and re-reads/parses disk config that the caller already resolved. Consider skipping the load when an override is present, or documenting why the base load is still needed (e.g. to catch keys the override doesn't set — but a full override spread means the base is only useful for the buildId file read below, which is separate). Worth a follow-up.

  2. Vite config is loaded multiple times per deploy. loadVinextNextConfigFromViteConfig, loadVinextCacheConfigFromViteConfig, loadVinextPrerenderConfigFromViteConfig, and the routeRoot loader each independently call loadProjectViteApi + loadConfigFromFile. This PR adds a fourth loadConfigFromFile on the deploy path. Pre-existing pattern, but as the count grows it may be worth loading the Vite config once and reading all vinext plugin properties from a single loaded.config.plugins pass.

Neither observation blocks merge. The change is well-scoped, tested, and the parity between cli.ts and deploy.ts prerender paths is maintained.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

I reviewed the full diff, ran the targeted tests (23 passing), and typechecked/linted all changed source files (clean).

Verdict: no blocking issues. The change correctly makes vinext build and vinext-cloudflare deploy honor inline vinext({ nextConfig }) when deciding static-export mode, and both production prerender call sites now pass nextConfigOverride so the runner doesn't silently reload disk config.

Correctness checks that passed:

  • findVinextNextConfigInPlugins correctly skips the always-present null property.
  • loadBuildViteConfigMetadata was properly de-duplicated to a single call site in buildApp().
  • Deploy resolution is correctly wrapped in withCloudflareEnv(buildEnv, ...), matching existing patterns.
  • Both runPrerender call sites (cli + deploy) are covered — no missed call sites.

Two non-blocking observations noted in the review:

  1. run-prerender.ts:188 still does a redundant disk loadNextConfig even when an override fully replaces it — harmless but wasteful; worth a follow-up.
  2. The deploy path now loads the Vite config file four separate times (cache/next/prerender/routeRoot); pre-existing pattern but could be consolidated into a single load.

github run

@james-elicx
james-elicx marked this pull request as ready for review July 6, 2026 09:10
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2543
npm i https://pkg.pr.new/create-vinext-app@2543
npm i https://pkg.pr.new/vinext@2543

commit: 3ad0046

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Reviewed the full diff (including the follow-up commit 08d2bc7), ran the targeted tests (tests/deploy-prerender-config.test.ts, tests/prerender-config.test.ts, tests/prerender.test.ts — 115 passing), and ran vp check on all changed source files (format/lint/type — clean).

The core change is correct and well-tested: both production prerender entrypoints (cli.ts:702, deploy.ts:898) now pass a fully-resolved nextConfig, and run-prerender.ts uses it directly instead of reloading disk config — so inline-only output: "export" is honored end-to-end. The deploy consolidation into loadDeployViteConfigMetadata (one loadConfigFromFile per deploy) directly addresses the prior review's observation #2, and the run-prerender.ts change addresses observation #1.

Non-blocking findings

1. Undocumented behavioral regression: deploy now always loads the Vite config. The previous deploy path deliberately skipped loading the Vite config when the prerender decision was already settled (--prerender-all or disk next.config output: "export"). This was guarded by two tests using a throwing vite config — both deleted in this PR (does not load Vite config when the prerender-all flag already wins, does not load Vite config when static export already wins). The new loadDeployViteConfigMetadata runs unconditionally at deploy.ts:846, so a project with a broken/throwing vite.config.ts that previously deployed under --prerender-all will now fail.

This eager load is inherent to the feature (you can't decide to skip loading the config you need to read inline nextConfig from), so I'm not asking to change the behavior. But the deletion of those guard tests removes a real invariant with no replacement assertion or comment. Consider adding a short comment at the loadDeployViteConfigMetadata call site noting that the Vite config must be loaded eagerly to discover inline nextConfig, so the reason it's no longer lazy is captured for the next reader.

2. Async/sync flatten inconsistency across the four plugin finders. The new findVinextNextConfigInPlugins uses an async flattenPluginOptions that awaits Promise plugin entries (and there's an explicit test: discovers inline Next.js config through promised plugin composition). But the three sibling finders called on the same plugins array in loadDeployViteConfigMetadatafindVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins — use synchronous flatten helpers that do not resolve Promises (config/prerender.ts:70, cache/cache-adapters-virtual.ts:62). loadConfigFromFile returns the raw user plugins without resolving promises, so a user who wraps vinext() in Promise.resolve(...) would get their inline nextConfig discovered but their prerender/cache/routeRoot config silently dropped. Not a regression (those finders were already sync), but the PR now introduces an asymmetry where one property is promise-aware and three are not. Worth a follow-up to unify them (or a comment noting the limitation).

3. Redundant disk read still present in emitPrerenderPathManifest. run-prerender.ts was fixed to skip loadNextConfig when a full config is supplied, but the parallel path in build/prerender-paths.ts:375 still does resolveNextConfig(await loadNextConfig(root)) and then fully spreads nextConfigOverride (the complete resolved config) over it. Result is correct (output: "export" is honored there too), but the disk read is dead work whenever the override is present — the same pattern this PR just removed from run-prerender.ts. Both cli.ts and deploy.ts call emitPrerenderPathManifest with the full resolved config, so this path could take the same nextConfig treatment.

Correctness checks that passed

  • findVinextNextConfigInPlugins correctly skips the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig short-circuits on null), so no false match when no inline config is set.
  • loadBuildViteConfigMetadata is now called exactly once in buildApp() and the duplicate later call was removed — no double loadConfigFromFile in vinext build.
  • Deploy resolves inline config inside withCloudflareEnv(buildEnv, ...), and the moved cacheConfig load preserves the same env scoping it had when it lived inside populateKVCacheFromPrerenderedArtifacts. Verified by the resolves function-form inline config inside the selected Cloudflare environment test (CLOUDFLARE_ENV === "preview"output: "export", buildId: "preview").
  • run-prerender.ts buildId reuse still works: config is always a fresh object and readBuiltBuildId overrides config.buildId after the merge, in both the nextConfig and nextConfigOverride branches.
  • Both runPrerender call sites are covered — no other invocations need the override.

None of the three findings block merge. The change is well-scoped and the cli/deploy prerender parity is maintained.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

Reviewed the full diff (including follow-up commit 08d2bc7), ran the targeted tests (115 passing across deploy-prerender-config, prerender-config, prerender), and ran vp check on all changed source files (clean).

Verdict: no blocking issues. The core change correctly makes vinext build and vinext-cloudflare deploy honor inline vinext({ nextConfig }) for static-export mode, and threads the fully-resolved config into runPrerender instead of reloading disk config.

Three non-blocking findings I flagged:

  1. Undocumented behavioral regression — deploy now loads the Vite config unconditionally (removing the previous lazy-load-skip when --prerender-all or disk export mode already decided). Two guard tests using a throwing vite config were deleted with no replacement. The eager load is inherent to the feature, but the removed invariant deserves a comment at the call site.

  2. Async/sync flatten asymmetry — the new findVinextNextConfigInPlugins awaits promised plugins (with a test), but the three sibling finders called on the same plugins array are synchronous and silently drop config from Promise.resolve(vinext(...)). Not a regression, but a new inconsistency worth unifying.

  3. Redundant disk read still in prerender-paths.ts:375 — the same wasted loadNextConfig this PR removed from run-prerender.ts remains in emitPrerenderPathManifest. Result is correct, but the base load is dead work when the full override is passed (which both callers do).

Correctness checks that passed: null short-circuit in the finder, single loadConfigFromFile in buildApp(), withCloudflareEnv scoping preserved for the moved cacheConfig load, and buildId reuse intact in both config branches.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Reviewed the full diff through the latest commit fd695b6 (which the two prior bonk reviews predate), ran the targeted suites (deploy-prerender-config, prerender-config, prerender, prerender-paths, cache-adapters-config — 145 passing), and ran vp check on all 8 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The core change is correct and well-tested, and the follow-up commit fd695b6 resolves both non-blocking findings from the earlier reviews.

The two prior review findings are now addressed

  1. Async/sync flatten asymmetry (prior finding #2) — fixed. findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, and findVinextRouteRootConfigInPlugins are now all async with promise-aware flattenPluginOptions, matching findVinextNextConfigInPlugins. All 7 call sites (cli.ts, deploy.ts, and the loadVinext*FromViteConfig wrappers) now await them, and new tests cover promised plugin composition for cache, prerender, and route-root config. No remaining asymmetry.

  2. Redundant disk read in emitPrerenderPathManifest (prior finding #3) — fixed. prerender-paths.ts:380-385 now takes a full nextConfig and skips loadNextConfig entirely when supplied, falling back to disk-load + nextConfigOverride only when it isn't. Both cli.ts:706 and deploy.ts:881 pass the fully-resolved nextConfig, so the dead disk read is gone. New test does not reload disk config when supplied resolved config uses a throwing next.config.mjs to prove the load is skipped.

  3. Documented eager Vite-config load (prior finding #1). The loadDeployViteConfigMetadata call at deploy.ts:846-848 now carries a comment explaining why the load must be eager (inline nextConfig can decide export/prerender behavior). The consolidation to a single loadConfigFromFile per deploy is verified by the new loads Vite config once for all deploy metadata test (asserts a load-count of exactly 1).

Correctness checks that passed

  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig skips null), so no false match when inline config is unset.
  • flattenPluginOptions uses strict value instanceof Promise, so real Vite Plugin objects (which are not Promise instances) are never mis-awaited; only genuine Promise<Plugin> entries in PluginOption[] are resolved.
  • cli/deploy prerender parity is maintained: both resolve inline config before the output-mode decision (cli.ts:495-536, deploy.ts:851-867) and pass the same resolved nextConfig into runPrerender and emitPrerenderPathManifest.
  • buildId reuse is intact in both branches of run-prerender.ts and prerender-paths.ts (fresh object spread, then readBuiltBuildId override).
  • Deploy env scoping preserved: cacheConfig is loaded once inside withCloudflareEnv(buildEnv, ...), matching the scoping it had inside the old populateKVCacheFromPrerenderedArtifacts; the function-form inline config inside the selected Cloudflare environment test confirms CLOUDFLARE_ENV === "preview"output: "export", buildId: "preview".

Non-blocking observations (follow-up, not required for this PR)

  1. flattenPluginOptions is now triplicated. Identical async copies live in config/next-config.ts:359, config/prerender.ts:70, and cache/cache-adapters-virtual.ts:62. Worth extracting into one shared helper (e.g. under utils/) to avoid the three diverging.

  2. Hybrid Pages Router plugin extraction still uses sync flatten. The separate loadConfigFromFile at cli.ts:613 (which extracts user transform plugins for the hybrid SSR build) uses .flat(Infinity) and does not resolve promised plugins. This is unrelated to config discovery and pre-existing — not a regression — but it means a user who wraps their transform plugins in Promise.resolve(...) would have them silently dropped from the hybrid Pages build. Consider unifying with the new async flatten in a follow-up.

Neither observation blocks merge. The change is well-scoped, the cli/deploy parity holds, and the follow-up commit closed the open review items.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

Reviewed the full diff through the latest commit fd695b6 (which the two prior bonk reviews predate), ran the targeted suites (145 passing), and ran vp check on all 8 changed source files (clean).

Verdict: no blocking issues. The core change correctly makes vinext build and vinext-cloudflare deploy honor inline vinext({ nextConfig }) for static export, resolving it before output-mode decisions and threading the fully-resolved config into runPrerender / emitPrerenderPathManifest.

Key finding: the follow-up commit fd695b6 closed both open items from the prior reviews:

  1. Async/sync flatten asymmetry — now all four plugin finders are promise-aware, with tests for promised composition.
  2. Redundant disk read in emitPrerenderPathManifest — now skipped when full config is supplied (verified by a throwing-config test).
  3. Eager Vite-config load is now documented with a comment and covered by a single-load-count test.

Correctness checks passed: null short-circuit in the finder, strict instanceof Promise (no mis-awaiting of real Plugin objects), cli/deploy prerender parity, buildId reuse in both branches, and preserved withCloudflareEnv scoping for cacheConfig.

Two non-blocking follow-ups noted: flattenPluginOptions is now triplicated across three modules (worth extracting), and the unrelated hybrid Pages Router plugin-extraction path at cli.ts:613 still uses sync .flat(Infinity) (pre-existing, not a regression).

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Reviewed the full diff through the latest commit dfb36cc — which the three prior bonk reviews all predate (they stopped at fd695b6). Ran the targeted suites (deploy-prerender-config, prerender-config, prerender, prerender-paths, plugin-options, cache-adapters-config147 passing) and ran vp check on all 9 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The core fix is correct and well-tested: both vinext build and vinext-cloudflare deploy now resolve inline vinext({ nextConfig }) before the output-mode decision and thread the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

The new commit dfb36cc closes both remaining follow-ups from prior reviews

  1. flattenPluginOptions triplication (prior finding #1) — fixed. The three copies in config/next-config.ts, config/prerender.ts, and cache/cache-adapters-virtual.ts are removed and now import a single shared helper from utils/plugin-options.ts. The extracted version returns the array (rather than mutating a passed-in target), which is a cleaner signature; all call sites updated accordingly.

  2. Hybrid Pages Router sync flatten (prior finding #2) — fixed. cli.ts:613's previous .flat(Infinity) extraction is replaced by the new promise-aware selectHybridPagesUserPlugins, so users who wrap transform plugins in Promise.resolve(...) are no longer silently dropped from the hybrid Pages SSR build. The framework-plugin exclusion filter (vinext:, vite:react, rsc:, vite-rsc-load-module-dev-proxy, vite-plugin-cloudflare) is preserved verbatim, and a new test asserts both promised selection and family exclusion.

Correctness checks that passed

  • CLI build reordering is safe. loadBuildViteConfigMetadata moved from after the plugin setup (old line ~563) to before the output-mode decision (cli.ts:495), so resolvedNextConfig — and therefore __VINEXT_SHARED_BUILD_ID, outputMode, and the standalone/export branches — now correctly reflect inline nextConfig. Nothing between the old and new call position reads state the move invalidates. The duplicate later call was removed (no double loadConfigFromFile).
  • Deploy env scoping is preserved and consistent. loadDeployViteConfigMetadata (single loadConfigFromFile for all four metadata pieces) and the inline-config resolution are both wrapped in withCloudflareEnv(buildEnv, ...), so a function-form inline nextConfig that reads CLOUDFLARE_ENV is evaluated inside the selected env — verified by resolves function-form inline config inside the selected Cloudflare environment (CLOUDFLARE_ENV === "preview"output: "export", buildId: "preview"). buildEnv and the wranglerEnv expression evaluate to the same value, so the moved cacheConfig load keeps its original scoping.
  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig skips null), so no false match when inline config is unset.
  • flattenPluginOptions uses strict value instanceof Promise, so real Vite Plugin objects (not Promise instances) are never mis-awaited; only genuine Promise<Plugin> entries are resolved. Ordering is preserved (Promise.all(...).flat()).
  • Redundant disk read is gone from both prerender paths. run-prerender.ts:190 and prerender-paths.ts:380 skip loadNextConfig entirely when the resolved nextConfig is supplied (which both cli and deploy now do), falling back to disk-load + nextConfigOverride only for the legacy test path. buildId reuse via readBuiltBuildId is intact in both branches. does not reload disk config when supplied resolved config proves the skip with a throwing next.config.
  • Inline function config gets defaultConfig: {} and PHASE_PRODUCTION_BUILD via resolveNextConfigInputresolveConfigValue, matching how disk-config functions are resolved.
  • Both runPrerender call sites (cli + deploy) are covered — no missed invocations.

Non-blocking observations (follow-up, not required for this PR)

  1. cacheConfig is now resolved eagerly even when there is no cache adapter. populateKVCacheFromPrerenderedArtifacts still guards on viteConfigHasCacheAdapter(root) and returns early, but viteConfigMetadata.cacheConfig is always computed up front now. This is pure wasted parsing (no correctness impact), and it's more than offset by the consolidation to a single loadConfigFromFile per deploy — just noting the resolved-but-unused case.

  2. Two --prerender-all / disk-export guard tests were deleted with no replacement. The previous deploy path lazily skipped loading the Vite config when the prerender decision was already settled; the tests using a throwing vite.config.ts to prove that (does not load Vite config when the prerender-all flag already wins / ...when static export already wins) are gone. The eager load is inherent to the feature — you can't skip loading the config you must read inline nextConfig from — and the new deploy.ts:846 comment plus the loads Vite config once for all deploy metadata load-count test capture the new invariant. So this is acceptable as-is; flagging only that the removed invariant is no longer asserted in the negative direction (a broken vite.config.ts will now fail a --prerender-all deploy that previously succeeded).

Neither observation blocks merge. The change is well-scoped, cli/deploy parity is maintained, and the latest refactor commit closed the open follow-ups from the earlier reviews.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

Reviewed the full diff through the latest commit dfb36cc — which all three prior bonk reviews predate (they stopped at fd695b6). Ran the targeted suites (147 passing) and vp check on all 9 changed source files (clean).

Verdict: no blocking issues. The fix correctly makes vinext build and vinext-cloudflare deploy honor inline vinext({ nextConfig }) by resolving it before the output-mode decision and threading the resolved config into the prerender runners instead of reloading disk config.

Key finding: the new commit dfb36cc (refactor) closes both remaining follow-ups the prior reviews left open:

  1. flattenPluginOptions triplication — extracted to a shared utils/plugin-options.ts helper.
  2. Hybrid Pages Router sync .flat(Infinity) — replaced with the promise-aware selectHybridPagesUserPlugins, preserving the framework-plugin exclusion filter, with new tests.

Correctness verified: CLI build reordering is safe (inline config now flows into outputMode/__VINEXT_SHARED_BUILD_ID), deploy env scoping preserved for function-form inline config inside withCloudflareEnv, null short-circuit in the finder, strict instanceof Promise avoids mis-awaiting real Plugin objects, redundant disk read removed from both prerender paths with buildId reuse intact, and both runPrerender call sites covered.

Two non-blocking observations: cacheConfig is now resolved eagerly even without a cache adapter (harmless, offset by single-load consolidation), and two --prerender-all/disk-export guard tests were deleted — acceptable since the eager load is inherent to the feature and documented with a comment + load-count test.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Independently reviewed the full diff through f011d06, ran the targeted suites (deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config58 passing), and ran vp check on all 9 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The fix correctly makes vinext build and vinext-cloudflare deploy resolve inline vinext({ nextConfig }) before the output-mode decision and thread the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

Correctness checks that passed

  • Dev/build/deploy parity on inline-vs-disk semantics. The plugin's config hook (index.ts:1848) treats inline nextConfig as a full override of disk config (with a drift warning). The new build (cli.ts:496) and deploy (deploy.ts) paths now match this exactly: inline present → resolveNextConfigInput, else loadNextConfig. No accidental merge, so no divergence from the existing runtime behavior.
  • Function-form parity. Inline function configs resolve through the same resolveConfigValue(config, PHASE_PRODUCTION_BUILD) with defaultConfig: {} used for disk function configs — verified by the resolves function-form inline config inside the selected Cloudflare environment test (CLOUDFLARE_ENV === "preview"output: "export", buildId: "preview").
  • CLI reorder is safe. loadBuildViteConfigMetadata moved ahead of the output-mode decision; the only later consumer of its result (emptyOutDir at cli.ts:578, routeRootConfig at cli.ts:690) still runs after the new call site. Duplicate later call removed — no double loadConfigFromFile.
  • Deploy env scoping preserved. loadDeployViteConfigMetadata and the inline-config resolution are both wrapped in withCloudflareEnv(buildEnv, ...), and the moved cacheConfig load keeps the same env scoping it had inside populateKVCacheFromPrerenderedArtifacts. The cache double-guard (viteConfigHasCacheAdapter(root) in both loader and consumer) stays consistent.
  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig skips null), so no false match when inline config is unset.
  • flattenPluginOptions uses strict value instanceof Promise, so real Vite Plugin objects are never mis-awaited; only genuine Promise<Plugin> entries resolve, and ordering is preserved (Promise.all(...).flat()). All four plugin finders are now uniformly async — no remaining sync/async asymmetry.
  • Redundant disk read removed from both prerender paths (run-prerender.ts:190, prerender-paths.ts:380); buildId reuse via readBuiltBuildId intact in both branches. No caller passes both nextConfig and nextConfigOverride, so the two-field precedence is never ambiguous at runtime.
  • Deleted --prerender-all/disk-export guard tests were replaced, not dropped: the old "does not load Vite config when …" throwing-config tests became positive "loads Vite config even when …" assertions, plus a loads Vite config once for all deploy metadata load-count test. The new eager load is documented at the loadDeployViteConfigMetadata call site. Correctly codifies the new invariant.

Non-blocking observations (follow-up, not required)

  1. EmitPrerenderPathManifestOptions lacks JSDoc on nextConfig vs nextConfigOverride precedence. RunPrerenderOptions documents both fields and their merge semantics; the parallel prerender-paths.ts:38 type does not, even though the nextConfig-wins-over-nextConfigOverride behavior is identical. Worth a short comment for the next reader.

  2. cacheConfig guard is duplicated. loadDeployViteConfigMetadata computes it only when viteConfigHasCacheAdapter(root), and populateKVCacheFromPrerenderedArtifacts re-checks the same predicate before using it. Harmless (and it keeps the consumer self-contained), but the two guards must stay in sync if either predicate changes.

Neither observation blocks merge. The change is well-scoped, cli/deploy parity holds, and the follow-up commits closed every substantive item from the earlier reviews.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

I independently reviewed the full diff through f011d06, ran the targeted suites (58 passing across deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config), and ran vp check on all 9 changed source files (clean).

Verdict: no blocking issues. The fix correctly resolves inline vinext({ nextConfig }) before the output-mode decision in both vinext build and vinext-cloudflare deploy, and threads the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

Key correctness findings verified:

  • Dev/build/deploy parity — the build and deploy paths now match the plugin's config hook: inline nextConfig fully overrides disk config (via resolveNextConfigInput), not merges. No accidental divergence from existing runtime behavior.
  • Function-form parity — inline function configs resolve with the same defaultConfig: {} + PHASE_PRODUCTION_BUILD as disk configs, and are evaluated inside the correct withCloudflareEnv scope.
  • CLI reorder is safe — the moved loadBuildViteConfigMetadata still runs before its only downstream consumers (emptyOutDir, routeRootConfig); duplicate call removed.
  • Deploy env scoping preserved, null short-circuit in the finder, strict instanceof Promise avoids mis-awaiting real Plugin objects, redundant disk reads removed from both prerender paths with buildId reuse intact.
  • Deleted guard tests were replaced (throwing-config negatives → positive "loads Vite config even when…" assertions + a load-count test), correctly codifying the new eager-load invariant.

Two non-blocking follow-ups noted: EmitPrerenderPathManifestOptions lacks JSDoc on the nextConfig-vs-nextConfigOverride precedence (its sibling RunPrerenderOptions documents both), and the cacheConfig viteConfigHasCacheAdapter guard is duplicated across loader and consumer (must stay in sync).

The prior bonk reviews' substantive observations were all addressed in the follow-up commits; my review confirms the current state is correct and complete.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Independently reviewed the full diff through the latest commit 5ad66ff (refactor(deploy): use resolved cache metadata) — which all six prior bonk reviews predate (they stopped at f011d06). Ran the targeted suites (deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config58 passing; prerender91 passing) and ran vp check on all 9 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The fix correctly resolves inline vinext({ nextConfig }) before the output-mode decision in both vinext build and vinext-cloudflare deploy, and threads the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

The new commit 5ad66ff is safe

The commit does two things, both verified correct:

  1. Removed the viteConfigHasCacheAdapter(root) early-return from populateKVCacheFromPrerenderedArtifacts (deploy.ts:307). This is safe: the guard now lives solely in loadDeployViteConfigMetadata (deploy.ts:279), which sets cacheConfig: null when no adapter is detected. The consumer's resolveKvDataAdapterConfig(null) returns null (deploy-config.ts:139-140), preserving the exact early-return behavior. The root param is still used by buildPrerenderKVPairs, so no dead argument. The net effect is that the same text-scan guard is applied once (at metadata-load time) instead of twice.

  2. Added JSDoc to EmitPrerenderPathManifestOptions distinguishing nextConfig (fully resolved, wins) from nextConfigOverride (merged on disk config, ignored when nextConfig is supplied) — this closes the exact doc-parity follow-up flagged in the prior review.

Correctness checks that passed

  • CLI reorder is safe. loadBuildViteConfigMetadata moved ahead of the output-mode decision (cli.ts:495); its only later consumer, emptyOutDir at cli.ts:578, still runs after the new call site, and the duplicate later call was removed — no double loadConfigFromFile. Inline nextConfig now correctly flows into __VINEXT_SHARED_BUILD_ID, outputMode, and the standalone/export branches.
  • Deploy env scoping preserved. loadDeployViteConfigMetadata and the inline-config resolution are both wrapped in withCloudflareEnv(buildEnv, ...) (deploy.ts:848, deploy.ts:851). The finder only extracts the raw function reference; the actual function evaluation happens in resolveNextConfigInput inside the second withCloudflareEnv, so function-form configs reading CLOUDFLARE_ENV resolve in the correct env (verified by the function-form inline config inside the selected Cloudflare environment test).
  • Resolve pipeline parity. Both cli.ts:496-499 and deploy.ts:851-857 feed inline config through resolveNextConfigInputresolveNextConfig, producing the same ResolvedNextConfig shape as the disk path. resolveNextConfigInput correctly does not treat a default key specially (inline receives the value, not a module namespace).
  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig skips null), so no false match when inline config is unset. Attached to the vinext:config plugin object (index.ts:1738).
  • flattenPluginOptions uses strict value instanceof Promise, so real Vite Plugin objects are never mis-awaited; ordering preserved via Promise.all(...).flat(). All four plugin finders are uniformly async — no sync/async asymmetry remains.
  • Redundant disk read removed from both prerender paths (run-prerender.ts:190, prerender-paths.ts:385); buildId reuse via readBuiltBuildId intact in both branches. No production caller passes both nextConfig and nextConfigOverride, so the precedence is never ambiguous at runtime; legacy test paths that pass neither still fall back to disk load correctly.

Non-blocking observations (follow-up, not required for this PR)

  1. Four load*FromViteConfig wrappers are now dead code. loadVinextNextConfigFromViteConfig (next-config.ts:374), loadVinextCacheConfigFromViteConfig (cache-adapters-virtual.ts:77), loadVinextPrerenderConfigFromViteConfig and loadVinextRouteRootConfigFromViteConfig (prerender.ts) are no longer referenced by any production code or test — deploy now uses the batched loadDeployViteConfigMetadata, and cli uses loadBuildViteConfigMetadata. They remain exported (and thus in the internal surface). Worth removing in a follow-up to avoid leaving three single-load helpers alongside the new consolidated loaders.

  2. cacheConfig guard is duplicated across load and consume. loadDeployViteConfigMetadata computes cacheConfig only when viteConfigHasCacheAdapter(root), and after 5ad66ff the consumer relies entirely on cacheConfig being null in that case. This is correct today, but the two must stay in sync — if a future change ever populates cacheConfig without the text-scan guard, the removed consumer-side guard means KV upload would run unconditionally. A one-line comment at populateKVCacheFromPrerenderedArtifacts noting it now relies on the loader's guard would make the coupling explicit.

Neither observation blocks merge. The change is well-scoped, cli/deploy parity holds, and the latest commit 5ad66ff is a clean, correct simplification.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

I independently reviewed the full diff through the latest commit 5ad66ff — which all six prior bonk reviews predate (they stopped at f011d06). Ran the targeted suites (58 + 91 passing) and vp check on all 9 changed source files (clean).

Verdict: no blocking issues.

Key finding — the new commit 5ad66ff is a safe simplification:

  1. Removed the viteConfigHasCacheAdapter(root) early-return from populateKVCacheFromPrerenderedArtifacts. Verified safe: the guard now lives only in loadDeployViteConfigMetadata, and resolveKvDataAdapterConfig(null) returns null, preserving the exact early-return behavior. The guard is applied once instead of twice.
  2. Added JSDoc distinguishing nextConfig (resolved, wins) from nextConfigOverride (merged, ignored when nextConfig present) — closes the doc-parity follow-up from the prior review.

Correctness verified: CLI reorder is safe (inline config flows into build ID / output mode), deploy env scoping preserved for function-form config inside withCloudflareEnv, resolve-pipeline parity between build/deploy/disk paths, null short-circuit in the finder, strict instanceof Promise avoids mis-awaiting real Plugin objects, redundant disk reads removed from both prerender paths with buildId reuse intact, and no runtime caller passes both nextConfig + nextConfigOverride.

Two non-blocking follow-ups:

  1. Four load*FromViteConfig wrappers are now dead code (unused in prod and tests) — worth removing.
  2. The cacheConfig text-scan guard is now single-sided (loader only); a comment at the consumer noting it relies on the loader's guard would make the coupling explicit.

The prior reviews' substantive items were all addressed in follow-up commits; my review confirms the current state (including the previously-unreviewed 5ad66ff) is correct and complete.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Independently reviewed the full diff through the latest commit 6aa8da3 (refactor(config): remove unused inline loader) — which all seven prior bonk reviews predate (they stopped at 5ad66ff). Ran the targeted suites (deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config58 passing; prerender91 passing) and ran vp check on all 9 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The fix correctly resolves inline vinext({ nextConfig }) before the output-mode decision in both vinext build and vinext-cloudflare deploy, and threads the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

The new commit 6aa8da3 is a clean cleanup

  1. Removed the now-dead loadVinextNextConfigFromViteConfig wrapper (next-config.ts) and its private ViteConfigLoader type. Verified safe: it had no remaining production caller or test — build uses loadBuildViteConfigMetadata and deploy uses loadDeployViteConfigMetadata, both of which call findVinextNextConfigInPlugins directly. This closes half of the "four dead wrappers" follow-up from the prior review.
  2. Added a coupling comment at populateKVCacheFromPrerenderedArtifacts (deploy.ts:312) noting that loadDeployViteConfigMetadata returns null cacheConfig unless a cache adapter is declared — this makes the single-sided viteConfigHasCacheAdapter guard explicit at the consumer, closing the exact coupling follow-up flagged in the prior review.

Correctness checks that passed

  • CLI reorder is safe. loadBuildViteConfigMetadata runs ahead of the output-mode decision (cli.ts:495); its only later consumers (emptyOutDir, outputMode, __VINEXT_SHARED_BUILD_ID, standalone/export branches) all read the now-inline-aware resolvedNextConfig. No duplicate loadConfigFromFile — the previous second call was removed.
  • Deploy env scoping preserved. Both loadDeployViteConfigMetadata (line 849) and inline-config resolution (line 852) are wrapped in withCloudflareEnv(buildEnv, ...). The finder extracts only the raw function reference; the function is actually invoked by resolveNextConfigInput inside the second withCloudflareEnv, so function-form configs reading CLOUDFLARE_ENV resolve in the correct env (verified by the function-form inline config inside the selected Cloudflare environment test). buildEnv (line 846) and the cache-upload env expression (line 910) evaluate identically, so the moved cacheConfig load keeps its original scoping.
  • Resolve-pipeline parity. Build (cli.ts:496-499) and deploy (deploy.ts:852-857) both feed inline config through resolveNextConfigInputresolveNextConfig, producing the same ResolvedNextConfig as the disk path. Inline is treated as a full override (matching the plugin's config hook), not a merge — no accidental dev/build divergence. resolveNextConfigInput correctly does not special-case a default key (inline receives the value, not a module namespace).
  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (if (nextConfig) return nextConfig skips null), so no false match when inline config is unset. Attached to the vinext:config plugin object (index.ts:1738).
  • flattenPluginOptions uses strict value instanceof Promise, so real Vite Plugin objects are never mis-awaited; ordering is preserved via Promise.all(...).flat(). All four plugin finders are uniformly async — no sync/async asymmetry remains.
  • Redundant disk read removed from both prerender paths (run-prerender.ts:190, prerender-paths.ts:385); each skips loadNextConfig when the resolved nextConfig is supplied (both cli and deploy do), falling back to disk-load + nextConfigOverride only for the legacy test path. buildId reuse via readBuiltBuildId is intact in both branches; the throwing-next.config test proves the skip. No production caller passes both nextConfig and nextConfigOverride, so the documented precedence is never ambiguous at runtime.
  • Deleted --prerender-all/disk-export guard tests were replaced, not dropped — the throwing-config negatives became positive "loads Vite config even when…" assertions plus a loads Vite config once for all deploy metadata load-count test. The eager load is documented at the loadDeployViteConfigMetadata call site (deploy.ts:847-848).

Non-blocking observations (follow-up, not required for this PR)

  1. Three load*FromViteConfig wrappers remain dead code. 6aa8da3 removed loadVinextNextConfigFromViteConfig, but loadVinextCacheConfigFromViteConfig (cache-adapters-virtual.ts:77), loadVinextPrerenderConfigFromViteConfig and loadVinextRouteRootConfigFromViteConfig (prerender.ts:103,115) have no remaining production caller or test (confirmed by grep — only dist/ type declarations reference them). Since one sibling was just removed for exactly this reason, the other three are worth removing in the same follow-up for consistency; leaving them creates the appearance that three single-load helpers still coexist with the consolidated loadDeployViteConfigMetadata.

  2. Hybrid Pages Router extraction at cli.ts:613 is now the only sync flatten path. It was migrated to the promise-aware selectHybridPagesUserPlugins in an earlier commit, so this is already addressed — noting only that it and the config finders now share utils/plugin-options.ts, so any future change to promise-resolution semantics lands in one place.

Both observations are cosmetic dead-code cleanup and do not affect behavior. The change is well-scoped, cli/deploy/disk parity holds, and the latest commit 6aa8da3 is a correct, low-risk cleanup that closed the coupling and one of the dead-wrapper follow-ups from the prior review.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

I independently reviewed the full diff through the latest commit 6aa8da3 (refactor(config): remove unused inline loader) — which all seven prior bonk reviews predate (they stopped at 5ad66ff). Ran the targeted suites (58 + 91 passing) and vp check on all 9 changed source files (clean).

Verdict: no blocking issues.

The new commit 6aa8da3 is a clean cleanup, verified safe:

  1. Removed the now-dead loadVinextNextConfigFromViteConfig wrapper — no production caller or test remained; build/deploy call findVinextNextConfigInPlugins directly.
  2. Added a coupling comment at populateKVCacheFromPrerenderedArtifacts making the single-sided viteConfigHasCacheAdapter guard explicit — closes the exact follow-up from the prior review.

Correctness verified: CLI reorder is safe (inline config flows into build ID / output mode), deploy env scoping preserved for function-form config inside withCloudflareEnv, resolve-pipeline parity between build/deploy/disk, null short-circuit in the finder, strict instanceof Promise avoids mis-awaiting real Plugin objects, redundant disk reads removed from both prerender paths with buildId reuse intact, deleted guard tests replaced with positive assertions + load-count test.

One non-blocking follow-up: three sibling load*FromViteConfig wrappers (cache/prerender/routeRoot) are still dead code — 6aa8da3 removed only the fourth (next-config) for exactly this reason, so removing the remaining three in the same follow-up would be consistent.

The change is well-scoped and cli/deploy/disk parity holds.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: honor inline next config for static export

Independently reviewed the full diff through the latest commit 08b979d (test(config): preserve internal metadata loaders) — which all eight prior bonk reviews predate (they stopped at 6aa8da3). Ran the targeted suites (deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config60 passing) and ran vp check on all 9 changed source files (format/lint/type — clean).

Verdict: no blocking issues. The fix correctly resolves inline vinext({ nextConfig }) before the output-mode decision in both vinext build and vinext-cloudflare deploy, and threads the fully-resolved config into runPrerender / emitPrerenderPathManifest instead of reloading disk config.

The new commit 08b979d is a clean test-only addition

The single previous review's outstanding follow-up — "three load*FromViteConfig wrappers are dead code" — is resolved by deciding to keep them as a tested internal surface rather than deleting them:

  • tests/cache-adapters-config.test.ts now covers loadVinextCacheConfigFromViteConfig end-to-end with a promised plugin, and
  • tests/prerender-config.test.ts now covers loadVinextPrerenderConfigFromViteConfig and loadVinextRouteRootConfigFromViteConfig with promised plugin composition.

Both tests assert the promise-aware path resolves correctly through the wrapper, which is a legitimate reason to keep the wrappers exported. No source change in this commit, so no new correctness risk.

Correctness checks that passed

  • CLI reorder is safe. loadBuildViteConfigMetadata runs ahead of the output-mode decision (cli.ts:495); inline nextConfig now flows into __VINEXT_SHARED_BUILD_ID, outputMode (cli.ts:537), and the standalone/export branches. Its only later consumer, emptyOutDir at cli.ts:578, still runs after the new call site. The duplicate later loadBuildViteConfigMetadata call was removed — no double loadConfigFromFile.
  • Resolve-pipeline parity with the plugin runtime. Build (cli.ts:496-499) and deploy (deploy.ts:852-857) feed inline config through resolveNextConfigInputresolveNextConfig, matching the plugin's config hook (index.ts:1856), which treats inline nextConfig as a full override of disk config (with the drift warning), not a merge. No accidental dev/build divergence. resolveNextConfigInput correctly does not special-case a default key (inline receives the value, not a module namespace — next-config.ts:931-933), and function-form inline configs resolve via resolveConfigValue with the same defaultConfig: {} + PHASE_PRODUCTION_BUILD as disk function configs.
  • Deploy env scoping preserved. Both loadDeployViteConfigMetadata (deploy.ts:849) and inline-config resolution (deploy.ts:852) are wrapped in withCloudflareEnv(buildEnv, ...). The finder extracts only the raw function reference; the function is invoked by resolveNextConfigInput inside the second withCloudflareEnv, so function-form configs reading CLOUDFLARE_ENV resolve in the correct env (verified by resolves function-form inline config inside the selected Cloudflare environmentCLOUDFLARE_ENV === "preview" yields output: "export", buildId: "preview"). buildEnv (deploy.ts:846) and the cache-upload env expression (deploy.ts:910) evaluate identically, so the moved cacheConfig load keeps its original scoping.
  • Cache guard coupling is correct. viteConfigHasCacheAdapter is a pure text-scan of the vite config file (deploy-config.ts:97-111), so it is env-independent and safe to call inside loadDeployViteConfigMetadata. The single-sided guard (loader sets cacheConfig: null when no adapter) is safe: resolveKvDataAdapterConfig(null) returns null (deploy-config.ts:139-140), preserving the exact early-return the consumer previously performed. The coupling comment at deploy.ts:312 documents the invariant.
  • findVinextNextConfigInPlugins short-circuits on the always-present __vinextNextConfig: options.nextConfig ?? null (next-config.ts:364if (nextConfig) return nextConfig skips null), so no false match when inline config is unset. The property is attached in the plugin object at index.ts:1738.
  • flattenPluginOptions uses strict value instanceof Promise (utils/plugin-options.ts:2), so real Vite Plugin objects (not Promise instances) are never mis-awaited; only genuine Promise<Plugin> entries resolve, and ordering is preserved via Promise.all(...).flat(). All four plugin finders are uniformly async — no sync/async asymmetry remains. The hybrid Pages Router extraction now shares the same helper via selectHybridPagesUserPlugins, preserving the framework-plugin exclusion filter verbatim.
  • Redundant disk read removed from both prerender paths. run-prerender.ts:190 and prerender-paths.ts:385 skip loadNextConfig when a resolved nextConfig is supplied (which both cli and deploy do), falling back to disk-load + nextConfigOverride only for the legacy test path. buildId reuse via readBuiltBuildId is intact in both branches (fresh object spread, then override). No production caller passes both nextConfig and nextConfigOverride, so the documented precedence (nextConfig wins) is never ambiguous at runtime.
  • Both runPrerender call sites (cli + deploy) covered — no missed invocations.

Non-blocking observations (follow-up, not required for this PR)

  1. The nextConfig-vs-nextConfigOverride two-field API exists only for the legacy test path. RunPrerenderOptions and EmitPrerenderPathManifestOptions both carry nextConfig (production) and nextConfigOverride (tests, disk-merge). All production callers pass only nextConfig; only tests pass nextConfigOverride. This is documented via JSDoc, so it's fine, but a future cleanup could collapse the test path onto nextConfig to remove the dual-field surface entirely.
  2. Two --prerender-all/disk-export guard tests were replaced, not restored. The old throwing-vite.config.ts negatives became positive "loads Vite config even when…" assertions plus the loads Vite config once for all deploy metadata load-count test. This correctly codifies the new eager-load invariant (a broken vite.config.ts now fails a --prerender-all deploy that previously succeeded), which is inherent to the feature and documented at deploy.ts:847-848. Flagging only that the behavioral change is intentional and captured.

The prior reviews' substantive items were all addressed across the follow-up commits, and the latest 08b979d closes the final dead-code follow-up by keeping and testing the internal loaders. The change is well-scoped, cli/deploy/disk parity holds, and I found no correctness issues.

@ask-bonk

ask-bonk Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2543.

Summary

I independently reviewed the full diff through the latest commit 08b979d — which all eight prior bonk reviews predate (they stopped at 6aa8da3). Ran the targeted suites (60 passing across deploy-prerender-config, prerender-config, prerender-paths, plugin-options, cache-adapters-config) and vp check on all 9 changed source files (clean).

Verdict: no blocking issues.

The new commit 08b979d is a clean test-only addition — it resolves the prior review's "three dead load*FromViteConfig wrappers" follow-up by deciding to keep them as a tested internal surface (adding promise-aware coverage) rather than deleting them. No source change, no new risk.

Correctness verified:

  • CLI reorder is safe — inline nextConfig flows into build ID / output mode; duplicate loadConfigFromFile removed.
  • Build/deploy/disk resolve-pipeline parity — inline is treated as a full override (matching the plugin's config hook), not a merge.
  • Deploy env scoping preserved for function-form config inside withCloudflareEnv; text-scan cache guard is env-independent and correctly single-sided.
  • null short-circuit in the finder, strict instanceof Promise avoids mis-awaiting real Plugin objects, all four finders uniformly async.
  • Redundant disk reads removed from both prerender paths with buildId reuse intact; both runPrerender call sites covered.

Two non-blocking follow-ups noted: the nextConfig/nextConfigOverride dual-field API now exists only for the legacy test path (could be collapsed later), and the deleted --prerender-all/disk-export guard tests were replaced with positive load-count assertions codifying the intentional eager-load behavior change.

The change is well-scoped and I found no correctness issues.

github run

Comment thread packages/vinext/src/utils/plugin-options.ts Outdated
@james-elicx
james-elicx merged commit 753c229 into main Jul 6, 2026
64 checks passed
@james-elicx
james-elicx deleted the codex/fix-inline-next-config-export branch July 6, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

output: "export" requires next.config.ts; vinext({ nextConfig }) does not trigger static export

2 participants