Apply Grey Haven's TanStack ecosystem patterns - Router file-based routing, Query data fetching with staleTime, and Start server functions. Use when building React applications with TanStack Start...
Follow Grey Haven Studio's patterns for TanStack Start, Router, and Query in React 19 applications.
Grey Haven uses the complete TanStack ecosystem:
src/routes/
āāā __root.tsx # Root layout (wraps all routes)
āāā index.tsx # Homepage (/)
āāā _authenticated/ # Protected routes group (underscore prefix)
ā āāā _layout.tsx # Auth layout wrapper
ā āāā dashboard.tsx # /dashboard
ā āāā settings/
ā āāā index.tsx # /settings
āāā users/
āāā index.tsx # /users
āāā $userId.tsx # /users/:userId (dynamic param)
Key conventions:
__root.tsx - Root layout with QueryClient provider_authenticated/ - Protected route groups (underscore prefix)_layout.tsx - Layout wrapper for route groups$param.tsx - Dynamic route parametersconst queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60000, // 1 minute default
retry: 1,
refetchOnWindowFocus: false,
},
},
});
// ā
CORRECT - Specific to general
queryKey: ["user", userId]
queryKey: ["users", { tenantId, page: 1 }]
queryKey: ["organizations", orgId, "teams"]
// ā WRONG
queryKey: [userId] // Missing resource type
queryKey: ["getUser", userId] // Don't include function name
queryKey: [{ id: userId }] // Object first is confusing
// ALWAYS include tenant_id parameter
export const getUserById = createServerFn("GET", async (
userId: string,
tenantId: string
) => {
const user = await db.query.users.findFirst({
where: and(
eq(users.id, userId),
eq(users.tenant_id, tenantId) // Multi-tenant isolation!
),
});
if (!user) throw new Error("User not found");
return user;
});
export const Route = createFileRoute("/_authenticated/dashboard")({
// Loader fetches data on server before rendering
loader: async ({ context }) => {
const tenantId = context.session.tenantId;
return await getDashboardData(tenantId);
},
component: DashboardPage,
});
function DashboardPage() {
const data = Route.useLoaderData(); // Type-safe loader data
return <div>...</div>;
}
const mutation = useMutation({
mutationFn: (data: UserUpdate) => updateUser(userId, data),
// Always invalidate queries after mutation
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user", userId] });
},
});
Grey Haven uses these staleTime defaults:
| Data Type | staleTime | Use Case |
|---|---|---|
| Auth data | 5 minutes | User sessions, tokens |
| User profiles | 1 minute | User details |
| Lists | 1 minute | Data tables, lists |
| Static/config | 10 minutes | Settings, configs |
| Realtime | 0 (always refetch) | Notifications |
const STALE_TIMES = {
auth: 5 * 60 * 1000, // 5 minutes
user: 1 * 60 * 1000, // 1 minute
list: 1 * 60 * 1000, // 1 minute
static: 10 * 60 * 1000, // 10 minutes
realtime: 0, // Always refetch
};
All supporting files are under 500 lines per Anthropic best practices:
examples/ - Complete code examples
reference/ - Configuration references
templates/ - Copy-paste ready templates
checklists/ - Pre-PR validation
Use this skill when:
These patterns are from Grey Haven's production template: