Dana Iti
UX.Front-end.Systems.
Dana Iti
How I WorkCase StudiesWritingContact
Reading mode
HomeWritingEngineering & ArchitectureWhen Static Pages Weren’t Enough, Client Components Took Over

When Static Pages Weren’t Enough, Client Components Took Over - Pt 5

21 September 2025•3 min read
•By Dana Iti•Engineering & Architecture
Next.jsClient ComponentsReactFrontend ArchitectureSystem Design
Reading mode

Series: Part 5 of Rendering with Intent

Breaking down how I learned to choose rendering strategies intentionally rather than relying on defaults.

Previous: Switched to SSR When “Good Enough” Started Causing Real ProblemsNext: I Made Data Feel Faster Without Actually Speeding It UpView all posts in this series

Most of WonderBook’s interface could run on the server or be cached. Some parts needed to stay awake. They had to listen, respond, and update in real time.

That job belonged to Client Components.

When interaction matters more than static speed

Next.js 15 made one thing clear: you don’t mark something "use client" unless it needs it. Once you do, you’re opting into hydration, JavaScript, and reactivity.

For WonderBook, this trade-off was worth it in only a few places:

  1. Live Editors, Chapters, titles, and covers update instantly while autosaving in the background.
  2. Realtime Likes, On the Explore page, likes appear instantly through Supabase Realtime.
  3. Interactive Forms, Sign-up and payment flows rely on validation, focus states, and animation.

Everything else could stay server-rendered.

A simple principle guided the structure:

If it moves, it belongs to the client. If it stays still, render it once and serve it fast.

A page with both worlds

Client Components worked best when paired with a Server Component that prepared their data. The server fetched, validated, and structured. The client reacted and displayed.

// app/dashboard/page.tsx export const revalidate = 120 // 2 minutes export default async function DashboardPage() { const user = await getCurrentUser() const projects = await getRecentProjects(user.id) return <DashboardClient initialProjects={projects} userId={user.id} /> }
// app/dashboard/_components/dashboard-client.tsx 'use client' import { useEffect, useState } from 'react' export default function DashboardClient({ initialProjects, userId }) { const [projects, setProjects] = useState(initialProjects) useEffect(() => { const interval = setInterval(async () => { const response = await fetch(`/api/projects?user=${userId}`) const data = await response.json() setProjects(data) }, 10000) return () => clearInterval(interval) }, [userId]) return ( <div className="grid gap-4"> {projects.map(project => ( <div key={project.id} className="p-4 border rounded"> <h3>{project.name}</h3> <p>{project.status}</p> </div> ))} </div> ) }

Server Components handled data. Client Components handled interaction. The pattern kept pages fast and users engaged without pushing everything into the browser.

Keeping real-time under control

Adding reactivity everywhere can cause chaos. To prevent that, every Client Component followed three rules:

  1. Receive, don’t fetch. The server prepared data first. The client only listened for changes.
  2. Render optimistically. When a user liked a story, the UI updated right away and synced later.
  3. Scope carefully. Only the parts that truly needed interaction were marked "use client".

This kept WonderBook responsive without heavy bundles or race conditions.

The human layer

When users typed in the story editor, words appeared instantly. When they tapped like, the heart filled before the database confirmed it.

The experience felt immediate and personal. It reacted the way people expect technology to respond when it’s paying attention.

Client Components made that possible. They didn’t replace the server; they complemented it.

What I learned

  • Start from the server. Use static or server rendering until you find a reason not to.
  • Add interactivity with purpose. Every "use client" should serve a visible experience, not convenience.
  • Keep boundaries clean. Server Components prepare; Client Components react.
  • Test the feel. Interaction isn’t about data accuracy, it’s about rhythm and timing.

Client Components gave WonderBook its sense of presence. They turned a static story builder into something responsive and alive.

Rendering strategy isn’t just about speed. It’s about how close the product feels to the user.

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 [You are here]
  • Part 6: I Made Data Feel Faster Without Actually Speeding It Up
When interaction matters more than static speedA page with both worldsKeeping real-time under controlThe human layerWhat I learned

Previous

I Made Data Feel Faster Without Actually Speeding It Up

Next

Switched to SSR When “Good Enough” Started Causing Real Problems

Related Posts

View all posts

© 2026 Dana Iti

·
AboutWhat's newContact

Related Posts

Engineering & Architecture

Static Rendering. Doing the Work Before Anyone Asks For It

Static rendering does the work before anyone asks. That one change made pages load 10x faster.

2 Sept 2025•4 min
Next.jsRendering+3 More
Engineering & Architecture

I Made Data Feel Faster Without Actually Speeding It Up

I made WonderBook feel faster without actually making it faster. Here's how streaming changed everything.

25 Sept 2025•3 min
Next.jsReact+4 More
Engineering & Architecture

Rendering Blindly Cost Me Speed, Money, and Sanity. Here’s How I Fixed It

I built WonderBook without understanding rendering strategies. Cost me speed, cost me money, nearly cost me my sanity. Then I learnt how it actually works.

28 Aug 2025•4 min
Next.jsFrontend Architecture+2 More