Server-Side Rendering in Next.js: A Backend Engineer's Notes
I've spent most of my career on the server: Java and Spring Boot at LXME, .NET at CarWale. When I built this portfolio with Next.js and the App Router, I expected the frontend to feel alien. Instead, it felt like a rendering layer I already understood — once I mapped it onto backend concepts.
Here's that map.
The one idea that unlocks it
In the App Router, every component is a server component by default. It runs on the server, never ships JavaScript to the browser, and can talk to a database or an internal service directly.
That's not a foreign concept — it's a controller that returns HTML instead of JSON.
// app/posts/page.tsx — runs on the server
export default async function Posts() {
const posts = await getBlogPosts(); // direct data access, no API hop
return <ul>{posts.map((p) => <li key={p.slug}>{p.title}</li>)}</ul>;
}No fetch to your own API. No serialization boundary you don't need. It's the same instinct as keeping a service call inside the process rather than bouncing through HTTP.
The client boundary is explicit
Where Spring has @Controller to mark a web entry point, Next.js has "use client". You opt in to shipping code to the browser, and only for the parts that need interactivity.
"use client";
import { useState } from "react";
export function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}Everything else stays on the server. This is exactly the discipline of drawing a service boundary — keep logic where it belongs, expose only what the client needs.
Caching is a first-class citizen
In Spring you think about two layers of cache: HTTP caching and your app-level cache (Redis, Caffeine). Next.js folds both into a single mental model with fetch caching and revalidation.
force-cache— fetch once, reuse (like a long TTL).revalidate: 60— stale-while-revalidate (like a high-throughput cache with a background refresh).no-store— bypass the cache (like hitting the source of truth directly).
Once I saw it as cache TTLs and revalidation windows, it stopped being magic.
Where the server truly ends
The blurriest part for a backend brain: some code runs at build time, some per-request, some in an edge runtime close to the user. Knowing where your code executes is the whole game.
| Runs at | Backend analogy |
|---|---|
| Build time | Batch job / static asset generation |
| Request time (Node) | Your Spring Boot controller |
| Edge runtime | A CDN-layer proxy |
The takeaway
Next.js didn't ask me to unlearn backend engineering. It asked me to relocate it. The App Router is a rendering pipeline with explicit boundaries, caching semantics, and a clear server/client split — all ideas I'd already internalised on the JVM.
If you're a backend engineer eyeing the frontend: you already have the hard half. The rest is syntax and a new vocabulary for the same trade-offs.