Repository navigation
Conversation
yangshun
marked this pull request as ready for review
October 23, 2026 11:06
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds support for
.tsxand.jsxin HTTP/WebSocket route files and recognizes.server.tsx/.server.jsxpage companions.It also fixes existing
.server.tsxcompanions being discovered as standalone pages such as/foo.serverand included in generated client component manifests.The extension enables JSX syntax; the file's location and naming determine its role.
This PR ended up larger than I imagined, mainly because there are many cases to handle. Half of the changes are tests and docs updates. There's also a fair bit of consolidation going on.
P.S. TBH I didn't start out wanting to support JSX for WebSockets, but the route conventions + extensions logic was quite entangled and it was hard to untangle them. Adding support for JSX WebSockets was the easiest way forward while not making the code messy. One use case I convinced myself to add support for JSX in WebSockets is chat apps that sends rendered HTML over WebSockets (hopefully that's not too much of a stretch to imagine!).
Motivations
It's only a matter of time before someone requests for this, given the precedents for JSX-capable HTTP route files.
api/og.tsxandapi/og.jsx, as well as Next.jsapp/api/og/route.tsx, to generate images from JSX inside HTTP handlers.server.handlersinroutes/hello.tsx, alongside a JSX page component.Christoph also mentioned that he prefers
.tsx.Scope
routes/api.tsx/routes/api.jsx/apiroutes/api.dev.tsx/routes/api.prod.jsxroutes/chat.ws.tsx/routes/chat.ws.jsx/chatroutes/chat.dev.ws.tsx/routes/chat.prod.ws.jsxpages/foo.server.tsx/pages/foo.server.jsxpages/foo.tsx/pages/foo.jsxBoth JSX extensions support
.devand.prodrestrictions. For WebSocket routes, the environment suffix goes before.ws.HTTP and WebSocket discovery, filename parsing, page companion lookup, and route binding inference share
ROUTE_EXTENSIONS(ts,tsx,js,jsx,mjs,mts) fromshared/route-extensions.ts. The page scanner uses the derivedROUTE_EXTENSIONS_SETfor membership checks. Both route scanners use the fixedlistRouteFiles()helper and the existing WebSocket filename matcher to distinguish their roles.listConventionFiles()retains its existing script extensions for crons and queues.Discovery and binding inference share the
.well-knownglob policy, including configured source directories. Environment filtering applies only to route files, so_-prefixed helpers retain their bindings in both environments. Companion exclusion applies only to supported script extensions, preserving page components such asweb.server.md.WebSocket resource-name inference strips JSX extensions so changing
chat.ws.tstochat.ws.tsxorchat.ws.jsxpreserves the inferred Durable Object identity. JSX compilation uses the app's configured runtime.The documentation covers supported HTTP, WebSocket, and companion extensions. General enforcement against manually importing
.server.*modules from client code remains a separate feature.Validation
vp run check: all 33 workspaces passed.vp run test: 11,214 passed, 31 skipped.vp test run --maxWorkers=4: 14,594 passed, 31 skipped.Regression coverage executes JSX HTTP handlers and WebSocket messages using React and Hono runtimes, checks environment filtering and generated types/bindings (including
.well-knownroutes and underscored helpers), and executes companion loaders/actions. It also verifies that Vue, Svelte, and Markdown pages with a.serversuffix remain discoverable. Production-build assertions confirm companion code is present on the server and absent from the generated client graph and assets.Follow-up
#559 excludes server companions from React and Solid's client bundle detection, so island-only apps do not emit the full
pages-cliententry.