Next.js Server Boundaries: What Stays in the Framework, What Leaves

Next.js lays out so much that the server can do that plenty of people pour everything in without thinking first. After version 15 (released October 2024), the App Router and RSC have settled in; the tooling is mature enough that it’s worth stepping back and asking the plain question: what does each of these four acronyms actually solve, what belongs in the framework’s server, and what should leave.

Sort the four acronyms out first

SSR renders the page on the server on every request — right for pages that change by viewer or by the minute. SSG renders once at build time and serves a static file afterwards — right for the kind of page everyone sees identically and that rarely changes: marketing pages, documentation. ISR sits between them: it serves a cached version and regenerates on demand in the background, so you needn’t drop the whole site back to SSR for a little freshness, nor rebuild everything for a minor content tweak. Those three are the same concern at different points in time — when the HTML is generated, and how many times.

RSC is a different axis. It isn’t asking when the HTML is made; it’s asking whether a component’s JS gets shipped to the browser at all. A server component runs to completion on the server, serialises its result, and sends that across, so the logic and the dependencies it pulls in never enter the client bundle. That’s a real win for bundle size, and data fetching can sit closer to the server. But it isn’t a synonym for SSR, and conflating the two is exactly where people stop being able to say which part they actually wanted.

Nearly every mess I’ve seen comes from tangling “when is the HTML made” together with “does the JS ship to the client.” Split them into two independent axes in your head first, and the decisions downstream come out clean.

Aggregation for the browser vs. risk-bearing backend logic

The line worth drawing isn’t between those four acronyms. It’s elsewhere: the code you put on the server — is it aggregating the data a screen needs for the browser, or is it backend-facing, cross-service, risk-bearing logic?

API routes, server actions, and server components all run on the server, but the responsibilities shouldn’t blur. The first is aggregation: what a page shows is scattered across three or four downstreams, so in Next’s server segment you fetch them, stitch them into the shape the screen wants, and trim the fields you don’t need. That kind of for-this-screen aggregation belongs in the Next server quite happily — it already knows best what its own pages need.

The second is a different animal. Transactions spanning several services, writes to core data, calls carrying downstream addresses and internal auth, third-party API keys — that lot I’d move out, into a standalone BFF or backend service, rather than wedge it into the framework server. The reasoning is the line I always take: a boundary should be held up by structure, not by discipline. A server action looks harmless, yet it can import straight into your data layer and read keys out of the env — so you’re betting on whether some bit of that code gets dragged into the client by accident, betting on whether the build tool drew the line cleanly. Lock the risk-bearing logic behind a separate service with a clear boundary, and the Next side doesn’t even need to know those endpoints exist. That’s security by structure.

Then there’s lock-in. Aggregation tied to Next’s server runtime is no great worry — it’s part of the presentation layer anyway. But grow your cross-service core logic, your auth, and your outbound integrations inside server actions and route handlers, and the day you want to switch frameworks or move the stack to another runtime, you’ll find the business logic fused to Next’s execution model. Pull that layer outside the framework and Next is just one of its callers; swap the framework or don’t, the logic doesn’t move.

Before you reach for it, admit the cost

I’m not running RSC down. Keeping server-bound things on the server is a direction I agree with. But be honest: the complexity and the cost are real. The boundary between server and client components, where to draw "use client", which props can be serialised across that line — all of it is fresh mental overhead. Hydration isn’t free either; the markup the server sends has to be picked up on the client, and that overhead bites back on interaction-heavy pages.

The criticism of Next’s ecosystem lock-in isn’t coming out of nowhere. The App Router’s caching behaviour and RSC’s boundary rules carry a fair amount of framework-specific knowledge — you’re learning “how Next thinks” more than “how the web works.” That sits with a long-standing unease of mine: when a framework’s abstraction runs too deep, people slowly lose their feel for HTTP and for what the browser is actually doing. So decide up front which part you came for: the static speed of SSG, the on-demand freshness of ISR, or the slimmer bundle of RSC. Take that part, and don’t flip everything else on just because the framework supports it. It building is not the same as the design being sound.

Key takeaways

  • SSR / SSG / ISR are about when HTML is made and how often; RSC is about whether JS ships to the client. Treat them as two separate axes.
  • Screen-level aggregation for the browser belongs in the Next server — it knows its own pages best.
  • Cross-service logic, core-data writes, and calls carrying downstream auth and keys go to a standalone BFF, not the framework server.
  • Security by structure, not discipline: keep the endpoints outside the framework and the client never knows they exist.
  • RSC and hydration carry real cost and lock-in; decide which part you need before turning everything on.

Sheng’s take, drafted with Claude · part of the 2026-06-13 blog renovation, paint still drying.