Repository navigation
feat(tanstack-start): two-line setup with a Vite plugin and router integration - #2418
chenxin-yan wants to merge 22 commits into
Conversation
Bring the TanStack Start setup closer to gt-next's DX. - setupRouterGTIntegration hooks the router's dehydrate/hydrate/Wrap options: the server resolves the locale and loads translations before render, the client hydrates them before its first render, and Wrap injects GTProvider. Apps no longer need a root loader or GTProvider. - The server condition store falls back to Start's request context via getRequest(), so gtMiddleware is no longer required. It still works. - The gt-tanstack-start/plugin/vite plugin supplies gt.config.json and a translation loader (generated from files.gt.output, or a detected src/loadTranslations) through gt-tanstack-start/internal/_config, so apps no longer call initializeGT() or write a loader. Without the plugin, a stub keeps the manual initializeGT() path working. - Migrate tests/apps/tanstack-start to the new setup.
…outer as an option
Match TanStack's casing (tanstackStart) and the shape of TanStack's own
router integrations (setupRouterSsrQueryIntegration({ router, ... })),
which leaves room for integration options.
…nabled setupRouterGTIntegration installs a TanStack Router URL rewrite when the initialized GT config enables localeRouting: input strips one supported locale segment and output prefixes the current non-default locale, so apps declare locale-free routes and links. Opt out with localeRewrite: false. - Install dehydrate/hydrate/Wrap/rewrite through router.update() at setup, composed with any app rewrite (GT runs outside it), once per router. - Apply hydrated GT state before the app's hydrate callback. - With localeRouting, client init prefers a pathname locale over the cookie. - Add a tanstack-start-locale-routing e2e variant of the TanStack app.
gtTanstackStart() now returns the config plugin plus the GT compiler's Vite plugin, on by default. experimentalCompilerOptions mirrors gt-next's options (type 'babel' | 'none', logLevel, compileTimeHash, disableBuildChecks, enableAutoJsxInjection); compileTimeHash: false disables the compiler with a warning. - Both plugins read gt.config.json from one path resolved at factory time against the working directory; the compiler gets the parsed config. - The compiler skips node_modules and receives the file path for TanStack Router ?tsr-split route modules, so <T> in route components is hashed. - Warn when the GT compiler plugin is registered more than once. - Export GtInternalTranslateJsx and GtInternalVar so auto JSX injection imports resolve from gt-tanstack-start.
Add changesets for gt-tanstack-start, gt, and @generaltranslation/compiler, and show the two-line setup in the package README.
…cross entrypoints Read locale-routing enablement from the global condition store so the main and /server entrypoints agree, resolve parseLocale() through the initialized store without middleware, and leave GT state out of prerendered SPA shells.
…eport config errors Resolve files.gt.output from the working directory, replace every [locale] placeholder in the generated loader, wrap gt.config.json read and parse failures in a diagnostic, and cover the config module's loader branches through its real Vite hooks.
…TanStack setup - Count the Vite plugin as registered only when a plugins element is the plugin call itself, not a call behind a condition. - Count the router as integrated only when the options object has one router property for the returned binding and no spread or computed key. - Detect previous-setup namespace usage from GT.initializeGT, GT.gtMiddleware and <GT.GTProvider> references instead of any text mention, so a comment no longer blocks setting up a new app.
The compiler only adds build-time hashes, build checks, and auto JSX injection; translations work without it. Match gt-next instead of turning an experimental Babel pass on for every app: - experimentalCompilerOptions.type now defaults to 'none'. - @generaltranslation/compiler is an optional peer dependency, loaded only when type is 'babel'; a missing install warns and skips the compiler. - Warn when gt.config.json enables auto JSX injection but the compiler is off, since the CLI then extracts JSX the app does not inject. - The example app opts in so e2e still covers compiled route chunks.
There was a problem hiding this comment.
Caution
This PR breaks package imports on React 18 and leaves SSR configuration stale after config hot reload. Both issues are reproduced and detailed inline.
Reviewed changes Reviewed the complete plugin, router integration, compiler support, and CLI setup changes.
- Vite integration: Generates runtime config and translation loaders, with an optional compiler that handles TanStack route-split modules.
- Router lifecycle: Adds provider wrapping, translation dehydration/hydration, request-context fallback, and automatic locale-prefix rewrites.
- CLI setup: Configures the plugin and router while preserving prior manual setups and maintaining existing translation loaders.
- Packaging and coverage: Adds public entrypoints, documentation, changesets, regression tests, and the migrated example with a locale-routing E2E variant.
Validation: 233 focused tests passed, along with the package typecheck and example production build. Browser E2E was not rerun.
gpt-6-astra | 𝕏
… export Also type gt.config.json with GTConfig from generaltranslation/types instead of a local shape.
- Report a manual action when a registered gtTanstackStart() call reads a statically known config path other than the one gt init uses, leaving the Vite config unchanged; runtime-only paths are not flagged. - Escape backslashes, the file's quote and line breaks when writing the config path into the plugin call. - Report a manual action while the loader the Vite plugin uses has no default or named loadTranslations export, on init and configure syncs.
…ver restart Restarting Vite in place keeps GT's process-wide state, so ask users to stop the dev server and start it again.
size-limit report 📦
|
There was a problem hiding this comment.
Important
No new issues found, but the existing server/browser config-consistency concern remains reproducible. I updated the original thread rather than duplicating it.
Reviewed changes Reviewed the changes since c24138065, including the compatibility fixes, setup validation, and restart-only config handling.
- Restored React 18 compatibility: Replaced React 19-only
use()calls with retained-promise suspension and added failure-path coverage. - Separated runtime entrypoints: Split browser/server helpers and middleware, with built-package checks for browser imports.
- Strengthened setup validation: Added custom-loader diagnostics, existing plugin config-path checks, escaped path generation, and shared config types.
- Changed config-edit handling: Removed direct virtual-config watch invalidation and added a full-process restart warning; later module loads can still read settings newer than SSR's initialized state.
- Expanded guidance and tests: Added document-language setup instructions and middleware-free request cache isolation/retry coverage.
Validation: 258 focused tests passed, both package typechecks passed, and the example client/SSR production build passed. All three built runtime entrypoints imported successfully with React 18.3.1; that review thread is resolved. Browser E2E was not rerun.
gpt-6-astra | 𝕏
@generaltranslation/api
gt
@generaltranslation/compiler
generaltranslation
@generaltranslation/format
gtx-cli
gt-i18n
@generaltranslation/icu
locadex
gt-next
gt-node
@generaltranslation/python-extractor
gt-react
@generaltranslation/react-core
@generaltranslation/react-core-linter
gt-react-native
gt-remark
gt-rrweb
gt-sanity
@generaltranslation/supported-locales
gt-tanstack-start
gt-vue
@generaltranslation/vue-extractor
commit: |
GT's server state is set once per process, but later config module loads (a first browser request after an edit, or a plugin recreated by a Vite in-process restart) re-read gt.config.json and could hand the browser settings SSR never applied. Generate the config module once per process so both environments always agree until the process restarts.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the config-snapshot fix since abf7d75.
- Cached the generated config per process: SSR and browser module loads now share the first snapshot, including across recreated Vite plugin instances.
- Added reload regression coverage: Verified repeated loads and plugin recreation retain the original config until a full process restart.
Validation: all 116 TanStack Start tests, the package build, and typecheck passed. An actual example-server probe confirmed consistent SSR/client settings after a config edit and an in-process Vite restart, then adoption of the new locales and routing after a fresh process start. Browser E2E was not rerun.
gpt-6-astra | 𝕏
…ient Both setupRouterGTIntegration entries duplicated the logic that suspends Wrap on pending GT state and the read of locale, region, and translations from a condition store. Move both into createGTRouterWrap, with each entry passing its own provider and loader so the shared module has no server- or browser-only imports.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the shared router-wrapper extraction since e3ffe993d.
- Extracted shared wrapping: Centralized per-router promise caching, resolved state, error propagation, and provider composition in
createGTRouterWrap. - Unified state loading: Reused condition reads and translation snapshots while preserving environment-specific providers, server dehydration, and client hydration ordering.
Validation: all 116 TanStack Start tests, the package build, typecheck, and example client/SSR production build passed after aligning the dependency installation with the PR lockfile. Browser E2E was not rerun.
gpt-6-astra | 𝕏
The rendering-mode test app imported server helpers from the deprecated gt-tanstack-start/server subpath, and the bundle-analysis example still used the manual initializeGT, root loader, GTProvider, and gtMiddleware setup with components from gt-react. Move both to the root import, the Vite plugin, and setupRouterGTIntegration so they exercise and measure what new users install. The deprecated subpath stays covered by the package export tests.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the example migration since e7b1f95e1.
- Migrated the bundle-analysis example: Replaced manual initialization, translation loading, provider wrapping, and GT middleware with the Vite plugin and router integration; retained locale-aware document language.
- Standardized example imports: Used the main
gt-tanstack-startentrypoint for components and server helpers, and removed the bundle example's directgt-reactdependency and obsolete loader configuration.
Validation: all 116 package tests, both example typechecks, the test-app client/SSR build, the bundle-analysis integration build, and both TanStack browser E2E variants passed. Browser checks of the migrated bundle example also confirmed translated server/client content, French/Chinese switching, navigation, and reload persistence without hydration or runtime errors. Production SPA-shell mode was not exercised.
gpt-6-astra | 𝕏



Summary
TanStack Start setup now takes two lines, about as much as gt-next's
withGTConfig+<GTProvider>.How the pieces fit, built on TanStack's own integration points (the same ones
@tanstack/react-router-ssr-queryuses):Also in this PR:
@generaltranslation/compilernow handles imports fromgt-tanstack-startlikegt-react(previously<T>fromgt-tanstack-startgot no compile-time hash).gt initsets up TanStack Start with the plugin + router integration, and leaves apps on the previous setup untouched (with a manual action).initializeGT,gtMiddleware, root loader +<GTProvider>) keeps working and is not deprecated.Decisions taken in this PR:
localeRoutingis on;localeRewrite: falseopts out. Default locale stays unprefixed.experimentalCompilerOptions: { type: 'babel' }with@generaltranslation/compilerinstalled as an optional peer. Translations work without it; it adds build-time hashes and build checks. Ifgt.config.jsonenables auto JSX injection while the compiler is off, a warning explains the mismatch.gtTanstackStart(matchestanstackStartcasing),setupRouterGTIntegration({ router })(matchessetupRouterSsrQueryIntegration({ router, queryClient })). Integration is TanStack Start only, not plain TanStack Router.Evidence
Before:
tests/apps/tanstack-startneededsrc/start.tsmiddleware,initializeGT+ a hand-written loader, and a root loader +<GTProvider>. WithlocaleRouting, apps declared{-$locale}segments themselves.After: the same app uses only the two lines above; the existing e2e (SSR,
ssr: false, data-only routes, locale switching, reload, no console/hydration errors) passes unchanged.New e2e variant
tanstack-start-locale-routing(same app,localeRouting: true), also confirmed to fail withlocaleRewrite: false:Compiler (the example app opts in), from the example app build:
<T>insrc/routes/index.tsxgets_hash: "7135c59c7d131098"in both client and server chunks, matching the key insrc/_gt/fr.json. Before this PR it had no_hash, because the compiler ignoredgt-tanstack-startimports and?tsr-splitroute chunks.Gates on the final head: tanstack-start 101 tests, CLI 3193 tests, compiler 648 tests, typecheck for all touched packages and apps, oxlint/oxfmt,
check:library-defaults, and e2etanstack-start+tanstack-start-locale-routing. Every new test was confirmed to fail before its change.Merge Danger
Door: two-way
The new APIs are additive and the old setup still works. One behavior change: apps that already enable
localeRoutingand declare{-$locale}segments or their own locale rewrite get double-prefixed URLs until they passlocaleRewrite: false. The changeset calls this out.gt-tanstack-startis marked experimental.Blast Radius: TanStack-Start
gt-tanstack-start: new plugin entry and router integration;gtMiddlewareis now optional (the server falls back to Start's request context);@generaltranslation/compileris a new optional peer.@generaltranslation/compiler: one more recognized import source (additive).gtCLI:gt init/gt configureTanStack Start paths only; the Vite SPA path is unchanged.gt-tanstack-start(minor).Follow-ups (not in this PR): a production SPA-mode e2e (shell HTML still renders in the build-time locale); a basepath e2e variant; a compiler-level
?queryhandling option for plain TanStack Router + gt-react users; docs site pages for the new setup.Fix React 18 compatibility and config-path selection before merging, and satisfy the repository requirements for escaping, shared types, and request-cache tests.
Fix with agent prompt
Summary
Adds
gtTanstackStart()andsetupRouterGTIntegration()to load config, supply translations, and connect locale routing to TanStack Start. The CLI now installs this setup, and the compiler recognizes imports fromgt-tanstack-start.localeRewrite: false. Deferred by chenxin-yan: production SPA-mode testing, build-time shell language, a basepath e2e variant, plain Router query handling, and docs-site pages.Diagram
Reviews (1) · Last reviewed commit: "feat(tanstack-start): make the GT compil..." · Reviewed by Greptile