- React
- Next.js 16
- Architecture
- Server Components
Building Modern React Applications with the Next.js App Router
A current guide to Server Components, client boundaries, streaming, Cache Components, Server Actions, and production tradeoffs in Next.js 16.
- Published
- Updated
- Reading time
- 10 min read

On this page
The App Router is most effective when it changes where work happens—not when it simply recreates a client-rendered application inside a new directory. Begin with server-rendered output, add client boundaries around interaction, and make caching an explicit product decision.
This guide reflects the Next.js 16 model used by this portfolio: Server Components by default, streaming through Suspense, Cache Components for deliberate reuse, and Server Actions for mutations. The examples focus on the production tradeoffs behind that model rather than presenting every new API as mandatory.
Start with a server-rendered route
A page component can fetch data, read request-bound values, and render meaningful HTML without shipping its implementation to the browser. This is a strong default for article pages, product detail views, account summaries, and any screen whose first job is to be read.
- Fetch from the source on the server instead of calling your own public API route.
- Keep secrets and privileged SDKs in modules guarded by
server-only. - Validate remote data at the boundary; TypeScript does not validate network payloads.
- Return useful HTML before adding optimistic behavior or client-side state.
The server-first default is not a ban on client code. It is a way to make each client dependency justify its cost.
Place client boundaries around interaction
A 'use client' directive defines a module boundary. Everything imported beneath that boundary becomes part of the client graph, so placing it high in the tree can turn an otherwise static route into a large JavaScript payload.
- Keep content, layout, and data composition in Server Components.
- Extract small controls for menus, filters, forms, and browser-only APIs.
- Pass serializable data and rendered children into those controls.
- Avoid global providers when a local state boundary serves the same task.
The practical test is simple: if JavaScript fails to load, should the visitor still be able to understand the page? For most portfolio, editorial, and commerce content, the answer is yes.
Stream useful work, not a collection of spinners
Suspense allows the shell and fast content to arrive before slower regions. Good streaming preserves hierarchy: the visitor sees the title, task context, and stable layout while a secondary recommendation or expensive query resolves.
- Place boundaries around independently useful regions, not every component.
- Reserve the final space so late content does not shift the interface.
- Use specific skeletons or explanatory copy instead of generic full-page loaders.
- Handle expected empty and error states inside the same product context.
Streaming cannot repair a slow query. Measure the source, remove request waterfalls, and then use Suspense to improve how unavoidable latency is experienced.
Treat Cache Components as an explicit contract
Next.js 16 makes caching opt-in through Cache Components and the use cachedirective. That is healthier than assuming every fetch has the same lifetime. A marketing page, personalized account view, and live operations screen should not share one policy.
- Cache only work that is safe to reuse for the intended audience.
- Choose lifetimes from product freshness requirements, not convenience.
- Use cache tags or paths so a mutation can invalidate the exact stale material.
- Keep request-specific values outside shared cache scopes.
Read the official Cache Components guide before migrating an existing application; enabling the feature changes rendering and caching expectations across the route tree.
Design Server Actions as public trust boundaries
Server Actions can keep a mutation close to the form that invokes it, but convenience does not make them trusted. Treat every argument as user input and apply the same authorization, validation, rate limiting, and audit requirements as any other endpoint.
- Authorize the action against the current resource, not only the current session.
- Return field-level validation that a form can explain and recover from.
- Use optimistic UI only when the operation is safely reversible.
- Revalidate affected views after success and preserve useful input after failure.
A production checklist
Before calling an App Router migration complete, inspect the output rather than the folder structure. Verify the JavaScript shipped by important routes, server/client boundaries, cache behavior after mutations, streaming order, metadata, error recovery, and keyboard access.
The official Next.js 16 upgrade guide is the source of truth for changed defaults. Keep the framework upgrade separate from an unrelated visual redesign so performance or behavior regressions have a clear cause.
For the broader structure behind this site, read Designing a Portfolio as a Product System.