I Made Data Feel Faster Without Actually Speeding It Up - Pt 6
Series: Part 6 of Rendering with Intent
Breaking down how I learned to choose rendering strategies intentionally rather than relying on defaults.
Users hate waiting. They’ll tolerate a slow process if it feels like it’s moving, but they lose patience when nothing happens on screen.
That idea guided how I started using React Suspense and Next.js streaming in WonderBook.
The illusion of speed
Streaming doesn’t make your app faster in a technical sense. It just changes what users see while the real work continues in the background.
Instead of one big payload after a long pause, the server sends chunks as soon as they’re ready. The page builds up piece by piece, and the user sees progress instead of emptiness.
That changes how the experience feels.
How it works conceptually
A streaming route renders parts of a page as data arrives. Suspense boundaries act like safety nets. They show placeholders until a component’s data is ready, then swap it in.
// app/dashboard/page.tsx import { Suspense } from 'react'; import RecentActivity from './_components/recent-activity'; import SummaryCard from './_components/summary-card'; export default async function DashboardPage() { return ( <div className="space-y-8"> <SummaryCard /> <Suspense fallback={<p>Loading activity...</p>}> <RecentActivity /> </Suspense> </div> ); }
// app/dashboard/_components/recent-activity.tsx export default async function RecentActivity() { const logs = await fetchActivityLogs(); return ( <ul> {logs.map(log => ( <li key={log.id}>{log.message}</li> ))} </ul> ); }
The summary card renders first while activity logs stream in later. From the user’s perspective, the dashboard loads almost instantly.
What it changed for WonderBook
In WonderBook, story generation could take several seconds. Before streaming, the screen stayed empty until everything finished. Now, a message appears right away: “Building your story…”. Then one section loads at a time, title, chapter preview, and finally the full text.
Each update gives the user something real to look at, which shortens perceived wait time by half.
It also makes long-running AI tasks feel controlled instead of uncertain.
When streaming matters most
Streaming shines when you have:
- Long or unpredictable tasks, AI calls, database aggregation, file generation.
- Independent sections, parts of a page that can render separately.
- Users who would otherwise stare at a blank screen.
If the user can start reading, scrolling, or editing while something else loads, you’ve succeeded.
Guardrails to keep it stable
- Use Suspense boundaries intentionally. Each one adds complexity.
- Keep fallbacks simple. A sentence or skeleton works better than animation overload.
- Measure time-to-first-byte and time-to-interaction. The goal is perception, not just numbers.
- Test over real network conditions. Streaming helps most on slower connections.
What I learned
- People respond to movement more than milliseconds.
- Streaming isn’t only about bandwidth; it’s about attention.
- Suspense makes partial loading predictable instead of glitchy.
- The best streaming setup feels invisible, it just flows.
Streaming helped WonderBook feel calm under heavy AI workloads. It kept users engaged while data was still on its way.
Sometimes good engineering isn’t about shaving seconds off a process. It’s about giving users something to believe in while they wait.
In case you missed the other posts, here they are:
The Rendering with Intent Series
- Part 1: Rendering Blindly Cost Me Speed, Money, and Sanity. Here’s How I Fixed It
- Part 2: Static Rendering. Doing the Work Before Anyone Asks For It
- Part 3: I Wanted Static Speed Without Serving Yesterday’s Data. ISR Was the Answer
- Part 4: Switched to SSR When “Good Enough” Started Causing Real Problems
- Part 5: When Static Pages Weren’t Enough, Client Components Took Over
- Part 6: I Made Data Feel Faster Without Actually Speeding It Up [You are here]