Skip to content

feat(prerender): usability enhancements for pre-rendering (#565)#732

Draft
raed04 wants to merge 1 commit intocloudflare:mainfrom
raed04:feat/prerender-usability-565
Draft

feat(prerender): usability enhancements for pre-rendering (#565)#732
raed04 wants to merge 1 commit intocloudflare:mainfrom
raed04:feat/prerender-usability-565

Conversation

@raed04
Copy link
Copy Markdown
Contributor

@raed04 raed04 commented Apr 1, 2026

Summary

Closes #565 — Pre-rendering usability enhancements.

  • Improved progress bar: Shows phase labels (App Router / Pages Router), elapsed time, render rate (routes/s), and real-time status breakdown (rendered/skipped/errors)
  • Better route classification: Detects dynamic API imports (headers, cookies, draftMode from next/headers; connection from next/server; unstable_noStore/noStore from next/cache) via static analysis — routes importing these are classified as ssr instead of unknown
  • Prerender info in build report: Routes show [prerendered], [prerendered: N paths], [skipped], or [error] annotations; prerender summary section added
  • Public API for external control: runPrerender, PrerenderProgress, compileRouteGlob, matchRouteGlob and types exported from package entry point
  • Route filtering: includeRoutes/excludeRoutes glob patterns (* and **) with compiled regex caching
  • Dry-run mode: Scan and classify routes without rendering — shows what would be prerendered
  • External progress callback: onProgress option for programmatic tracking alongside built-in TTY progress bar

Files changed

File Changes
packages/vinext/src/build/report.ts Dynamic API detection, prerender annotations in report, SYMBOLS export
packages/vinext/src/build/run-prerender.ts Enhanced progress bar, route filtering, dry-run, public API
packages/vinext/src/index.ts New public exports
tests/build-report.test.ts 120 tests (44 new) covering all new features

Test plan

  • 120/120 unit tests pass (44 new tests added)
  • Full test suite: 3733 pass — same pre-existing failures as main, zero regressions
  • Lint (oxlint): 0 warnings, 0 errors
  • Formatting: all modified files clean
  • Build: successful
  • Backward compatible — all new type fields optional, no breaking API changes
  • classifyAppRoute behavioral change documented: routes importing dynamic APIs now return ssr instead of unknown (more accurate, errs toward restrictive)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 1, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@732

commit: e7a7c9a

@raed04 raed04 force-pushed the feat/prerender-usability-565 branch 2 times, most recently from 6d4ba1b to dda07d1 Compare April 1, 2026 02:57
@raed04
Copy link
Copy Markdown
Contributor Author

raed04 commented Apr 1, 2026

Added: KV cache seeding at deploy time (closes #562)

This commit adds automatic KV cache population during vinext deploy. After pre-rendering completes, pre-rendered HTML/RSC files are uploaded to Workers KV via the Cloudflare bulk REST API so pages are cache HITs from the first request.

How it works

  • Step 6c in deploy pipeline (after prerender, before wrangler deploy)
  • Automatic when CLOUDFLARE_API_TOKEN is set and VINEXT_CACHE KV namespace exists
  • Opt-out via --no-seed-cache
  • Non-fatal: failures log a warning but don't block deployment

Key details

  • Correct cache key format: cache:app:<buildId>:<pathname>:html (matches runtime KVCacheHandler.get())
  • RSC data is base64-encoded to match KVCacheHandler serialization
  • Static routes omit expiration_ttl (matching runtime behavior — no expiry)
  • ISR routes get 10x revalidate TTL clamped to [60s, 30d]
  • Uploads in batches of 10,000 via Cloudflare KV bulk REST API

Security

  • Path traversal guard on manifest-controlled file paths
  • TOCTOU-safe file reads (try/catch, no existsSync)
  • Error messages capped to prevent sensitive data leakage
  • Checks Cloudflare API success: false responses

Tests

27 new unit tests covering key format, binary RSC, TTL clamping, unicode paths, batch splitting, API errors, and mixed manifests. All 213 tests pass.

Files

  • New: packages/vinext/src/cloudflare/seed-kv-cache.ts (300 lines)
  • New: tests/seed-kv-cache.test.ts (27 tests)
  • Modified: deploy.ts (+40 lines), cli.ts (+1 line)

@raed04 raed04 force-pushed the feat/prerender-usability-565 branch from b6f5aee to dda07d1 Compare April 1, 2026 03:40
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Solid PR — the feature set is coherent, the test coverage is thorough (44 new tests), and the implementation choices are sensible. A few things worth addressing before merge.

Summary of findings:

  • One false-positive edge case in the dynamic API regex (multi-line block comments)
  • Naming nit: detectsDynamicApiUsage reads as a description, not a function name
  • The PrerenderProgress class tracks internal counters that duplicate the caller's counters — minor but confusing
  • Dry-run mode doesn't classify unknown routes distinctly in its output (intentional? worth a note)
  • The public API exports look good and the types are well-chosen


/** Detects `import { headers | cookies | draftMode } from "next/headers"` */
const DYNAMIC_HEADERS_RE =
/^\s*import\s*\{[^}]*(?<!\btype\s)\b(?:headers|cookies|draftMode)\b[^}]*\}\s*from\s*['"]next\/headers['"]/m;
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.

The ^ anchor with /m correctly skips // import { headers } ... (single-line comments) because the // precedes import. However, it produces false positives for imports inside multi-line block comments that start on a prior line:

const x = 1; /*
import { headers } from "next/headers";
*/

Here import starts at the beginning of a line (matching ^ in multiline mode), but the code is dead.

This is an inherent limitation of regex-based analysis and the JSDoc already documents the heuristic nature, so it's fine as-is for a build report. But worth adding to the "Limitations" comment block so future readers know.

* - Does not detect `type` imports (which are erased at runtime — correct behavior)
* - Anchored to line start to avoid false positives from commented-out imports
*/
export function detectsDynamicApiUsage(code: string): boolean {
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.

Naming nit: detectsDynamicApiUsage reads like a property/descriptor ("this function detects...") rather than an imperative. The rest of the codebase uses imperative names (extractExportConstString, hasNamedExport, classifyAppRoute). Consider detectDynamicApiUsage or hasDynamicApiImport for consistency.

Not a blocker since this is exported and already referenced in tests, but worth considering if you're touching it again.

route: string,
status?: "rendered" | "skipped" | "error",
): void {
if (status === "rendered") this.rendered++;
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.

PrerenderProgress tracks rendered, skipped, and errors internally via update(), but finish() ignores these and takes its own rendered/skipped/errors parameters computed by the caller from allRoutes. The internal counters are only used for the live progress bar display.

This works correctly, but the duplication is a subtle maintenance trap — if someone later changes finish() to use the internal counters (since they exist), the counts could diverge (e.g., if update() is called without a status, the internal counters undercount).

Consider either:

  1. Making finish() use the internal counters and drop the parameters, or
  2. Removing the internal counters and passing status breakdown to update() externally

Not blocking, but worth a note.

for (const route of routes) {
const { type } = classifyAppRoute(route.pagePath, route.routePath, route.isDynamic);
const sym = SYMBOLS[type] ?? "?";
const action = type === "api" || type === "ssr" ? "skip" : "render";
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.

In dry-run mode, routes classified as unknown get action = "render", which matches the actual prerender behavior (speculative rendering). But the output → render might be confusing to users — the route might fail at render time since it couldn't be statically classified.

Consider distinguishing this in the dry-run output, e.g., → render (speculative) or using the ? symbol's meaning to hint that the outcome is uncertain.

export function compileRouteGlob(pattern: string): RegExp {
// Use a Unicode placeholder that won't appear in route patterns to
// distinguish ** from * during the replacement chain.
const DOUBLE_STAR = "\uFFFF";
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.

Minor: \uFFFF is a valid (though rare) Unicode character. If a route pattern ever contained it, this would produce incorrect regex output. A two-character sequence like \uFFFE\uFFFF or a longer sentinel would be safer, though in practice route patterns are ASCII so this is purely theoretical.

The approach is clean and the comment explains the rationale well.

completedUrls += 1;
progress.update(completedUrls, totalUrls, route);
progress.update(completedUrls, totalUrls, route, status);
try {
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.

Good defensive error handling on the onProgress callback. One thought: the error is written to process.stderr which is fine, but in a non-TTY context (CI), this message could be interleaved with other output. Consider logging it once and then suppressing subsequent callback errors for the same route (or using a flag to log once total) to avoid flooding stderr if the callback is consistently broken.

export { stripServerExports as _stripServerExports };
export { asyncHooksStubPlugin as _asyncHooksStubPlugin };

// Public prerender API
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.

The existing exports above this block use underscore-prefixed names (_parseStaticObjectLiteral, _stripServerExports) to signal internal/unstable APIs. The new prerender exports are un-prefixed, signaling they're public API.

This is intentional per the PR description — just confirming: are these APIs considered stable? Once external consumers depend on runPrerender, compileRouteGlob, etc., changing the signatures is a breaking change. If there's any uncertainty, consider _runPrerender to give room for iteration.

});

// ── Edge cases ──────────────────────────────────────────────────────────────

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.

Good test. One subtle gap: the // comment test passes because // import doesn't start with import after ^. But this test doesn't cover the false positive case of multi-line block comments:

// This would be a false positive:
const x = 1; /*
import { headers } from "next/headers";
*/

Worth adding as a documented known-limitation test with a comment explaining the expected true result and why it's acceptable.

paths.push(r.path);
prerenderPathsMap.set(r.route, paths);
}
} else if (!prerenderStatusMap.has(r.route)) {
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.

The priority logic — "rendered" wins over "skipped"/"error" for the same route — is smart for dynamic routes with multiple paths where some succeed and some fail. But the else if (!prerenderStatusMap.has(r.route)) guard means the first non-rendered status wins among skipped/error. If a route has both skipped and error results, whichever appears first in the array determines the status.

Is that intentional? It seems unlikely in practice (a route is either skipped or errored, not both), but if it can happen, you might want error to take priority over skipped.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 1, 2026

Review submitted successfully. Here's a summary of the feedback posted on PR #732:

Key findings (9 inline comments):

  1. Dynamic API regex false positive (report.ts:113): Multi-line block comments that start on a prior line can produce false positives with the ^-anchored regex. Documented limitation, not blocking.

  2. Naming nit (report.ts:139): detectsDynamicApiUsage reads as a descriptor rather than an imperative — detectDynamicApiUsage or hasDynamicApiImport would be more consistent with the codebase.

  3. Duplicated counters in PrerenderProgress (run-prerender.ts:66): update() tracks internal rendered/skipped/errors counters, but finish() takes its own parameters from the caller. The duplication is a subtle maintenance trap.

  4. Dry-run unknown routes (run-prerender.ts:274): Routes classified as unknown show → render in dry-run output, which could confuse users since these may fail at render time.

  5. Unicode sentinel in compileRouteGlob (run-prerender.ts:117): Using \uFFFF as a placeholder is clean but theoretically fragile if a route contained that character. Acknowledged as academic.

  6. onProgress error handling (run-prerender.ts:389): Good defensive try/catch, but could flood stderr if the callback is consistently broken — consider a log-once pattern.

  7. Public API stability (index.ts:3955): New un-prefixed exports signal stable API. Confirmed this is intentional per the PR description.

  8. Missing test for block comment false positive (build-report.test.ts:388): The comment test covers // but not multi-line /* ... */ block comments spanning lines.

  9. Prerender status priority (report.ts:793): "Rendered" correctly wins over "skipped"/"error", but among non-rendered statuses, first-in-array wins. If a route could have both skipped and error results, error should arguably take priority.

github run

…#565)

- Improve progress bar: phase labels (App/Pages Router), elapsed time,
  render rate, real-time status breakdown (rendered/skipped/errors)
- Improve route classification: detect dynamic API imports (headers,
  cookies, draftMode, connection, unstable_noStore) via static analysis
  to classify routes as dynamic instead of unknown
- Add prerender annotations to build report: [prerendered], [skipped],
  [error] suffixes on routes, prerender summary section
- Expose public API: runPrerender, PrerenderProgress, compileRouteGlob,
  matchRouteGlob, and associated types exported from package entry
- Add route filtering: includeRoutes/excludeRoutes glob patterns with
  compiled regex caching via createRouteFilter
- Add dry-run mode: scan and classify routes without rendering
- Add external onProgress callback for programmatic progress tracking
@raed04 raed04 force-pushed the feat/prerender-usability-565 branch from dda07d1 to e7a7c9a Compare April 9, 2026 14:30
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Thorough review of the prerender usability enhancements. The feature set is well-scoped and the test coverage is strong. I have a few substantive concerns and several minor observations.

Key concerns:

  1. The detectsDynamicApiUsage naming is inconsistent with the rest of the codebase (descriptive vs imperative)
  2. PrerenderProgress has duplicated counter state that will be a maintenance trap
  3. compileRouteGlob has a potentially surprising ** behavior where /api/**/users doesn't match /api/users
  4. The onProgress error handler can flood stderr
  5. The multi-line block comment false positive should be documented as a known limitation in the source

Looks good:

  • Dynamic API detection regex work is solid — import type and inline type modifiers are correctly handled
  • Route filtering with compiled regex caching is clean
  • Prerender status priority logic (rendered > skipped/error) is correct for the multi-path dynamic route case
  • The dry-run mode is a useful addition
  • Test coverage is comprehensive (44 new tests covering edge cases like aliased imports, mixed type modifiers, multi-line imports)

* - Does not detect `type` imports (which are erased at runtime — correct behavior)
* - Anchored to line start to avoid false positives from commented-out imports
*/
export function detectsDynamicApiUsage(code: string): boolean {
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.

Agreeing with the previous review: detectsDynamicApiUsage reads as a descriptor ("this function detects...") rather than an imperative. Every other function in this file uses imperative naming: extractExportConstString, hasNamedExport, classifyAppRoute.

Suggested change
export function detectsDynamicApiUsage(code: string): boolean {
export function detectDynamicApiUsage(code: string): boolean {

This is exported and referenced in tests, so the rename touches tests/build-report.test.ts too. Worth doing now before external consumers depend on the name.

process.stderr.write(`\r${padded}`);
}

finish(rendered: number, skipped: number, errors: number): void {
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.

finish() accepts its own rendered/skipped/errors parameters computed by the caller (lines 465-471), while update() independently tracks the same counters in this.rendered/this.skipped/this.errors (lines 66-68). The internal counters are only used for the live progress bar display.

This dual-tracking is a latent bug waiting to happen — a future maintainer could reasonably switch finish() to use this.rendered etc. (since the fields exist), and the counts would silently diverge if any update() call arrives without a status.

I'd recommend one of:

  1. Make finish() use the internal counters and drop the parameters
  2. Remove the internal counters entirely and have the caller pass the running counts into update()

Option 1 seems cleanest since finish() is always called after all update() calls.

.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
.replace(/\*\*/g, DOUBLE_STAR) // placeholder for **
.replace(/\*/g, "[^/]*") // * matches within segment
.replace(new RegExp(DOUBLE_STAR, "g"), ".*"); // ** matches across segments
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.

Note: ** in a mid-path position like /api/**/users does NOT match /api/users (zero intermediate segments). This is because ** compiles to .* but the surrounding / separators are literal, so the compiled regex is ^\/api\/.*\/users$ which requires at least one / between api and users.

This may surprise users expecting shell-glob semantics where ** can match zero segments. Minimatch and other glob libraries typically handle this. Consider documenting this limitation in the JSDoc for compileRouteGlob, or adjusting the compilation so that /**/ generates (?:/.*)?/ to allow zero-segment matching.

for (const route of routes) {
const { type } = classifyAppRoute(route.pagePath, route.routePath, route.isDynamic);
const sym = SYMBOLS[type] ?? "?";
const action = type === "api" || type === "ssr" ? "skip" : "render";
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.

Routes classified as unknown get action = "render" here, but the dry-run output shows a plain → render which is indistinguishable from confidently-static routes. For static routes, rendering is expected to succeed; for unknown routes, it's speculative and may fail.

Consider distinguishing this in the output:

Suggested change
const action = type === "api" || type === "ssr" ? "skip" : "render";
const action = type === "api" || type === "ssr" ? "skip" : "render";
const speculative = type === "unknown" ? " (speculative)" : "";
console.log(` ${sym} ${route.pattern}${action}${speculative}`);

Comment on lines +389 to +395
try {
options.onProgress?.(update);
} catch (err: unknown) {
process.stderr.write(
`[vinext] onProgress callback error: ${err instanceof Error ? err.message : String(err)}\n`,
);
}
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.

If onProgress is consistently broken (e.g., a TypeError from bad destructuring), this writes an error message to stderr on every single route. For a project with hundreds of routes, that's hundreds of identical error lines flooding the output.

Consider a log-once pattern:

Suggested change
try {
options.onProgress?.(update);
} catch (err: unknown) {
process.stderr.write(
`[vinext] onProgress callback error: ${err instanceof Error ? err.message : String(err)}\n`,
);
}
try {
options.onProgress?.(update);
} catch (err: unknown) {
if (!this._onProgressErrorLogged) {
this._onProgressErrorLogged = true;
process.stderr.write(
`[vinext] onProgress callback error (further errors suppressed): ${err instanceof Error ? err.message : String(err)}\n`,
);
}
}

(The same pattern applies to the identical block in the Pages Router phase at line 440-446.)

Note: this would need a flag on the runPrerender scope, not on PrerenderProgress, since the error handling is in the closure.

Comment on lines +106 to +108
// Anchored to line start (^) to avoid matching commented-out imports.
// Uses multiline flag so ^ matches each line in multi-line source.
// Negative lookbehind (?<!\btype\s) skips inline type modifiers like
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.

The comment says the ^ anchor avoids matching commented-out imports, but this only works for single-line // comments. As confirmed by testing, a multi-line block comment where import starts on its own line is a false positive:

const x = 1; /*
import { headers } from "next/headers";
*/

This is acceptable for a heuristic, but the comment should document the limitation explicitly so future readers know:

Suggested change
// Anchored to line start (^) to avoid matching commented-out imports.
// Uses multiline flag so ^ matches each line in multi-line source.
// Negative lookbehind (?<!\btype\s) skips inline type modifiers like
// Anchored to line start (^) to avoid matching commented-out imports.
// Uses multiline flag so ^ matches each line in multi-line source.
// Limitation: multi-line block comments where `import` starts on a new line
// will produce false positives — this is a known tradeoff of regex-based analysis.
// Negative lookbehind (?<!\btype\s) skips inline type modifiers like

Comment on lines +793 to +797
} else if (!prerenderStatusMap.has(r.route)) {
// Only set skipped/error if not already rendered (a dynamic route may
// have some rendered paths and some errors).
prerenderStatusMap.set(r.route, r.status);
}
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.

The status priority logic is: rendered always wins (overwrites any prior status), then first-seen non-rendered status sticks. This means if the results array has [{ route: "/x", status: "skipped" }, { route: "/x", status: "error" }], the route gets skipped — the error is silently hidden.

In practice a single route probably can't be both skipped and errored, but the defensive option would be to give error priority over skipped:

Suggested change
} else if (!prerenderStatusMap.has(r.route)) {
// Only set skipped/error if not already rendered (a dynamic route may
// have some rendered paths and some errors).
prerenderStatusMap.set(r.route, r.status);
}
} else if (!prerenderStatusMap.has(r.route)) {
prerenderStatusMap.set(r.route, r.status);
} else if (r.status === "error" && prerenderStatusMap.get(r.route) === "skipped") {
// Error takes priority over skipped for the same route.
prerenderStatusMap.set(r.route, r.status);
}

export { stripServerExports as _stripServerExports };
export { asyncHooksStubPlugin as _asyncHooksStubPlugin };

// Public prerender API
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.

These are exported without the _ prefix used by the internal exports above, signaling stable public API. Once external consumers depend on runPrerender, compileRouteGlob, matchRouteGlob, and PrerenderProgress, changing their signatures is a breaking change.

Is this intentional? If there's any uncertainty about the API shape (e.g., whether compileRouteGlob should be public or just matchRouteGlob), consider _ prefixing them for now. The types (RunPrerenderOptions, PrerenderResult, etc.) are fine since they're narrower contracts.

Specifically, PrerenderProgress as a class export is a strong commitment — consumers could subclass it. If the intent is just to let external code observe progress, the onProgress callback is sufficient and PrerenderProgress could remain internal.

Comment on lines +384 to +390
expect(detectsDynamicApiUsage("")).toBe(false);
});

// ── Edge cases ──────────────────────────────────────────────────────────────

it("ignores commented-out imports", () => {
const code = `// import { headers } from "next/headers";\nexport default function Page() {}`;
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.

Good test for single-line /* ... */ block comments. However, the multi-line block comment case that produces a false positive (confirmed by testing) is not covered:

const x = 1; /*
import { headers } from "next/headers";
*/

Worth adding as a documented known-limitation test:

Suggested change
expect(detectsDynamicApiUsage("")).toBe(false);
});
// ── Edge cases ──────────────────────────────────────────────────────────────
it("ignores commented-out imports", () => {
const code = `// import { headers } from "next/headers";\nexport default function Page() {}`;
it("ignores block-commented imports", () => {
const code = `/* import { headers } from "next/headers"; */\nexport default function Page() {}`;
// Block comments don't start with `import` at line start, so the ^ anchor skips them
expect(detectsDynamicApiUsage(code)).toBe(false);
});
it("false positive: multi-line block comment with import on new line (known limitation)", () => {
// When a block comment spans multiple lines and `import` starts at the beginning
// of a new line, the regex cannot distinguish it from real code. This is a known
// tradeoff of regex-based analysis — acceptable for a build report heuristic.
const code = `const x = 1; /*\nimport { headers } from "next/headers";\n*/`;
expect(detectsDynamicApiUsage(code)).toBe(true);
});

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 10, 2026

Review submitted successfully. Here's a summary of the feedback posted on PR #732:

9 inline comments covering:

  1. detectsDynamicApiUsage naming (report.ts:139) — Should be detectDynamicApiUsage (imperative, matching codebase convention). Suggestion provided.

  2. Duplicated counters in PrerenderProgress (run-prerender.ts:90) — update() and finish() independently track rendered/skipped/errors counts. Recommended making finish() use the internal counters to eliminate the dual-tracking maintenance trap.

  3. compileRouteGlob ** zero-segment matching (run-prerender.ts:122) — /api/**/users does NOT match /api/users (confirmed by testing). This may surprise users expecting shell-glob semantics where ** can match zero segments. Suggested documenting or fixing.

  4. Dry-run unknown route ambiguity (run-prerender.ts:274) — Routes classified as unknown show → render identically to confidently-static routes. Suggested appending (speculative) to distinguish them.

  5. onProgress error flooding (run-prerender.ts:389-395) — A consistently broken callback writes an error to stderr on every route. Suggested a log-once pattern. Same issue exists in the Pages Router phase.

  6. Multi-line block comment limitation undocumented (report.ts:106-108) — The comment claims ^ avoids commented-out imports, but doesn't mention the known false positive for multi-line block comments. Suggestion to add this to the comment.

  7. Prerender status priority for skipped vs error (report.ts:793-797) — First-seen non-rendered status wins. If a route has both skipped and error results, error should take priority. Suggestion provided.

  8. Public API stability (index.ts:4212) — New exports are un-prefixed (stable API). Questioned whether PrerenderProgress class should be public (consumers could subclass it) vs keeping it internal and relying on the onProgress callback.

  9. Missing multi-line block comment false positive test (build-report.test.ts:384-390) — The single-line /* ... */ case is tested but the known multi-line false positive is not. Suggested adding a documented known-limitation test.

github run

@james-elicx james-elicx marked this pull request as draft April 10, 2026 07:16
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.

Pre-rendering usability enhancements

2 participants