examples: add tanstack-start-basic example app#1081
examples: add tanstack-start-basic example app#1081moss-bryophyta wants to merge 1 commit intogeneraltranslation:mainfrom
Conversation
| @@ -0,0 +1,4 @@ | |||
| export default async function loadTranslations(locale: string) { | |||
| const translations = await import(`./src/_gt/${locale}.json`); | |||
There was a problem hiding this comment.
Unvalidated locale in dynamic import path
The locale string is embedded directly into a dynamic import path without any sanitization. In a TanStack Start app the loadTranslations function runs on the server (SSR/Nitro), and locale is ultimately derived from user-controlled input (e.g., Accept-Language headers or cookies). A crafted locale value such as ../../package could cause the server to resolve and import an unintended file (e.g., ./src/_gt/../../package.json), leaking sensitive information.
Adding an explicit allowlist check before the import ensures only known-good locales are ever loaded:
export default async function loadTranslations(locale: string) {
const allowedLocales = ['es', 'ja']
if (!allowedLocales.includes(locale)) {
return {}
}
const translations = await import(`./src/_gt/${locale}.json`);
return translations.default;
}Alternatively, sync this list from gt.config.json so it stays in one place.
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/tanstack-start-basic/loadTranslations.ts
Line: 2
Comment:
**Unvalidated locale in dynamic import path**
The `locale` string is embedded directly into a dynamic import path without any sanitization. In a TanStack Start app the `loadTranslations` function runs on the server (SSR/Nitro), and `locale` is ultimately derived from user-controlled input (e.g., `Accept-Language` headers or cookies). A crafted locale value such as `../../package` could cause the server to resolve and import an unintended file (e.g., `./src/_gt/../../package.json`), leaking sensitive information.
Adding an explicit allowlist check before the import ensures only known-good locales are ever loaded:
```typescript
export default async function loadTranslations(locale: string) {
const allowedLocales = ['es', 'ja']
if (!allowedLocales.includes(locale)) {
return {}
}
const translations = await import(`./src/_gt/${locale}.json`);
return translations.default;
}
```
Alternatively, sync this list from `gt.config.json` so it stays in one place.
How can I resolve this? If you propose a fix, please make it concise.| "@tanstack/react-router": "latest", | ||
| "@tanstack/react-start": "latest", | ||
| "@tanstack/router-plugin": "latest", | ||
| "gt-react": "latest", | ||
| "gt-tanstack-start": "latest", |
There was a problem hiding this comment.
Unpinned "latest" for major dependencies
Several packages are resolved to "latest" with no semver constraint at all:
@tanstack/react-router,@tanstack/react-start,@tanstack/router-plugin— these packages release breaking changes between minor/major versionsgt-react,gt-tanstack-start,gt— same concern for the GT packages themselves
Using "latest" means a fresh npm install on a different day can produce an entirely different (potentially incompatible) dependency tree. This is especially impactful in an example app that others will clone and run as a reference.
Consider pinning to a semver range (e.g., "^1.0.0") so the example remains reproducible, even if it's updated periodically. react/react-dom already correctly use ^19.2.0 — the same pattern should apply here.
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/tanstack-start-basic/package.json
Line: 11-15
Comment:
**Unpinned `"latest"` for major dependencies**
Several packages are resolved to `"latest"` with no semver constraint at all:
- `@tanstack/react-router`, `@tanstack/react-start`, `@tanstack/router-plugin` — these packages release breaking changes between minor/major versions
- `gt-react`, `gt-tanstack-start`, `gt` — same concern for the GT packages themselves
Using `"latest"` means a fresh `npm install` on a different day can produce an entirely different (potentially incompatible) dependency tree. This is especially impactful in an example app that others will clone and run as a reference.
Consider pinning to a semver range (e.g., `"^1.0.0"`) so the example remains reproducible, even if it's updated periodically. `react`/`react-dom` already correctly use `^19.2.0` — the same pattern should apply here.
How can I resolve this? If you propose a fix, please make it concise.| <Scripts /> | ||
| </body> |
There was a problem hiding this comment.
Scripts rendered outside GTProvider
<Scripts /> is placed after the closing </GTProvider> tag. In TanStack Start, <Scripts /> emits the client-side JavaScript bundles responsible for hydrating the React tree. If the hydration script runs outside the GTProvider boundary at the HTML level, the client bundle may try to reconcile a DOM tree that does not match what the server rendered (GTProvider wrapping the nav and children), potentially producing a hydration mismatch warning or subtle rendering issues.
The conventional pattern is to keep <Scripts /> inside the same root-level wrapper that was used during SSR:
| <Scripts /> | |
| </body> | |
| <body> | |
| <GTProvider translations={translations}> | |
| <nav style={{ padding: '1rem', borderBottom: '1px solid #eee' }}> | |
| <LocaleSelector /> | |
| </nav> | |
| {children} | |
| <Scripts /> | |
| </GTProvider> | |
| </body> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/tanstack-start-basic/src/routes/__root.tsx
Line: 53-54
Comment:
**`Scripts` rendered outside `GTProvider`**
`<Scripts />` is placed after the closing `</GTProvider>` tag. In TanStack Start, `<Scripts />` emits the client-side JavaScript bundles responsible for hydrating the React tree. If the hydration script runs outside the `GTProvider` boundary at the HTML level, the client bundle may try to reconcile a DOM tree that does not match what the server rendered (GTProvider wrapping the nav and children), potentially producing a hydration mismatch warning or subtle rendering issues.
The conventional pattern is to keep `<Scripts />` inside the same root-level wrapper that was used during SSR:
```suggestion
<body>
<GTProvider translations={translations}>
<nav style={{ padding: '1rem', borderBottom: '1px solid #eee' }}>
<LocaleSelector />
</nav>
{children}
<Scripts />
</GTProvider>
</body>
```
How can I resolve this? If you propose a fix, please make it concise.
Adds a minimal TanStack Start example app with gt-tanstack-start for internationalization.
Greptile Summary
This PR adds a minimal TanStack Start example app (
examples/tanstack-start-basic) demonstrating General Translation'sgt-tanstack-startpackage for SSR-compatible i18n. The overall structure follows the GT/TanStack Start integration patterns well —initializeGTat module level,GTProviderwrapping the shell, andgetTranslations/getLocalein the root loader. Pre-generated translations for Spanish and Japanese are included so new developers can run the example immediately.Issues found:
loadTranslations.ts: Thelocaleparameter is interpolated directly into a dynamicimport()path with no allowlist validation. In an SSR context (Nitro/Vinxi), this could allow a crafted locale value to load unintended files from the server filesystem.<Scripts />rendered outsideGTProvider: The hydration script tag is placed after the closingGTProvider, which can lead to a mismatch between the server-rendered DOM tree and what the client hydrator expects."latest"for core dependencies: All@tanstack/*and GT packages resolve to whatever is newest at install time, making the example non-reproducible and potentially broken by a future breaking release.reactandreact-domcorrectly use^19.2.0— the same pattern should apply to the other packages.Confidence Score: 3/5
loadTranslations.ts(path traversal) andsrc/routes/__root.tsx(Scripts placement) need attention before this example is linked from official docs.Important Files Changed
Scriptsis rendered outside GTProvider which may cause hydration mismatches"latest"with no semver pinning, making the example non-reproducible over timeSequence Diagram
sequenceDiagram participant Browser participant TanStackServer as TanStack Start (Nitro) participant GTLib as gt-tanstack-start participant FS as Filesystem (_gt/*.json) Browser->>TanStackServer: HTTP GET / (Accept-Language: es) TanStackServer->>GTLib: initializeGT({ locales, loadTranslations }) TanStackServer->>GTLib: getLocale() GTLib-->>TanStackServer: "es" TanStackServer->>GTLib: getTranslations() GTLib->>FS: loadTranslations("es") → import("./src/_gt/es.json") FS-->>GTLib: { "01ba21e09b6e9f0d": [...], ... } GTLib-->>TanStackServer: translations object TanStackServer->>TanStackServer: SSR render RootDocument + Home Note over TanStackServer: GTProvider injects translations into React tree TanStackServer-->>Browser: HTML (lang="es", pre-translated content) Browser->>Browser: Hydrate React tree with client bundles (Scripts) Browser->>GTLib: LocaleSelector onChange → navigate with new locale Browser->>TanStackServer: HTTP GET / (Accept-Language: ja)Comments Outside Diff (3)
examples/tanstack-start-basic/loadTranslations.ts, line 2 (link)Unvalidated locale in dynamic import path
The
localestring is embedded directly into a dynamic import path without any sanitization. In a TanStack Start app theloadTranslationsfunction runs on the server (SSR/Nitro), andlocaleis ultimately derived from user-controlled input (e.g.,Accept-Languageheaders or cookies). A crafted locale value such as../../packagecould cause the server to resolve and import an unintended file (e.g.,./src/_gt/../../package.json), leaking sensitive information.Adding an explicit allowlist check before the import ensures only known-good locales are ever loaded:
Alternatively, sync this list from
gt.config.jsonso it stays in one place.examples/tanstack-start-basic/package.json, line 11-15 (link)Unpinned
"latest"for major dependenciesSeveral packages are resolved to
"latest"with no semver constraint at all:@tanstack/react-router,@tanstack/react-start,@tanstack/router-plugin— these packages release breaking changes between minor/major versionsgt-react,gt-tanstack-start,gt— same concern for the GT packages themselvesUsing
"latest"means a freshnpm installon a different day can produce an entirely different (potentially incompatible) dependency tree. This is especially impactful in an example app that others will clone and run as a reference.Consider pinning to a semver range (e.g.,
"^1.0.0") so the example remains reproducible, even if it's updated periodically.react/react-domalready correctly use^19.2.0— the same pattern should apply here.examples/tanstack-start-basic/src/routes/__root.tsx, line 53-54 (link)Scriptsrendered outsideGTProvider<Scripts />is placed after the closing</GTProvider>tag. In TanStack Start,<Scripts />emits the client-side JavaScript bundles responsible for hydrating the React tree. If the hydration script runs outside theGTProviderboundary at the HTML level, the client bundle may try to reconcile a DOM tree that does not match what the server rendered (GTProvider wrapping the nav and children), potentially producing a hydration mismatch warning or subtle rendering issues.The conventional pattern is to keep
<Scripts />inside the same root-level wrapper that was used during SSR:Last reviewed commit: 5b9eea0