Web DevelopmentBy Abdul Rahman29 Apr 20263 min read
React Server Components and the Next.js App Router Explained
Server Components change where React code runs. What they are, how they differ from Client Components, and the patterns that keep Next.js apps fast and secure.

React Server Components (RSC) are the biggest change to how React applications are built since hooks. In the Next.js App Router they're the default, which means every Next.js developer now needs a clear mental model of where their code runs.
This portfolio is built on the App Router, so the patterns below come straight from real use.
Server Components fetch data and render HTML; only interactive islands ship JavaScript to the browser.
Server Components vs Client Components
Server Components render on the server only:
- they can be
asyncand fetch data directly (database, file system, internal APIs), - their code is never sent to the browser,
- they can safely use secrets and server-only libraries,
- they can't use state, effects or browser APIs.
Client Components, marked with "use client", are what React components always were: rendered on the server for the initial HTML, then hydrated in the browser so they can respond to clicks, keep state and use effects.
Data Fetching Without the Waterfall
In a Server Component, data fetching is just an await:
// app/blog/page.tsx — a Server Component
export const revalidate = 300; // re-generate at most every 5 minutes
export default async function BlogPage() {
const posts = await getPublishedPosts(); // runs on the server
return <BlogBrowser posts={posts} />; // Client Component for filtering
}
No useEffect, no loading spinner for the initial content, no API route just to feed your own UI. The blog on this site works exactly like this: posts are fetched on the server, and only the category filter tabs run as a Client Component.
The Boundary Rule
"use client" marks a boundary: that component and everything it imports become client code. To keep bundles small:
- Push
"use client"down to the smallest interactive component, not the whole page. - Pass Server Components as
childrento Client Components when you need interactivity around server-rendered content. - Only pass serialisable props (plain objects, strings, numbers, dates as strings) from server to client.
Security: Keep Server Code on the Server
Because Server Components can access secrets directly, it's easy to accidentally import server code into a client file. Two safeguards:
- Import the
server-onlypackage in modules that must never reach the browser; the build fails if a Client Component imports them. - Remember that only environment variables prefixed with
NEXT_PUBLIC_are exposed to the browser.
Server Actions — functions marked "use server" that forms and buttons can call — are public HTTP endpoints. Always re-check authentication and authorisation inside every action.
Caching and Revalidation
The App Router can cache rendered output and fetched data. Choose deliberately per route:
- Static with revalidation (
export const revalidate = 300) for content like blogs. - Dynamic (
export const dynamic = "force-dynamic") for per-user pages such as dashboards. - On-demand revalidation with
revalidatePath()after data changes — for example when an admin publishes a post.
Streaming
Wrap slow sections in <Suspense> to stream the page: the shell appears immediately and slower parts fill in as their data arrives, improving perceived performance.
Key Takeaways
- Server Components render on the server, can fetch data directly and ship no JavaScript.
- Add
"use client"only to the interactive leaves of the tree. - Protect server-only code with
server-onlyand re-check auth in every Server Action. - Choose static, revalidated or dynamic rendering per route, and stream slow sections.


