Dana Iti
UX.Front-end.Systems.
Dana Iti
How I WorkCase StudiesWritingContact
Reading mode
HomeWritingWhen 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.

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

Previous

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

Next

I Made Data Feel Faster Without Actually Speeding It Up

Related Posts

View all posts

© 2026 Dana Iti

·
AboutWhat's newContact

Related Posts

Engineering & Architecture

Next.js 16. Caching Finally Makes Sense

Next.js 16 flipped caching from implicit magic to explicit choice. Everything is dynamic by default now. You opt into caching instead of fighting mysterious behaviour.

22 Oct 2025•6 min
Next.jsCaching+2 More
Design & UX

Revamping My Website

My old site looked nice but felt wrong. This time I treated it like a system, not a portfolio. Every element had to justify why it existed or it got cut.

15 Oct 2025•7 min
DesignFrontend+3 More
AI

I Don’t Want AI to Replace Thinking. I Want It to Help People Think Better

Most AI tools throw answers at you. People don't need more answers. They need help thinking through the mess.

28 Sept 2025•2 min
FounderbaseAI+3 More