diff --git a/AGENTS.md b/AGENTS.md index c2801ac..7ca6e9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,10 +83,12 @@ Each concept has one owner file that covers it in full. Other files may overlap | Isolation levels, MVCC, indexes, WAL, DB replication setup, pooling | database | backend, system | | Caching strategies, rate-limiting algorithms, load balancing, CDN, observability (SLI/SLO), capacity math (Little's law), multi-region and DR tiers | system | backend, devops, os, aws | | REST/gRPC/GraphQL, pagination, idempotency keys, outbox, graceful shutdown, N+1, schema evolution, multi-tenancy, time handling | backend | system, database, distributed | -| AuthN, sessions, OAuth/OIDC, JWT, XSS, CSRF, CORS, clickjacking, crypto, TLS | security | backend, frontend | +| AuthN, sessions, OAuth/OIDC, JWT, XSS, CSRF, CORS, clickjacking, crypto, TLS | security | backend, frontend, nextjs | | Containers, Kubernetes, cloud, IaC, deployment strategies, zero-downtime migrations | devops | backend, database | | Browser event loop, JS language semantics | javascript | frontend, typescript | -| Rendering strategies, browser pipeline, Web Vitals, HTTP caching in the browser, browser storage, React | frontend | javascript, security | +| Rendering strategies, browser pipeline, Web Vitals, HTTP caching in the browser, browser storage | frontend | javascript, security, react, nextjs | +| React rendering model, hooks, client state management, Suspense and transitions, Server Components and Server Functions, hydration | react | frontend, nextjs | +| Next.js App Router, rendering and caching layers, `use cache`, routing conventions, Server Actions configuration, proxy, self-hosting | nextjs | react, frontend | | asyncio event loop | python | — | | TCP/UDP, HTTP versions, QUIC, DNS, IP addressing, NAT, MTU, BGP/anycast | networking | backend, system, devops | | Processes, signals, scheduling, virtual memory, OOM, file descriptors, epoll/io_uring, fsync, locks | os | devops, database, golang | diff --git a/README.md b/README.md index bfdda2f..29fcbb4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,9 @@ Each topic in `docs/topics/` is a brief grouped by subtopic, covering what an ex ### Web - [Backend](docs/topics/backend.md) — API design, idempotency and concurrency, service calls and webhooks, async work, data access, lifecycle and health, security, testing -- [Frontend](docs/topics/frontend.md) — rendering strategies, browser pipeline, CSS, performance, caching, React rendering and hooks, state management, accessibility +- [Frontend](docs/topics/frontend.md) — rendering strategies, browser pipeline, CSS, performance, caching, accessibility +- [Next.js](docs/topics/nextjs.md) — rendering and Cache Components, caching layers and invalidation, routing conventions, Server Actions, proxy and auth, build and deployment, built-in components, Pages Router +- [React](docs/topics/react.md) — rendering model, hooks, state management, performance, Server Components, Suspense and transitions, forms and Actions, hydration, error boundaries, events and refs, version changes - [Security](docs/topics/security.md) — server-side and browser-side attacks, auth and sessions, tokens and OAuth, authorization, cryptography, TLS, supply chain, threat modeling ### Infrastructure diff --git a/docs/index.md b/docs/index.md index 3e75eee..660d6fd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,7 +25,9 @@ Topic briefs on what engineers forget before technical interviews, grouped by su ### Web - [Backend](topics/backend.md) — API design, idempotency and concurrency, service calls and webhooks, async work, data access, lifecycle and health, security, testing -- [Frontend](topics/frontend.md) — rendering strategies, browser pipeline, CSS, performance, caching, React rendering and hooks, state management, accessibility +- [Frontend](topics/frontend.md) — rendering strategies, browser pipeline, CSS, performance, caching, accessibility +- [Next.js](topics/nextjs.md) — rendering and Cache Components, caching layers and invalidation, routing conventions, Server Actions, proxy and auth, build and deployment, built-in components, Pages Router +- [React](topics/react.md) — rendering model, hooks, state management, performance, Server Components, Suspense and transitions, forms and Actions, hydration, error boundaries, events and refs, version changes - [Security](topics/security.md) — server-side and browser-side attacks, auth and sessions, tokens and OAuth, authorization, cryptography, TLS, supply chain, threat modeling ### Infrastructure diff --git a/docs/topics/frontend.md b/docs/topics/frontend.md index b0c5115..01abce6 100644 --- a/docs/topics/frontend.md +++ b/docs/topics/frontend.md @@ -1,6 +1,6 @@ # Frontend -What experienced frontend engineers forget before an interview, grouped by subtopic. For language mechanics see [javascript.md](javascript.md) and [typescript.md](typescript.md); for web vulnerabilities and token storage see [security.md](security.md). +What experienced frontend engineers forget before an interview, grouped by subtopic. For language mechanics see [javascript.md](javascript.md) and [typescript.md](typescript.md); for web vulnerabilities and token storage see [security.md](security.md); for React and Next.js see [react.md](react.md) and [nextjs.md](nextjs.md). ## Rendering strategies @@ -17,12 +17,12 @@ What experienced frontend engineers forget before an interview, grouped by subto - **Streaming SSR** flushes HTML in chunks, so the shell paints before slow data-dependent sections resolve. - **Hydration** runs the render on the client and attaches listeners to the server HTML, reusing the DOM instead of recreating it. -- A **hydration mismatch** comes from render output that differs between server and client — `Date.now()`, `Math.random()`, `window` checks during render. +- A **hydration mismatch** (server and client render different output) can force the framework to re-render that part on the client — React's causes and fixes in [react.md](react.md). ### Islands and Server Components - **Islands architecture**: the page is static HTML; only interactive islands ship JS and hydrate independently. -- **React Server Components** render only on the server and send serialized output, not code — less JS, but props crossing into client components must be serializable. +- **React Server Components** render only on the server and send serialized output, not code — only Client Components ship JS and hydrate (see [react.md](react.md)). ### Client-side routing @@ -138,88 +138,6 @@ Any XSS can read all of them except `HttpOnly` cookies — token storage in [sec - **Service workers** implement cache-first, network-first, or stale-while-revalidate strategies and offline support. - A waiting service worker is a common reason users keep seeing the old deploy. -## React rendering model - -### Reconciliation and keys - -- **Keys** match list items across renders; an array index as key attaches the wrong state after reorder or filter. -- A component re-renders on its own state change, a **parent re-render** (unless memoized), or a consumed **context value** change. - -### State snapshots and resets - -- State is a **snapshot** per render: `setCount(count + 1)` twice adds 1; the updater form `setCount(c => c + 1)` twice adds 2. -- Changing a component's **`key`** remounts it and resets its state — the clean way to reset a form when the selected item changes. -- `useRef` holds a mutable value across renders without triggering a re-render; don't read or write it during render. - -### Error boundaries - -- An error boundary catches errors thrown while **rendering** its subtree and shows a fallback UI instead of unmounting the whole app. -- It's still a **class component** (`getDerivedStateFromError`, `componentDidCatch`) or the `react-error-boundary` package; there's no hook equivalent. -- It doesn't catch errors in event handlers or async code (`setTimeout`, promises) — handle those with `try/catch` and state. - -### Fiber and concurrent rendering - -- **Fiber** splits rendering into interruptible units: the render phase can pause, restart, or be discarded; the **commit phase** applies DOM changes synchronously. -- Render must therefore be **pure** — it may run more than once for one commit. -- React 18 batches all state updates automatically, including in timeouts and promises. - -### Transitions and Suspense - -- **`useTransition`**/`startTransition` mark an update as non-urgent, so typing stays responsive while a heavy re-render runs; `useDeferredValue` defers a derived value. -- **``** shows a fallback while children wait for code or data; with streaming SSR each boundary streams in separately. - -### React 19 APIs - -- **Actions**: async functions passed to `
` or `useActionState`, with pending state handled by React; `useOptimistic` shows the expected result before the server confirms. -- **`use(promise)`** reads a promise or context during render, suspending until it resolves; `ref` is a normal prop (no `forwardRef`). -- 19.2: **``** keeps hidden UI mounted with state preserved; `useEffectEvent` reads the latest props/state inside an effect without making them dependencies. - -### React Compiler - -- **React Compiler** (stable 1.0, **October 2025**) auto-memoizes components and values at build time, replacing most manual `useMemo`/`useCallback`/`memo`. -- It relies on the Rules of React (pure render, no mutating props or state) and skips code it can't prove safe. - -### Strict Mode double invocation - -- In development, **Strict Mode** double-calls render functions to expose impure rendering. -- It also runs an extra effect setup → cleanup → setup cycle on mount (state kept) to expose **missing cleanup**; production skips it. - -## React hooks pitfalls - -### Stale closures - -- An effect or callback sees the values from the render that created it; a value missing from the dependency array stays **stale**. -- Fix by restructuring (a ref, a functional `setState`, moving the logic into the effect) — not by silencing the lint rule. - -### Effects: when not to use one - -- Deriving state inside an effect + `setState` causes an extra render and a flash — **compute it during render**. -- Every subscription, timer, or listener an effect creates needs a **cleanup** function. - -### Data fetching in effects - -- Responses can arrive **out of order**: ignore stale ones in the effect cleanup (an `ignore` flag or `AbortController`), or a slow earlier request overwrites newer data. -- Effect-based fetching creates **waterfalls** (parent fetches → child mounts → child fetches); hoist fetching to route loaders, Server Components, or a query library. -- Strict Mode runs effects twice in development, so a fetch without cleanup fires twice there. - -### Memoization pitfalls - -- `React.memo` is defeated by a **new object, array, or function** literal passed as a prop on every render. -- Memoize only when the skipped work costs more than the comparison. - -## State management - -### Context performance - -- Every consumer re-renders when the provider's `value` changes **identity** — an inline object literal changes it on every render. -- Memoize the value, split fast- and slow-changing state into separate contexts, or use a store with selectors (`useSyncExternalStore`, Zustand) so components subscribe to slices. -- Context suits low-frequency values (theme, locale, current user), not fast-changing shared state. - -### Server state vs client state - -- **Server state** (fetched data) has its own caching, staleness, refetching, and loading lifecycle — use TanStack Query or SWR, not a global client store. -- These libraries dedupe requests, cache by **query key**, and refetch after invalidating a key on mutation. - ## Accessibility ### Semantic HTML and ARIA diff --git a/docs/topics/nextjs.md b/docs/topics/nextjs.md new file mode 100644 index 0000000..0323b41 --- /dev/null +++ b/docs/topics/nextjs.md @@ -0,0 +1,248 @@ +# Next.js + +What experienced Next.js engineers forget before an interview, grouped by subtopic. Covers the App Router as of Next.js 16.3; React semantics (Server Components, Suspense, Actions) are in [react.md](react.md), and rendering strategies in general in [frontend.md](frontend.md). + +## Rendering + +### Static and dynamic rendering + +- Without Cache Components, a route is **prerendered at build** unless it reads request-time data: `cookies()`, `headers()`, `searchParams`, `connection()`, or `fetch` with `cache: 'no-store'`. +- Since **Next.js 15** a plain `fetch` isn't cached, yet a route that only uses it is still prerendered — the build-time response stays in the HTML until revalidation; `await connection()` forces request time. +- Segment configs choose explicitly: `export const dynamic = 'force-dynamic'`, `export const revalidate = 60` (ISR). +- `generateStaticParams` prerenders chosen params; others render on first request unless `dynamicParams = false` makes them 404. + +### Cache Components + +- **`cacheComponents: true`** (Next.js 16, replaces `experimental.ppr` and `dynamicIO`) makes everything dynamic by default; cache explicitly with `'use cache'`. +- The build prerenders a **static shell** (Partial Prerendering); request-time data goes inside `` and streams into the same response, or gets cached — a page with no static shell fails the build. +- In development, 16.3 validates that every navigation renders instantly and flags code that would block; `export const instant = false` accepts a blocking route. +- The `dynamic`, `revalidate`, `fetchCache`, and `dynamicParams` segment configs are errors under Cache Components. +- Opt-in in 16.x; planned as the default in a future major. + +### Streaming and `loading.tsx` + +- **`loading.tsx`** wraps the segment's page in ``: navigation shows the fallback at once while the shared layout stays interactive. +- Once streaming starts, the status (**200**) and headers are already sent — a `notFound()` or `redirect()` inside streamed content can't change the status code. +- Wrap each slow component in its own `` so it streams separately instead of blocking the whole page. + +### Server-side waterfalls + +- Sequential `await`s in one component, or a parent awaiting before rendering a child that fetches, run independent requests one after another. +- Start independent requests together: **`Promise.all`**, or create the promises first and await them later. +- Suspense streams results as they arrive but doesn't parallelize fetches the code itself serializes. + +### Client-only code + +- Everything in `app/` is a Server Component until a `'use client'` boundary (see [react.md](react.md)). +- `dynamic(() => import('./Map'), { ssr: false })` skips server rendering for browser-only libraries, but only inside a Client Component — a Server Component errors. +- **`useSearchParams`** in a prerendered route client-renders everything up to the nearest ``; without one the production build fails. + +## Caching + +### Caching layers before Cache Components + +| Layer | Where | Stores | Default since Next.js 15 | +|---|---|---|---| +| Request memoization | server, one render | duplicate `fetch` GETs | on | +| Data Cache | server, persistent | `fetch` responses | **off** (on in 14) — opt in with `cache: 'force-cache'` or `next: { revalidate }` | +| Full Route Cache | server, persistent | HTML + RSC payload of prerendered routes | on for static routes | +| Router Cache | browser memory | RSC payload of visited and prefetched segments | layouts and loading states reused; pages **0 s** (`staleTimes.dynamic`); any revalidation in a Server Action clears it | + +### `'use cache'` + +- Caches the return value of a function, component, or whole file; the key includes the build, the function, and its serialized **arguments and closed-over values**. +- Can't call `cookies()` or `headers()` inside — read them outside and pass the values in, or use `'use cache: private'` (per user, kept only in browser memory). +- Default storage is in memory per server instance; **`'use cache: remote'`** stores entries in a shared cache handler across instances. +- `cacheLife` sets `stale` (client, minimum 30 s), `revalidate` (server, stale-while-revalidate), and `expire`; the **default profile** is 5 min / 15 min / never. +- `cacheTag('posts')` labels an entry for on-demand invalidation. + +### Invalidation APIs + +| API | Callable from | Effect | +|---|---|---| +| `revalidateTag(tag, 'max')` | Server Actions, Route Handlers | marks tagged entries stale; the next visitor gets stale content while it refreshes (SWR); the one-argument form is deprecated in 16 | +| `updateTag(tag)` | Server Actions only | expires entries and reads fresh data in the same request (**read-your-writes**) | +| `revalidatePath('/blog')` | Server Actions, Route Handlers | invalidates everything rendered for a path | +| `refresh()` | Server Actions only | re-renders uncached data; the cache is untouched | +| `router.refresh()` | Client Components | re-fetches the current route's RSC payload, keeps client state | + +### Request memoization + +- Identical `fetch` GETs in one render pass run once across layouts, pages, and `generateMetadata` — not in Route Handlers. +- For database and SDK calls, wrap the function in React **`cache()`** so `generateMetadata` and the page share one query. +- Memoization lasts **one request**; persistence across requests needs `'use cache'` or the Data Cache. + +## Routing + +### Special files + +| File | Role | +|---|---| +| `layout` | shared UI; persists across navigations without re-rendering | +| `template` | like a layout, but remounts when its segment changes | +| `loading` | `` fallback for the segment | +| `error` | error boundary; must be a Client Component | +| `not-found` | UI for `notFound()` | +| `global-error` | replaces the root layout on error; renders its own `` and `` | +| `route` | Route Handler; can't share a segment with `page` | +| `default` | fallback for an unmatched parallel-route slot | + +### Error handling + +- `error.tsx` wraps the segment's page and nested segments but **not its own layout** — a layout error goes to the parent's boundary, and a root layout error to `global-error.tsx`. +- **`retry()`** re-fetches and re-renders Server Component children; `reset()` only clears the client error state, so it can't recover from a server error. +- `catchError(Fallback)` from `next/error` (16.3) builds component-level boundaries that let `redirect()` and `notFound()` pass through. +- In production, Server Component errors reach the client as a generic message plus a **`digest`** that matches the server log entry. + +### Layouts and templates + +- Layouts persist: navigating between child pages doesn't remount or re-render them, so their state survives and only changed segments are fetched (**partial rendering**). +- A Server Component layout can't read the current pathname or `searchParams` — use `usePathname`/`useSearchParams` in a Client Component. +- `template.tsx` remounts when its own segment, including dynamic params, changes — not on deeper navigations or search-param changes; use it for enter animations or per-page effects. + +### Dynamic segments + +- **`params` and `searchParams` are Promises** since Next.js 15 (`await params`); 16 removed sync access, as for `cookies()`, `headers()`, and `draftMode()`. +- `(group)` folders organize routes without adding a URL segment and allow several root layouts; `_folder` is private and never routed. +- `next/root-params` (16.3) reads root params like `[lang]` from any Server Component without prop drilling. + +### Parallel and intercepting routes + +- **`@slot`** folders render several pages in one layout at once (dashboard panels, modals); slots aren't URL segments. +- On a hard load, a slot with no match renders `default.tsx` — required for every slot since **Next.js 16**, or the build fails. +- **Intercepting routes** like `(.)photo/[id]` show a route inside the current layout on client navigation; a refresh or shared link renders the full page — the photo-modal pattern. +- `(..)` and `(...)` match one segment up and the app root — counted in route segments, not folders. + +### Metadata + +- A layout or page exports a static `metadata` object or an async **`generateMetadata`** — Server Components only. +- Metadata from nested segments merges **shallowly**: a child's `openGraph` replaces the parent's whole `openGraph`, so spread shared fields in. +- `generateMetadata` can call `redirect()` and `notFound()`, and its `fetch` calls are memoized with the page's. + +### Navigation and prefetching + +- Without Partial Prefetching, `` prefetches when it enters the viewport, **in production only**: static routes fully, dynamic routes down to the nearest `loading.tsx`; `prefetch={true}` fetches the full route. +- Next.js 16 downloads a shared layout once across many links and only prefetches segments not already cached. +- **Partial Prefetching** (16.3, `partialPrefetching: true`) prefetches one reusable shell per route instead of a request per link; `prefetch={true}` adds the link's cached, URL-dependent content. +- In the App Router, `useRouter` comes from `next/navigation`; `next/router` belongs to the Pages Router. + +## Server Actions + +### Server Actions in Next.js + +- A `'use server'` function passed to `` works **without JavaScript** (progressive enhancement); event handlers can call it too. +- Always a **POST**; Next.js rejects requests whose `Origin` host doesn't match `x-forwarded-host` or `host` (CSRF check); `serverActions.allowedOrigins` adds extra hosts. +- The request body limit is **1 MB** by default (`serverActions.bodySizeLimit`). +- After the mutation, call `updateTag` or `revalidatePath`, then `redirect`; the response carries the updated RSC payload in the same round trip. +- `after(fn)` (stable since 15.1) runs logging or analytics after the response is sent — in Server Actions, Route Handlers, and Server Components. + +### Closures and action IDs + +- An inline Server Action can capture variables from the component that renders it; Next.js **encrypts** them before sending them to the client — still, never capture secrets. +- Action IDs are encrypted, non-deterministic, and regenerated per build; unused actions are dropped from the client bundle. +- Several self-hosted instances need the same **`NEXT_SERVER_ACTIONS_ENCRYPTION_KEY`** at build, or one instance can't decrypt another's closures ("Failed to find Server Action"). + +### `redirect` and `notFound` + +- **`redirect()`** and `notFound()` work by throwing — call them outside `try`, or call `unstable_rethrow(err)` first in the `catch`. +- `redirect` responds 307 by default; in a Server Action it becomes a client navigation (303 without JavaScript); `permanentRedirect` uses 308. +- In client event handlers use `router.push` — `redirect` only works during render and in server code. + +### Route Handlers vs Server Actions + +| | Route Handler (`route.ts`) | Server Action | +|---|---|---| +| Caller | any HTTP client | your own React UI | +| Methods | GET, POST, PUT, DELETE, … | POST only | +| Use for | webhooks, public APIs, streamed responses, non-React clients | mutations from forms and buttons | +| Data fetching | yes | no — dispatched one at a time, uncached | +| Caching | GET only: opt-in since 15, on by default in 14 | never | + +## Proxy and security + +### `proxy.ts` + +- **`proxy.ts`** (renamed from `middleware.ts` in Next.js 16) runs before routing and caches on every matched request: redirects, rewrites, headers, A/B tests, i18n. +- It runs on the **Node.js runtime** only; `middleware.ts` still works for the Edge runtime but is deprecated. +- Narrow it with `config.matcher`, excluding `_next/static`, `_next/image`, and assets; there's one proxy file per project. + +### Authorization placement + +- Proxy is for **optimistic checks** only (decode the session cookie, redirect) — it also runs on prefetches, so no database calls. +- Real authorization belongs in a **Data Access Layer** that verifies the session next to every query, Server Action, and Route Handler. +- **CVE-2025-29927** (March 2025, CVSS 9.1): a spoofed `x-middleware-subrequest` header skipped middleware entirely; fixed in 15.2.3 and 14.2.25 — apps that checked auth only in middleware were open. + +### Server-only code + +- `import 'server-only'` fails the build if a module with secrets or database access ends up in client code. +- Props passed to Client Components are serialized into the page — return DTOs from the data layer, not raw rows. + +## Build and deployment + +### Environment variables + +- **`NEXT_PUBLIC_*`** values are inlined into client JS at **build time** — changing them at runtime does nothing, so one Docker image can't serve several environments that way. +- Other variables are server-only and read when the code runs: at build for prerendered routes, per request for dynamic ones. +- Next.js 16 removed `serverRuntimeConfig` and `publicRuntimeConfig` — use environment variables. + +### Output modes and self-hosting + +- **`output: 'standalone'`** traces only the files and `node_modules` the server needs into `.next/standalone`; copy `public/` and `.next/static` yourself or serve them from a CDN. +- **`output: 'export'`** emits static files only: no proxy, Server Actions, ISR, rewrites, cookies, or default image optimization. +- ISR and `'use cache'` entries live per instance by default — several instances need a shared cache handler. + +### Version skew + +- During a rolling deploy, old clients can request deleted chunks, call Server Action IDs the new build doesn't know, or fetch incompatible RSC payloads. +- **`deploymentId`** tags assets (`?dpl=`) and navigations (`x-deployment-id`); a mismatch triggers a full reload instead of a client navigation. +- Vercel's Skew Protection keeps old deployments reachable for a while; self-hosted setups keep old static assets around after a deploy. + +### Turbopack and runtimes + +- **Turbopack** is the default bundler for `next dev` and `next build` since **Next.js 16**; `--webpack` opts out. +- Its file-system cache is on by default for dev since 16.1 and for builds since 16.3. +- `next build` no longer lints — Next.js 16 removed `next lint`. +- The Edge runtime (`export const runtime = 'edge'`) runs on V8 isolates with Web APIs only — no `fs` or native modules; Node.js is the default and supports every feature. + +## Built-in components + +### `next/image` + +- Resizes and converts to WebP/AVIF on demand, lazy-loads by default, and needs `width`/`height` or **`fill`** to reserve space against layout shift. +- A `fill` image without `sizes` is assumed to be `100vw` wide and downloads a larger file than needed. +- Mark the LCP image with **`preload`**, which replaced `priority` (deprecated in Next.js 16). +- Remote hosts must match `images.remotePatterns`; `images.domains` is deprecated. +- Next.js 16 defaults: a 4-hour `minimumCacheTTL`, `qualities: [75]`, and local IPs blocked. + +### `next/font` + +- Downloads Google Fonts at **build time** and serves them from your own domain — no runtime request to Google. +- Generates a fallback font with `size-adjust` metrics, so the swap to the web font barely shifts layout. + +### `next/script` + +| Strategy | Loads | +|---|---| +| `beforeInteractive` | before hydration, injected into the server HTML; root layout only | +| `afterInteractive` (default) | after some of the page hydrates | +| `lazyOnload` | during browser idle time | +| `worker` (experimental) | in a web worker through Partytown; Pages Router only | + +## Pages Router + +### Data fetching functions + +| Pages Router | Runs | App Router equivalent | +|---|---|---| +| `getStaticProps` | at build; again after `revalidate` seconds (ISR) | a prerendered Server Component, or `'use cache'` | +| `getServerSideProps` | on every request | a Server Component reading request data | +| `getStaticPaths` | at build | `generateStaticParams` | +| `pages/api/*` | per request | Route Handlers | + +### Pages Router differences + +- Every page ships its JS and hydrates in full — no Server Components. +- `_app` wraps every page (global layout and state); `_document` customizes the HTML shell and renders only on the server. +- Nested layouts need the per-page `getLayout` pattern; the router is `next/router`. +- `getStaticPaths` `fallback`: `false` → 404 for unknown paths; `true` → a fallback page first (`router.isFallback`); `'blocking'` → server-render the first request, then cache it. +- Both routers can coexist during a migration, but the same route in `app/` and `pages/` fails the build. diff --git a/docs/topics/react.md b/docs/topics/react.md new file mode 100644 index 0000000..cd59e60 --- /dev/null +++ b/docs/topics/react.md @@ -0,0 +1,271 @@ +# React + +What experienced React engineers forget before an interview, grouped by subtopic. Covers React 19.3; for rendering strategies, the browser pipeline, and Web Vitals see [frontend.md](frontend.md); for the framework layer see [nextjs.md](nextjs.md). + +## Rendering model + +### Render and commit + +- **Render phase**: React calls components to compute the next tree; **commit phase**: it applies DOM changes, runs layout effects, then passive effects (usually after paint). +- Render must be **pure** — it may run more than once for one commit (Strict Mode, interrupted concurrent renders). +- A component re-renders on its own state change, a parent re-render (unless memoized), or a consumed context value change — never because a prop changed on its own. + +### Reconciliation and keys + +- A different element **type** at the same position unmounts the old subtree and its state; the same type updates in place. +- **Keys** match list items across renders; an array index as key attaches the wrong state after reorder or filter. +- A component defined inside another component is a new type on every render, so its subtree remounts and loses state each time. + +### State and tree position + +- State belongs to a **position** in the tree, not to a variable: `{isAdmin ? : }` keeps one shared state. +- Changing a component's **`key`** remounts it and resets its state — the clean way to reset a form when the selected item changes. + +### State snapshots and batching + +- State is a **snapshot** per render: `setCount(count + 1)` twice adds 1; the updater form `setCount(c => c + 1)` twice adds 2. +- **Automatic batching** (React 18+) merges all updates from one tick into one render, including in timeouts and promises. +- `flushSync(() => setValue(x))` opts out: it renders and commits synchronously before returning. +- Setting state to an `Object.is`-equal value skips the re-render — mutating an object in place and setting it again does nothing. +- `useRef` holds a mutable value across renders without triggering a re-render; don't read or write it during render. + +### Fiber and concurrent rendering + +- **Fiber** splits rendering into interruptible units: the render phase can pause, restart, or be discarded; the commit phase is synchronous. +- Time slicing applies only to updates marked non-urgent (transitions, deferred values); other updates render without yielding. +- **Tearing**: an interrupted render can read different values from a mutable external store in different components — `useSyncExternalStore` prevents it. + +## Hooks + +### Rules of Hooks + +- React identifies each hook's state by **call order**, so hooks run at the top level in the same order every render — no conditions, loops, or calls after an early return. +- **`use`** is the exception: it may be called inside `if` and loops, but not inside `try/catch`. +- Custom hooks share logic, not state: each call gets its own state. + +### Effect timing + +| Hook | Runs | Use for | +|---|---|---| +| `useEffect` | after commit, usually after paint | subscriptions, syncing with external systems | +| `useLayoutEffect` | after DOM mutation, **before paint** (blocks it) | measuring layout and re-rendering without flicker (tooltips) | +| `useInsertionEffect` | before layout effects | CSS-in-JS style injection | + +### Stale closures + +- An effect or callback sees the values from the render that created it; a value missing from the dependency array stays **stale**. +- Fix by restructuring (a ref, a functional `setState`, moving the logic into the effect, an Effect Event) — not by silencing the lint rule. + +### Effect Events + +- **`useEffectEvent`** (stable in 19.2) wraps logic that must read the latest props or state without re-running the effect: `onConnected` sees the current `theme` while the effect depends only on `roomId`. +- Effect Events stay out of dependency arrays and may only be called from effects in the same component. +- Only for code that is conceptually an event fired by the effect — not a way to silence the lint rule. + +### Unnecessary effects + +- Deriving state inside an effect + `setState` causes an extra render and a flash — **compute it during render**. +- Logic caused by a user action belongs in the event handler, not in an effect watching a flag. +- Every subscription, timer, or listener an effect creates needs a **cleanup** function. + +### Data fetching in effects + +- Responses can arrive **out of order**: ignore stale ones in the effect cleanup (an `ignore` flag or `AbortController`), or a slow earlier request overwrites newer data. +- Effect-based fetching creates **waterfalls** (parent fetches → child mounts → child fetches); hoist fetching to route loaders, Server Components, or a query library. +- Strict Mode runs setup → cleanup → setup in development, so the request fires twice; the cleanup is what makes the first response get ignored. + +### `useSyncExternalStore` + +- Subscribes components to an external store (a browser API, Redux, Zustand) with no **tearing** under concurrent rendering. +- `getSnapshot` must return the **same value** while the store is unchanged — returning a new object on each call re-renders forever. +- `getServerSnapshot` supplies the value for server rendering and hydration. + +## State management + +### Context performance + +- Every consumer re-renders when the provider's `value` changes **identity** — an inline object literal changes it on every render. +- Memoize the value, split fast- and slow-changing state into separate contexts, or use a store with selectors (`useSyncExternalStore`, Zustand) so components subscribe to slices. +- Context suits low-frequency values (theme, locale, current user), not fast-changing shared state. + +### Server state vs client state + +- **Server state** (fetched data) has its own caching, staleness, refetching, and loading lifecycle — use TanStack Query or SWR, not a global client store. +- These libraries dedupe requests, cache by **query key**, and refetch after invalidating a key on mutation. + +### State placement and stores + +- Shareable UI state (filters, tabs, pagination) belongs in the **URL** — it survives reloads and works with the back button and shared links. +- `useReducer` fits updates that touch several fields or follow named events; `dispatch` has a stable identity, so a context holding only `dispatch` never re-renders its consumers. +- **Selectors** limit re-renders: Redux `useSelector` and Zustand re-render a component only when its selected value changes by reference. +- A selector that builds a new object or array on each call defeats that — use `shallowEqual`/`useShallow` or a memoized selector. +- Jotai splits state into atoms; a component re-renders only for the atoms it reads. + +## Performance + +### Memoization pitfalls + +- `React.memo` is defeated by a **new object, array, or function** literal passed as a prop on every render. +- `memo` compares each prop with `Object.is`; a custom `arePropsEqual` that ignores function props leaves the child calling **stale closures**. +- `useCallback` only helps when the function goes to a memoized child or into a dependency array. +- Memoize only when the skipped work costs more than the comparison. + +### React Compiler + +- **React Compiler** (stable 1.0, **October 2025**) auto-memoizes components and values at build time, replacing most manual `useMemo`/`useCallback`/`memo`. +- It relies on the Rules of React (pure render, no mutating props or state) and skips code it can't prove safe. + +### Profiling + +- The React DevTools **Profiler** records which components rendered, how long they took, and why. +- **Performance Tracks** (19.2) add React's scheduler and component work to the Chrome DevTools Performance panel. + +### Virtualization and code splitting + +- Long lists: **virtualize** (TanStack Virtual, react-window) so only the visible rows mount. +- `lazy(() => import('./Chart'))` inside `` splits a component into its own chunk, loaded on first render. + +## Server Components + +### Server and Client Components + +- Server Components are the default in an RSC framework — there's **no directive** for them; `'use server'` marks Server Functions. +- **`'use client'`** marks a module boundary: the module and everything it imports become client code. +- Client Components still pre-render to HTML on the server; "client" means their code also ships and hydrates. +- Server Components can be `async` but have no state, effects, or browser APIs, and their code never reaches the client. +- A Client Component can't import a Server Component, but it can render one passed as **`children`** or another prop. + +### Serialization boundary + +- Props crossing from server to client must be **serializable**: primitives, plain objects and arrays, `Date`, `Map`, `Set`, `BigInt`, typed arrays, Promises, JSX, and Server Functions. +- Plain functions and class instances can't cross — pass data, and keep event handlers inside the Client Component. +- An unawaited Promise passed as a prop starts the work on the server; the client reads it with **`use()`** inside ``. +- Every prop lands in the RSC payload the browser downloads — pass only the fields the component needs, never whole database rows. + +### Server Functions + +- **`'use server'`** marks async functions the client can call: React serializes the arguments, POSTs them, and returns the serialized result. +- Every exported Server Function is a **public endpoint** — anyone can call it with crafted arguments, so authenticate, authorize, and validate inside each one. +- Designed for mutations: the client dispatches them **one at a time** and their results aren't cached, so they're a poor fit for data fetching. +- A Server Function passed to `` or `formAction`, or called inside a transition, is a Server Action. + +### RSC protocol vulnerabilities + +- **React2Shell** (CVE-2025-55182, December 2025, CVSS **10.0**): unauthenticated remote code execution through Flight-protocol deserialization in `react-server-dom-*` 19.0.0, 19.1.0–19.1.1, and 19.2.0 (fixed in 19.0.1, 19.1.2, 19.2.1), exploited within days. +- Follow-up denial-of-service and source-code-exposure CVEs needed several more patch rounds into 2026 — stay on the latest patch of the RSC packages and the framework. +- Client-only React apps were unaffected — only servers that decode RSC requests (Next.js App Router, React Router RSC mode, Waku). + +## Concurrency and Suspense + +### Transitions + +- **`startTransition`**/`useTransition` mark an update as non-urgent: it renders in the background, typing interrupts it, and the old UI stays visible; `isPending` flags it. +- A transition can't drive a controlled input's `value` — the input update must stay urgent. +- **`useDeferredValue(value)`** renders with the old value first and the new one in the background — for values you receive rather than set. +- An async function in `startTransition` (React 19) keeps `isPending` true until it settles; state set after an `await` needs its own `startTransition`. +- Async transitions can finish **out of order**; `useActionState` and `` keep them in order, a hand-rolled flow needs its own queue or abort. + +### Suspense boundaries + +- Suspense activates for `lazy`, `use(promise)`, and Suspense-enabled frameworks — **not** for fetching in an effect or event handler. +- Content that suspends again shows the fallback again, unless the update was a **transition**, which keeps the old UI on screen. +- Promises passed to `use` must be **cached**: one created during render is new on every render and never resolves for React ("uncached promise"). +- React reveals boundaries at most once every 300 ms, so boundaries that resolve close together appear together. + +### Activity + +- **``** (19.2) hides its children with `display: none` but keeps their state and DOM. +- Hidden children **unmount their effects** and process updates only when React is otherwise idle; switching back to `visible` re-runs the effects. +- Use it for tabs that keep their state and for pre-rendering a likely next screen in the background. + +### View Transitions + +- **``** (stable in 19.3) animates elements on enter, exit, update, and shared-element moves with the browser View Transition API (see [frontend.md](frontend.md)). +- It only animates updates inside a **Transition**, a Suspense reveal, or `useDeferredValue`; a plain `setState` doesn't animate. +- `addTransitionType('next')` inside `startTransition` picks a different animation for the same element depending on the cause. + +## Forms and Actions + +### Actions + +- An **Action** is an async function passed to ``, `formAction`, or `startTransition`; React tracks its pending state and sends thrown errors to the nearest error boundary. +- After a `` succeeds, React **resets uncontrolled fields** (`requestFormReset` does it manually). +- `useActionState(action, initialState)` returns `[state, formAction, isPending]` and passes the previous state to the action as its first argument. +- **`useFormStatus()`** reads the pending state of the parent ``, so it must be called in a component rendered inside that form. + +### Optimistic updates + +- **`useOptimistic(state, updateFn)`** shows the expected result immediately while an Action runs. +- When the Action settles, the optimistic value is dropped and the real state shows — a failure rolls back automatically. + +### Controlled and uncontrolled inputs + +- `value={undefined}` followed by a string flips an input from uncontrolled to controlled and warns — initialize with `''`. +- `onChange` fires on every keystroke (the native `input` event), unlike the DOM `change` event. + +## SSR and hydration + +### Hydration mismatches + +- **Hydration** attaches listeners to the server HTML and expects the first client render to match it exactly. +- Mismatch sources: `Date.now()`, `Math.random()`, `typeof window` branches, locale or timezone formatting, invalid nesting (`
` inside `

`), browser extensions. +- A text or structure mismatch makes React throw away the server HTML and **client-render** up to the nearest Suspense boundary; a mismatched attribute isn't patched at all. +- Fixes: **`useId`** for IDs, client-only values set in an effect, or `use(browser())` (19.3), which renders the Suspense fallback on the server and the component only on the client. +- `suppressHydrationWarning` silences one element's attribute and text mismatches, one level deep; React still doesn't patch them. + +### Streaming and selective hydration + +- `renderToPipeableStream` (Node) and `renderToReadableStream` (Web Streams) send the shell first; each Suspense boundary streams in later with an inline script that swaps it into place. +- **Selective hydration**: boundaries hydrate independently, and one the user interacts with jumps the queue. +- Crawlers and static generation wait for all content (`onAllReady`); browsers get the shell as soon as it's ready (`onShellReady`). +- **Partial pre-rendering** (19.2): `prerender` produces a static prelude for the CDN, and `resume` fills in the postponed parts per request. + +## Errors and Strict Mode + +### Error boundaries + +- An error boundary catches errors thrown while **rendering** its subtree and shows a fallback UI instead of unmounting the whole app. +- It's still a **class component** (`getDerivedStateFromError`, `componentDidCatch`) or the `react-error-boundary` package; there's no hook equivalent. +- It doesn't catch errors in event handlers or async callbacks (`setTimeout`, promises) — except inside `useTransition`'s `startTransition`, whose errors do reach the boundary. +- React 19 reports each error once through the `createRoot` options `onCaughtError` and `onUncaughtError` instead of re-throwing it. + +### Strict Mode double invocation + +- In development, **Strict Mode** double-calls render functions to expose impure rendering. +- It also runs an extra effect setup → cleanup → setup cycle on mount (state kept) to expose **missing cleanup**; production skips it. + +## DOM, events, and refs + +### Synthetic events + +- Since **React 17**, listeners attach to the **root container**, not `document`, so several React roots or versions can coexist on a page. +- React 17 removed event pooling; `e.persist()` is a no-op. +- `onScroll` doesn't bubble since React 17. +- `onFocus`/`onBlur` are built on `focusin`/`focusout`, so unlike the DOM events they bubble. + +### Portals + +- `createPortal(children, node)` renders into another DOM node, so modals and tooltips escape `overflow: hidden` and parent stacking contexts. +- Events bubble through the **React tree**, not the DOM tree: a click inside a portal reaches the React parent's `onClick`. +- Context flows into a portal like into any other child. + +### Refs + +- `ref` is a regular prop for function components since **React 19** — `forwardRef` is no longer needed. +- A callback ref gets the node on attach; since React 19 it can return a **cleanup** function instead of being called with `null` on detach. +- `useImperativeHandle` exposes a narrow API (`focus()`, `scrollTo()`) instead of the raw DOM node. +- **Fragment refs** (19.3): `` gives focus, event-listener, and observer methods over a group of children without a wrapper element. + +## Version changes + +### React 18 + +- **`createRoot`** turns on concurrent features; the legacy `ReactDOM.render` kept React 17 behavior. +- Added automatic batching everywhere, `useTransition`/`useDeferredValue`, and Suspense on the server (streaming SSR, selective hydration). +- Added `useId`, `useSyncExternalStore`, and `useInsertionEffect`; Strict Mode started double-running effects in development. + +### React 19 + +- Added `` as a provider and ``/`<meta>`/`<link>` hoisted into `<head>` from any component, alongside **Actions**, `use`, and `ref` as a prop. +- Removed `ReactDOM.render`/`hydrate`, string refs, legacy context, `propTypes`, and **`defaultProps`** on function components (use default parameters). +- Server Components and Server Functions are stable for apps, but the bundler APIs underneath don't follow semver, so frameworks pin React versions. diff --git a/zensical.toml b/zensical.toml index 858355a..3f94ad8 100644 --- a/zensical.toml +++ b/zensical.toml @@ -25,6 +25,8 @@ nav = [ { "Web" = [ { "Backend" = "topics/backend.md" }, { "Frontend" = "topics/frontend.md" }, + { "Next.js" = "topics/nextjs.md" }, + { "React" = "topics/react.md" }, { "Security" = "topics/security.md" }, ] }, { "Infrastructure" = [ @@ -43,3 +45,18 @@ features = ["navigation.sections", "toc.follow"] [project.validation] invalid_links = true invalid_link_anchors = true + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/pinebit/interviews" +name = "Source on GitHub" + +[[project.extra.social]] +icon = "fontawesome/brands/linkedin" +link = "https://www.linkedin.com/in/pinebit/" +name = "Andrei Smirnov on LinkedIn" + +[[project.extra.social]] +icon = "simple/buymeacoffee" +link = "https://buymeacoffee.com/pinebit" +name = "Buy me a coffee"