feat(remix): scaffold remix-router, remix-start, and basic example#7359
Draft
tannerlinsley wants to merge 2 commits intomainfrom
Draft
feat(remix): scaffold remix-router, remix-start, and basic example#7359tannerlinsley wants to merge 2 commits intomainfrom
tannerlinsley wants to merge 2 commits intomainfrom
Conversation
Adds a 'remix' target option to router-generator and introduces two new packages plus a basic example exercising the new target. Preserves WIP that was at risk of being dropped from the clever-ellis-6fe30d worktree.
The remix-router scaffold commit (5a13aec) salvaged WIP from clever-ellis-6fe30d but accidentally truncated several files and left references to functionality that lives on other branches. None of it built end-to-end. This restores or removes the broken pieces, fixes the example, and adds a parallel pure-Remix-3 islands example for reference. Adjacent-package fixes (build-blockers from the original scaffold): - router-generator/src/config.ts: restored from main, re-added 'remix' target to the schema enum - start-plugin-core/src/vite/dev-server-plugin/plugin.ts: restored from main (was truncated mid-function) - start-plugin-core/src/import-protection/defaults.ts: removed (orphaned, referenced missing ./utils, nothing imports it) - start-server-core/src/index.tsx: removed early-hints type re-exports (file lives on a different branch, not used here) - remix-router/src/headContentUtils.ts: removed isInlinableStylesheet reference (also lives on a different branch) - remix-start/src/plugin/vite.ts: imports moved to match solid-start shape; the ./vite subpath isn't an exported entry of start-plugin-core - remix-start/src/server/index.ts: removed createStartApp export (never existed in createStartHandler.ts) Binding fix: - remix-router/src/awaited.tsx: gate handle.update() in onSettle to client-only via isServer. The SSR scheduler doesn't implement scheduleUpdate, so post-stream updates were crashing the dev server whenever a route used <Await>. Example fixes (examples/remix/basic): - All 16 route files: createRoute('/path')(options) was wrong shape (createRoute takes a single options arg, not curried). Switched call sites to createFileRoute('/path')(options) to match the router-generator template, which is the actual file-based pattern these routes are meant to use. - routes/index.tsx: added (was missing; routeTree.ts referenced an Index route that didn't exist) - routes/streaming.tsx, routes/deferred.tsx: removed. Both are blocked on real binding work — Frame needs resolveFrame threading through nested SSR renders + Manuel's pending buildServerFnUrl PR for the URL-builder side; <Await> SSR streaming chunks aren't reaching the response body. Tracked in the README. - routes/__root.tsx: dropped Deferred link from nav - routes/catalog.tsx: input was value={search.q} with no event that updated value, so typing did nothing visible. Switched to defaultValue. Loader now uses sort in loaderDeps and actually applies the sort to the items. - README.md: rewrote with architecture diagram, primitive table, accurate route list, hydration model section, and a "what's not covered" section calling out the binding gaps. New example (examples/remix/islands): - Pure-Remix-3 reference using @remix-run/ui directly with no TanStack Router. Demonstrates idiomatic island hydration: a static-rendered page with two clientEntry()-marked components (LinkIsland, Counter) hydrating independently. Runs on a small Vite-middlewareMode SSR server (47-line server.js). Documentation: - remix-router/README.md: test count refreshed to 120 (111 passing); three real binding gaps (Frame, Await SSR streaming, serverComponent endpoint) added to the "Not yet" list. - remix-router/START.md: bundle-size table updated with measured numbers (296 kB raw / 64 kB gzip total client; ~46 kB gzip initial entry; 556 kB server bundle) replacing the prior estimate. What works in the basic example after this: - 16 routes serving HTTP 200 (or intentional 500/404 for the lab routes), SPA navigation working, server stays up across the route set, type-check clean, all builds green.
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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.
feat(remix): scaffold remix-router, remix-start, and basic example
Brings up an experimental TanStack Router binding to Remix 3 (
@remix-run/ui) plus the Start adapter and a working basic example.What's in the box
@tanstack/remix-router@tanstack/remix-startuseServerFnexamples/remix/basicexamples/remix/islands@remix-run/uidirectly (no TanStack), demonstrates idiomatic island hydrationpackages/remix-router/testsFor comparison:
react-routersrc is 6,699 LOC,solid-routeris 5,487.react-startis 251,solid-startis 127.Build & test
nx run @tanstack/remix-router:buildnx run @tanstack/remix-start:buildnx run @tanstack/remix-router:test:unitexamples/remix/basicpnpm buildThe 9 test failures are all in
tests/serverComponent.test.tsxandtests/dom/serverComponent.test.tsx. They fail because_resetServerComponentRegistryanddeactivateServerComponentCollectoraren't exported from the source — the test scaffold was added before the implementation. Not blocking; tracked as a follow-up below.Bundle size (basic example, production build)
index+useRouter)Initial-load gzip (~46 kB) is dominated by the router runtime + Remix UI reconciler. The whole route tree hydrates as one Remix UI mount — there is no per-component selective hydration. This is intentional: TanStack Router's reactive store subscriptions need to be live across the route tree.
For comparison,
examples/remix/islands(no TanStack Router, pure@remix-run/uiislands viarun()) demonstrates what selective hydration would look like: onlyclientEntry()-marked regions ship as hydration roots, and the rest is inert HTML. That's a different stack, not a different mode of this one.Routes that work in the basic example
//users,/users/$id<Outlet>/posts,/posts/$slugcreateServerFnrendering markdown HTML; mounted viainnerHTML/admin/users/$userId/sessions/$sessionId/catalog?…validateSearch,loaderDeps,<Link search={updater}>, form-drivenuseNavigate/slowpendingComponentUI/lab/errorerrorComponent(intentional 500)/lab/missingnotFound()→notFoundComponent(intentional 404)/lab/render-error<CatchBoundary>/guestbookcreateServerFn({ method: 'POST' })withinputValidator; formon('submit')calls server fn/counterclientEntry()-marked island embedded in route componentWhat's not (yet) covered
Three pieces are partially implemented and need follow-up work in the binding before the corresponding example routes can ship:
1.
<Frame>server-fn–backed boundariesrenderPostBody.url(slug)(curried)..urlis a string property, not a function. Manuel Schiller'sbuildServerFnUrlPR (branchorigin/buildServerFnUrl) lands abuildServerFnUrl(fn, input): Promise<string>helper; once merged, the example loader can precompute the URL.No resolveFrame providedfrom a recursiverenderToStreamcall. The remix-router top-level handler does passresolveFrame, but inner renders (when a frame'ssrcis itself a route URL) don't get the option threaded through. NeedsresolveFrameto be a property of the request context rather than an option of the outerrenderRouterToStreamcall.2.
<Await>/defer()SSR streamingawaited.tsxonSettle → handle.update()now skips on the server. The post-streamscheduleUpdate not implementedcrash is gone.scriptBuffer.enqueuebut don't reach the response body. The server holds the connection open for the deferred duration, but the only<script>chunk in the response is the initial dehydration with the placeholder — never the resolution. The bug is somewhere betweencrossSerializeStream'sonSerializecallback andpipeWithDehydration'scollectInjection()(which runs once, at end-of-stream, afterwaitForSerialization())./deferredroute was prepared and verified end-to-end during this work, then rolled back since "renders fallback forever" is worse UX than no route. Restore from git history once (b) is fixed.3.
serverComponent()re-render endpointTest scaffold present in
tests/serverComponent.test.tsxandtests/dom/serverComponent.test.tsx(9 failing tests). Source files exist (src/serverComponent.tsx,src/serverComponentClient.ts,src/serverComponentEndpoint.ts,src/serverComponentSSR.tsx) but the test helpers_resetServerComponentRegistryanddeactivateServerComponentCollectoraren't exported. Probably means the implementation was started, the tests were sketched against the intended API, and the work paused. No demo route — the surface is incomplete.Files changed
Adjacent-package edits made to get the binding to build on this branch (each was either a corrupted-truncated file from the salvage commit or a missing reference to functionality that landed on a different branch):
packages/router-generator/src/config.ts— restored from main, re-added'remix'targetpackages/start-plugin-core/src/vite/dev-server-plugin/plugin.ts— restored from mainpackages/start-plugin-core/src/import-protection/defaults.ts— removed (orphan, referenced missing./utils)packages/start-server-core/src/index.tsx— removedearly-hintsre-exports (referenced unimported file)packages/remix-router/src/headContentUtils.ts— removedisInlinableStylesheetreference (lives on a different branch)packages/remix-start/src/plugin/vite.ts— fixed import paths to matchsolid-startshapepackages/remix-start/src/server/index.ts— removedcreateStartAppexport (never existed)packages/remix-router/src/awaited.tsx— addedif (isServer) returnguard inonSettleAPI surface in
@tanstack/remix-routerWhat's wired up and exercised by the example or tests:
Testing notes for reviewers
The basic example is intentionally self-contained — it doesn't depend on
<Frame>or<Await>SSR streaming working. Once the two binding gaps above are fixed, the/streamingand/deferredroutes can be restored from git history.What this PR is NOT
react-router(devtools,<Form>middleware integration, RSC-style boundaries are not in scope).packages/remix-router/START.md, which is a separate piece of work.