UI System Design in Next JS

Next.js

Mental Model First: Next.js is a React framework that moves work to the server — rendering, data fetching, caching, routing — so the client receives less JavaScript and more ready-to-use HTML. Every decision in Next.js is about where and when code runs: build time, server request time, or client.


Routing — App Router vs Pages Router

These are two completely different routing paradigms. App Router (Next.js 13+) is the current standard. Pages Router still works and is in millions of production apps — you must understand both.

Skill Core Concepts App Router (/app) Pages Router (/pages) Resources
File-based Routing Folder/file structure defines URL structure — no router config needed app/dashboard/page.tsx/dashboard. Special files: page, layout, loading, error, not-found pages/dashboard.tsx/dashboard. _app.tsx (global wrapper), _document.tsx (HTML shell) Next.js – App Router
Layouts Persistent UI that wraps child routes. Layouts do not re-render on navigation — state is preserved layout.tsx files nest automatically. Root layout required. Layouts can fetch their own data _app.tsx for global layout; custom layout pattern using per-page getLayout function Next.js – Layouts
Dynamic Routes URL segments as parameters app/blog/[slug]/page.tsxparams.slug; [...slug] for catch-all; [[...slug]] for optional pages/blog/[slug].tsxuseRouter().query.slug or getStaticProps({ params }) Next.js – Dynamic Routes
Nested Routes & Parallel Routes Render multiple independent route segments in the same layout Parallel Routes via @slot folders — render sibling pages (e.g., modal + page simultaneously). Intercepting Routes via (.) convention Not available Next.js – Parallel Routes
Route Groups Organize routes without affecting URL structure. Share layouts among a subset of routes (marketing)/about/page.tsx/about (parens excluded from URL) Not available Next.js – Route Groups
Navigation Client-side navigation without full page reload <Link> component (prefetches on hover), useRouter() for programmatic nav, redirect() for server-side redirects <Link> component, useRouter().push(), router.replace() Next.js – Linking

Rendering Strategies

The core question: when is the HTML generated? Each strategy has different performance, freshness, and infrastructure tradeoffs.

Strategy When HTML is Generated App Router Approach Pages Router Approach Best For
SSG – Static Site Generation Once at build time fetch(url, { cache: 'force-cache' }) inside async Server Component; generateStaticParams for dynamic paths getStaticProps + getStaticPaths Marketing pages, blogs, docs — content that rarely changes
SSR – Server-Side Rendering On every request fetch(url, { cache: 'no-store' }) or const data = await fetchData() with no caching in a Server Component getServerSideProps — runs on every request Dashboards with real-time data, personalized pages, auth-protected content
ISR – Incremental Static Regeneration At build time, revalidated after N seconds or on-demand fetch(url, { next: { revalidate: 60 } }) or export const revalidate = 60 at segment level getStaticProps with revalidate: 60 return value E-commerce product pages, news sites — mostly static but needs freshness
CSR – Client-Side Rendering In the browser after JS loads Add 'use client' + TanStack Query / SWR for data fetching — opt out of server rendering useEffect + fetch, or SWR/React Query on page component Highly interactive sections, real-time data (dashboards, charts, user feeds)
Streaming Progressively from the server as components resolve Wrap slow components in <Suspense fallback={...}>. loading.tsx auto-wraps the entire segment Not available natively Pages with mixed fast/slow data sources — avoid blocking the whole page on one slow query

Server Components vs Client Components

The most important mental model in the App Router. All components are Server Components by default.

Aspect Server Components Client Components
Where they run Node.js server — never shipped to browser Browser (+ server for initial HTML via SSR)
How to declare Default — no directive needed Add 'use client' at the top of the file
Can use async/await, direct DB access, secrets/env vars, server-only APIs Hooks (useState, useEffect), browser APIs, event listeners
Cannot use Hooks, browser APIs, window, event handlers Direct DB access, server-only modules
Bundle impact Zero JS added to client bundle JavaScript is shipped to and executed by the browser
When to use Layout, data fetching, non-interactive UI Interactive UI, forms, animations, anything that responds to user input
Composition rule Server Components can import Client Components Client Components cannot import Server Components (but can receive them as children props)

Data Fetching

App Router fundamentally changed data fetching. The extended fetch API and React's cache system replace getStaticProps / getServerSideProps entirely.

Skill Core Concepts Tools & Techniques Key Details Resources
Server Component Fetching async/await directly in the component body — no useEffect, no loading state needed. React deduplicates identical fetch calls automatically Extended fetch with cache options cache: 'force-cache' (SSG), cache: 'no-store' (SSR), next: { revalidate: N } (ISR). Fetch at the component level, not the page level — only fetch what each component needs Next.js – Data Fetching
Server Actions Next.js 14+ feature. Async server functions called directly from forms or client components — no API route needed for mutations. Replace most POST endpoints 'use server' directive (inline or in a separate file) Form action={serverAction}, useFormState / useActionState for progressive enhancement, automatic revalidation with revalidatePath / revalidateTag Next.js – Server Actions
Route Handlers API endpoints inside the App Router. Replace pages/api/ routes. Used for webhooks, OAuth callbacks, or when you need a real HTTP endpoint app/api/route/route.ts with exported GET, POST, PATCH, DELETE functions Request/Response Web API, no Express needed, streaming responses supported, NextRequest / NextResponse helpers Next.js – Route Handlers
ORM / Database Access Query the database directly from Server Components or Server Actions — no intermediary API layer needed Prisma (type-safe, popular), Drizzle (lightweight, SQL-first), Mongoose (MongoDB) Keep DB logic in a lib/db.ts or server/ folder; never import server-only modules into Client Components; use server-only package to guard Prisma + Next.js
Caching & Revalidation Next.js has a multi-layer cache. Understanding it prevents stale data bugs revalidatePath, revalidateTag, unstable_cache Tag fetches with next: { tags: ['products'] } → call revalidateTag('products') on mutation for surgical cache invalidation Next.js – Caching