Skip to content

fix(pages): include .dev and .prod pages only in their environment - #595

Merged
Brooooooklyn merged 13 commits into
mainfrom
fix/pages-env-suffix
Oct 26, 2026
Merged

Brooooooklyn merged 13 commits into
mainfrom
fix/pages-env-suffix

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Fixes #558.

Pages stripped .dev and .prod from the URL but kept the page in every environment. So debug.dev.tsx shipped to production, and a home.dev.tsx + home.prod.tsx pair failed as a duplicate route. Pages now follow the same rule as routes/.

Page file(s) vp dev build / deploy / preview URL
home.tsx ✓ ✓ /home
debug.dev.tsx ✓ — /debug
stats.prod.tsx — ✓ /stats
home.dev.tsx + home.prod.tsx dev file prod file /home
home.tsx + home.dev.tsx error (two pages for /home) home.tsx /home
home.tsx + home.prod.tsx home.tsx error /home
environment   when                          pages kept
dev           vp dev                        plain + .dev
prod          build, deploy, vite preview   plain + .prod
(none)        void prepare (types only)     all; clash check runs once as dev, once as prod

Rules:

  • The suffix goes before .island: chart.dev.island.tsx.
  • A companion repeats it: home.dev.tsx pairs with home.dev.server.ts. A dropped page's companion is never read.
  • Layouts take no suffix.
  • The page id keeps the suffix (home.dev, home.prod), so the two pages of a pair never share an id.

Every way a .dev page could reach production, and what closes it

core route table, prerender, ISR, deploy, types   → scanPages(environment), passed from the app analysis
adapter renderer + client entry (React/Vue/Svelte/Solid)
                                                  → pages plugin rescans with the current environment
adapter island list and island client             → islands plugin scans with the current environment
islands client entry, dev island rescan           → hasIslandPages scans 'prod'; the dev watcher scans 'dev'
inferred bindings (D1, KV, R2, AI…)               → page files and companions filtered like routes/
@void/md pages list and md client scripts         → same filter; the listed path drops the suffix

Framework apps that own pages/ (Nuxt and others) are unchanged.

Generated types

  • For a .dev + .prod pair, void prepare writes one PageActionMap entry per URL as a union, { dev shape } | { prod shape }. So each page's form body type-checks even when the two bodies differ.
  • ResolveActionBody and ActionUrl in void/routes (and their generated copies) now read each half of that union on its own. So useForm accepts each page's URLs and body, even when one page has action and the other has actions, or the two have different named actions. A page with only named actions no longer loosens the other page's bare-URL body. For a single page the result is the same as before.

Tests

  • pages-scan.test.ts: .dev page; .dev + .prod pair; plain + .dev and plain + .prod clashes, per environment and unfiltered; shadow warnings per environment; .dev.island; a broken companion of a dropped page is never parsed; index.dev; layouts; a pageFileEnvironment table checked against the scanner.
  • scan.test.ts, route-types.test.ts, plugin-routing.test.ts, pages-islands-rescan.test.ts, plugin-inference.test.ts (including a Nuxt app, whose bindings are not filtered).
  • React, Vue, Svelte and Solid: pages and islands in build and in serve, including pages/ created after the dev server starts.
  • @void/md: serve lists debug.dev.md as /debug; a build leaves it and its script out.
  • Integration:
    • tsx-server.test.ts runs a real vp dev and vp build. /debug gives 200 in dev and 404 in prod, /greeting shows each environment's page, and the production types and module graphs have no .dev page.
    • page-action-types-typecheck.test.ts runs tsc on generated pair types through the same U extends ActionUrl constraint the adapters' useForm uses.
  • Mutation checks: each guarded line was broken in turn (the scanner filter, the per-environment clash loop, every adapter's environment value and the isDev move, the islands helpers, the type join, ResolveActionBody, the inference filter, the md filter). Each break made a test fail.
  • vp fmt, vp lint, the void/react/vue/svelte/solid/md typechecks, vp run test and one full vp test run pass.

A separate review built real apps and found no trace of .dev files in any production output: bundles, manifests, prerender output, _headers, wrangler bindings or types. It also checked vp preview, adding and removing pages while vp dev runs, and void prepare.

Choices to check

  • Types follow the running environment. The dev server and builds write .void/routes.d.ts for their own environment, as routes/ already does, and void prepare writes both. So while vp dev runs, a .prod page that uses useForm shows a type error in the editor (and the other way round after vp build). Fixing that would need a second page scan just for types, which would have to parse dev-only files during a production build. The docs say this plainly.
  • home.tsx + home.prod.tsx errors only at build or void prepare, not on the dev server, because only one page runs in dev. The same goes for routes/.
  • Helper files shared by .dev pages (pages/_db.ts, src/db.ts) still add their bindings to production. Binding inference reads each file on its own and doesn't follow imports, as with routes/.
  • Wrong suffix order (chart.island.dev.tsx) gets no new error. It is a dev-only regular page at /chart.island. The docs say the suffix goes first, as in chat.dev.ws.ts for WebSocket routes.
  • An app whose only island page or only md page with a <script> is .dev gets an empty client chunk in production. No page code ships.

🤖 Generated with Claude Code

Brooooooklyn and others added 11 commits October 26, 2026 17:45
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ment

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… body

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erver

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T10:24:22.723778Z 7648ea3 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfe3c3da63

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/codegen/route-types.ts
…pair

A .dev + .prod pair is a union of two page shapes in PageActionMap.
ActionUrl tested the whole union at once, so a pair with different
named actions, or with `action` in one page and `actions` in the other,
lost URLs such as `/contact?save`, and useForm rejected them. Read each
shape on its own, in void/routes and in the generated copy, and check
the useForm URL constraint with tsc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bea2f7d8c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/void/src/routes-stub.ts Outdated
…has named actions

For a .dev + .prod pair where one page exports `action` and the other
`actions`, the bare URL's body type also took the named-actions shape's
untyped fallback, so any body passed. Read the body only from shapes that
have one, and fall back to an untyped body only when none does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 7648ea32ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit f36e7bd into main Oct 26, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/pages-env-suffix branch October 26, 2026 10:37
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.

Pages strips .dev and .prod suffixes without restricting pages by environment

1 participant