Server-Side Rendering in Next.js: A Backend Engineer's Notes

December 10, 2025 (10mo ago)·3 min read
#Next.js#React#Rendering#Web

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.