Modern web apps often mix fast and slow data fetches. You don’t want a single 300ms API call to make the whole page feel sluggish. In TanStack Start, you can achieve true partial loading using React Suspense and TanStack Query — so one component shows a loading state while everything else renders instantly.
The Problem
Imagine a dashboard page:
- A statistics overview loads in 2ms
- A heavy analytics chart loads in 300ms
If you fetch everything together, users see a full-page loading state until the slowest request completes — resulting in a poor user experience.
The Solution: Component-Level Suspense + useSuspenseQuery
TanStack Start (built on React) supports streaming SSR and fine-grained loading states. The key is to wrap only the slow component with a <Suspense> boundary.
1. Page / Route Component
// app/routes/dashboard.tsx
import { Suspense } from 'react';
import { FastStats } from './components/FastStats';
import { SlowAnalytics } from './components/SlowAnalytics';
export default function Dashboard() {
return (
<div className="space-y-8 p-6">
<h1 className="text-3xl font-bold">Dashboard</h1>
{/* Fast component renders immediately */}
<FastStats />
{/* Only this section waits */}
<Suspense fallback={<AnalyticsSkeleton />}>
<SlowAnalytics />
</Suspense>
{/* Other components continue rendering */}
<QuickLinks />
</div>
);
}
2. The Slow Component (Using useSuspenseQuery)
// components/SlowAnalytics.tsx
import { useSuspenseQuery } from '@tanstack/react-query';
async function fetchAnalytics() {
// Simulate real API delay
await new Promise((resolve) => setTimeout(resolve, 300));
const res = await fetch('/api/analytics');
return res.json();
}
export function SlowAnalytics() {
const { data } = useSuspenseQuery({
queryKey: ['analytics'],
queryFn: fetchAnalytics,
});
return (
<div className="bg-white rounded-xl shadow p-6">
<h2 className="text-xl font-semibold mb-4">Analytics Overview</h2>
{/* Render your charts, tables, etc. */}
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
function AnalyticsSkeleton() {
return (
<div className="bg-white rounded-xl shadow p-6 animate-pulse">
<div className="h-6 bg-gray-200 rounded w-1/3 mb-6"></div>
<div className="space-y-4">
<div className="h-40 bg-gray-100 rounded"></div>
</div>
</div>
);
}
3. The Fast Component
// components/FastStats.tsx
import { useQuery } from '@tanstack/react-query';
export function FastStats() {
const { data } = useQuery({
queryKey: ['stats'],
queryFn: async () => {
await new Promise((r) => setTimeout(r, 2));
return { users: 1243, revenue: 45820 };
},
});
return (
<div className="grid grid-cols-3 gap-4">
{/* Stats cards */}
</div>
);
}
Why This Works So Well in TanStack Start
- Streaming SSR: The server sends HTML for the fast parts immediately. The slow component streams in later.
- useSuspenseQuery: Automatically suspends the component (throws a promise) until data is ready.
- Independent Loading States: Only the wrapped component shows loading — the rest of the UI stays interactive.
- Great for UX: Users can start reading and interacting while heavy sections load.
Pro Tips
- Place
as close as possible to the async component for better granularity. - Error Handling: Wrap with
or use TanStack Router’s errorComponent. - Multiple Slow Components:
<Suspense fallback={<SkeletonA />}>
<ComponentA />
</Suspense>
<Suspense fallback={<SkeletonB />}>
<ComponentB />
</Suspense>
- Server Components: You can also return promises directly from server components and use React.use() + Suspense.
- Prefetching: Use queryClient.prefetchQuery() in route loaders for even faster perceived performance.
Final Thoughts
TanStack Start + TanStack Query gives you one of the most powerful data-fetching experiences available in React today. By leveraging Suspense boundaries, you can stop treating your page as a single unit and start thinking in independent async components.
This pattern scales beautifully for complex dashboards, e-commerce product pages, user profiles — anywhere you have mixed-speed data requirements.
Member discussion
0 commentsStart the conversation
Become a member of >hacksubset_ to start commenting.
Already a member? Sign in