Skip to content

Commit e6862d1

Browse files
committed
docs: some best practices
1 parent 166cc45 commit e6862d1

File tree

5 files changed

+293
-5
lines changed

5 files changed

+293
-5
lines changed

docs/router/framework/react/guide/authenticated-routes.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,41 @@ export const Route = createFileRoute('/_authenticated')({
5151
> [!TIP]
5252
> The `redirect()` function takes all of the same options as the `navigate` function, so you can pass options like `replace: true` if you want to replace the current history entry instead of adding a new one.
5353
54+
### Handling Auth Check Failures
55+
56+
If your authentication check can throw errors (network failures, token validation, etc.), wrap it in try/catch:
57+
58+
```tsx
59+
import { createFileRoute, redirect, isRedirect } from '@tanstack/react-router'
60+
61+
// src/routes/_authenticated.tsx
62+
export const Route = createFileRoute('/_authenticated')({
63+
beforeLoad: async ({ location }) => {
64+
try {
65+
const user = await verifySession() // might throw on network error
66+
if (!user) {
67+
throw redirect({
68+
to: '/login',
69+
search: { redirect: location.href },
70+
})
71+
}
72+
return { user }
73+
} catch (error) {
74+
// Re-throw redirects (they're intentional, not errors)
75+
if (isRedirect(error)) throw error
76+
77+
// Auth check failed (network error, etc.) - redirect to login
78+
throw redirect({
79+
to: '/login',
80+
search: { redirect: location.href },
81+
})
82+
}
83+
},
84+
})
85+
```
86+
87+
The [`isRedirect()`](../api/router/isRedirectFunction.md) helper distinguishes between actual errors and intentional redirects.
88+
5489
Once you have authenticated a user, it's also common practice to redirect them back to the page they were trying to access. To do this, you can utilize the `redirect` search param that we added in our original redirect. Since we'll be replacing the entire URL with what it was, `router.history.push` is better suited for this than `router.navigate`:
5590

5691
```tsx

docs/router/framework/react/guide/data-loading.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,28 @@ export const Route = createFileRoute('/posts')({
160160
})
161161
```
162162

163+
> [!WARNING]
164+
> **Only include dependencies you actually use in the loader.**
165+
>
166+
> A common mistake is returning the entire `search` object:
167+
>
168+
> ```tsx
169+
> // ❌ Don't do this - causes unnecessary cache invalidation
170+
> loaderDeps: ({ search }) => search,
171+
> loader: ({ deps }) => fetchPosts({ page: deps.page }), // only uses page!
172+
> ```
173+
>
174+
> This causes the route to reload whenever ANY search param changes, even params not used in the loader (like `viewMode` or `sortDirection`). Instead, extract only what you need:
175+
>
176+
> ```tsx
177+
> // ✅ Do this - only reload when used params change
178+
> loaderDeps: ({ search }) => ({
179+
> page: search.page,
180+
> limit: search.limit,
181+
> }),
182+
> loader: ({ deps }) => fetchPosts(deps),
183+
> ```
184+
163185
### Using `staleTime` to control how long data is considered fresh
164186
165187
By default, `staleTime` for navigations is set to `0`ms (and 30 seconds for preloads) which means that the route's data will always be considered stale and will always be reloaded in the background when the route is matched and navigated to.

docs/router/framework/react/guide/document-head-management.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,66 @@ export const Route = createRootRoute({
156156
),
157157
})
158158
```
159+
160+
## Inline Scripts with ScriptOnce
161+
162+
For scripts that must run before React hydrates (like theme detection), use `ScriptOnce`. This is particularly useful for avoiding flash of unstyled content (FOUC) or theme flicker.
163+
164+
```tsx
165+
import { ScriptOnce } from '@tanstack/react-router'
166+
167+
const themeScript = `(function() {
168+
try {
169+
const theme = localStorage.getItem('theme') || 'auto';
170+
const resolved = theme === 'auto'
171+
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
172+
: theme;
173+
document.documentElement.classList.add(resolved);
174+
} catch (e) {}
175+
})();`
176+
177+
function ThemeProvider({ children }) {
178+
return (
179+
<>
180+
<ScriptOnce children={themeScript} />
181+
{children}
182+
</>
183+
)
184+
}
185+
```
186+
187+
### How ScriptOnce Works
188+
189+
1. During SSR, renders a `<script>` tag with the provided code
190+
2. The script executes immediately when the browser parses the HTML (before React hydrates)
191+
3. After execution, the script removes itself from the DOM
192+
4. On client-side navigation, nothing is rendered (prevents duplicate execution)
193+
194+
### Preventing Hydration Warnings
195+
196+
If your script modifies the DOM before hydration (like adding a class to `<html>`), use `suppressHydrationWarning` to prevent React warnings:
197+
198+
```tsx
199+
export const Route = createRootRoute({
200+
component: () => (
201+
<html lang="en" suppressHydrationWarning>
202+
<head>
203+
<HeadContent />
204+
</head>
205+
<body>
206+
<ThemeProvider>
207+
<Outlet />
208+
</ThemeProvider>
209+
<Scripts />
210+
</body>
211+
</html>
212+
),
213+
})
214+
```
215+
216+
### Common Use Cases
217+
218+
- **Theme/dark mode detection** - Apply theme class before hydration to prevent flash
219+
- **Feature detection** - Check browser capabilities before rendering
220+
- **Analytics initialization** - Initialize tracking before user interaction
221+
- **Critical path setup** - Any JavaScript that must run before hydration

docs/router/framework/react/guide/static-route-data.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,73 @@ declare module '@tanstack/react-router' {
7979
```
8080

8181
As long as there are any required properties on the `StaticDataRouteOption`, you'll be required to pass in an object.
82+
83+
## Common Patterns
84+
85+
### Controlling Layout Visibility
86+
87+
Use staticData to control which routes show or hide layout elements:
88+
89+
```tsx
90+
// routes/admin/route.tsx
91+
export const Route = createFileRoute('/admin')({
92+
staticData: { showNavbar: false },
93+
component: AdminLayout,
94+
})
95+
```
96+
97+
```tsx
98+
// routes/__root.tsx
99+
function RootComponent() {
100+
const showNavbar = useMatches({
101+
select: (matches) =>
102+
!matches.some((m) => m.staticData?.showNavbar === false),
103+
})
104+
105+
return showNavbar ? (
106+
<Navbar>
107+
<Outlet />
108+
</Navbar>
109+
) : (
110+
<Outlet />
111+
)
112+
}
113+
```
114+
115+
### Route Titles for Breadcrumbs
116+
117+
```tsx
118+
// routes/posts/$postId.tsx
119+
export const Route = createFileRoute('/posts/$postId')({
120+
staticData: {
121+
getTitle: () => 'Post Details',
122+
},
123+
})
124+
```
125+
126+
```tsx
127+
// In a Breadcrumb component
128+
function Breadcrumbs() {
129+
const matches = useMatches()
130+
131+
return (
132+
<nav>
133+
{matches
134+
.filter((m) => m.staticData?.getTitle)
135+
.map((m) => (
136+
<span key={m.id}>{m.staticData.getTitle()}</span>
137+
))}
138+
</nav>
139+
)
140+
}
141+
```
142+
143+
### When to Use staticData vs Context
144+
145+
| staticData | context |
146+
| -------------------------------------- | ------------------------------- |
147+
| Synchronous, defined at route creation | Can be async (via `beforeLoad`) |
148+
| Available before loading starts | Can depend on params/search |
149+
| Same for all instances of a route | Passed down to child routes |
150+
151+
Use staticData for static route metadata. Use context for dynamic data or auth state that varies per request.

docs/start/framework/react/guide/server-functions.md

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,70 @@ function PostList() {
6666
}
6767
```
6868

69+
## File Organization
70+
71+
For larger applications, consider organizing server-side code into separate files. Here's one approach:
72+
73+
```
74+
src/utils/
75+
├── users.functions.ts # Server function wrappers (createServerFn)
76+
├── users.server.ts # Server-only helpers (DB queries, internal logic)
77+
└── schemas.ts # Shared validation schemas (client-safe)
78+
```
79+
80+
- **`.functions.ts`** - Export `createServerFn` wrappers, safe to import anywhere
81+
- **`.server.ts`** - Server-only code, only imported inside server function handlers
82+
- **`.ts`** (no suffix) - Client-safe code (types, schemas, constants)
83+
84+
### Example
85+
86+
```tsx
87+
// users.server.ts - Server-only helpers
88+
import { db } from '~/db'
89+
90+
export async function findUserById(id: string) {
91+
return db.query.users.findFirst({ where: eq(users.id, id) })
92+
}
93+
```
94+
95+
```tsx
96+
// users.functions.ts - Server functions
97+
import { createServerFn } from '@tanstack/react-start'
98+
import { findUserById } from './users.server'
99+
100+
export const getUser = createServerFn({ method: 'GET' })
101+
.inputValidator((data: { id: string }) => data)
102+
.handler(async ({ data }) => {
103+
return findUserById(data.id)
104+
})
105+
```
106+
107+
### Static Imports Are Safe
108+
109+
Server functions can be statically imported in any file, including client components:
110+
111+
```tsx
112+
// ✅ Safe - build process handles environment shaking
113+
import { getUser } from '~/utils/users.functions'
114+
115+
function UserProfile({ id }) {
116+
const { data } = useQuery({
117+
queryKey: ['user', id],
118+
queryFn: () => getUser({ data: { id } }),
119+
})
120+
}
121+
```
122+
123+
The build process replaces server function implementations with RPC stubs in client bundles. The actual server code never reaches the browser.
124+
125+
> [!WARNING]
126+
> Avoid dynamic imports for server functions:
127+
>
128+
> ```tsx
129+
> // ❌ Can cause bundler issues
130+
> const { getUser } = await import('~/utils/users.functions')
131+
> ```
132+
69133
## Parameters & Validation
70134
71135
Server functions accept a single `data` parameter. Since they cross the network boundary, validation ensures type safety and runtime correctness.
@@ -197,12 +261,46 @@ For more advanced server function patterns and features, see these dedicated gui
197261

198262
### Server Context & Request Handling
199263

200-
Access request headers, cookies, and response customization:
264+
Access request headers, cookies, and customize responses:
265+
266+
```tsx
267+
import { createServerFn } from '@tanstack/react-start'
268+
import {
269+
getRequest,
270+
getRequestHeader,
271+
setResponseHeaders,
272+
setResponseStatus,
273+
} from '@tanstack/react-start/server'
274+
275+
export const getCachedData = createServerFn({ method: 'GET' }).handler(
276+
async () => {
277+
// Access the incoming request
278+
const request = getRequest()
279+
const authHeader = getRequestHeader('Authorization')
280+
281+
// Set response headers (e.g., for caching)
282+
setResponseHeaders(
283+
new Headers({
284+
'Cache-Control': 'public, max-age=300',
285+
'CDN-Cache-Control': 'max-age=3600, stale-while-revalidate=600',
286+
}),
287+
)
288+
289+
// Optionally set status code
290+
setResponseStatus(200)
291+
292+
return fetchData()
293+
},
294+
)
295+
```
296+
297+
Available utilities:
201298

202-
- `getRequest()` - Access the full request object
203-
- `getRequestHeader()` - Read specific headers
204-
- `setResponseHeader()` - Set custom response headers
205-
- `setResponseStatus()` - Custom status codes
299+
- `getRequest()` - Access the full Request object
300+
- `getRequestHeader(name)` - Read a specific request header
301+
- `setResponseHeader(name, value)` - Set a single response header
302+
- `setResponseHeaders(headers)` - Set multiple response headers via Headers object
303+
- `setResponseStatus(code)` - Set the HTTP status code
206304

207305
### Streaming
208306

0 commit comments

Comments
 (0)