// LOADING
// LOADING
// LOADING_ARTICLE
react server components (rsc) are the biggest shift in how we build react apps since hooks. instead of every component shipping to the browser, the app router renders most of your tree on the server and sends the client only what genuinely needs interactivity. the result is less javascript, faster first loads, and a much simpler data-fetching story.
in a traditional single-page app, the browser downloads a large javascript bundle, boots react, then makes a second round trip to fetch data. the user stares at a spinner while all of that happens. we papered over it for years with SSR and hydration, but the bundle kept growing.
server components flip the model: components that only display data never reach the browser at all. they run once on the server, produce html, and disappear. only client components — the parts with state, effects, or event handlers — get hydrated.
think of it as two zones:
await data directly. they cannot use useState, useEffect, or browser APIs."use client"): can use hooks and respond to user input, but run in the browser like classic react.a healthy app router tree is mostly server components with small client "islands" for the interactive bits. if you are deciding where shared state should live in those islands, my notes on local vs global state in react & next.js cover when a hook beats a global store.
this is where rsc shines. an async server component can fetch data inline, with no useEffect, no loading flag, and no client-side waterfall:
tsx// app/notelogs/page.tsx — a server component async function Notelogs() { // runs on the server; the await never reaches the browser const posts = await getPublishedBlogs(); return ( <ul> {posts.map((p) => ( <li key={p.slug}>{p.title}</li> ))} </ul> ); }
because the fetch happens on the server, your api keys and database connection stay private. it also pairs naturally with a clean backend — the same principles in how to design APIs for Next.js applications apply when those server components call your route handlers or services.
"use client" boundarythe rule that trips people up: "use client" marks an entry point, not a single file. everything imported into a client component also becomes client code. so push the directive as far down the tree as you can — wrap the tiny interactive piece, not the whole page.
a common pattern is a server component that fetches data and passes it as props into a small client component that handles the interaction.
the app router can stream html as it is ready. wrap a slow server component in <Suspense> and the rest of the page paints immediately while that section resolves:
tsx<Suspense fallback={<Skeleton />}> <SlowComments /> </Suspense>
this is great for perceived performance — the user sees the shell instantly. it directly improves the metrics i break down in core web vitals in 2026.
rsc is not a replacement for static generation or caching — it is the rendering model underneath them. you still choose whether a route is static, dynamic, or revalidated. if those trade-offs are fuzzy, read server-side rendering vs static site generation in Next.js for the decision matrix.
"use client" only when you need interactivity."use client" at the top of a layout and accidentally dragging the whole app into the bundle.useState in a server component (you will get an error — move it to a client island).react server components make the server the default again, and that is a good thing: less shipped javascript, private data access, and simpler fetching. start small — convert one page, move "use client" to the leaves, and measure. if you want to see these ideas in production code, browse my projects.