From 319443b30097991c876232be23f290558afbf62b Mon Sep 17 00:00:00 2001 From: Andrei Smirnov Date: Tue, 6 Oct 2026 12:35:42 +0300 Subject: [PATCH 1/2] Add React and Next.js topic briefs Add react.md (React 19.3) and nextjs.md (Next.js 16.3 App Router) to the Web group. Move the React concepts out of frontend.md into react.md, link to it from the two frontend concepts that overlap, and record the new owners in the AGENTS.md ownership table. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 6 +- README.md | 4 +- docs/index.md | 4 +- docs/topics/frontend.md | 88 +------------ docs/topics/nextjs.md | 248 ++++++++++++++++++++++++++++++++++++ docs/topics/react.md | 271 ++++++++++++++++++++++++++++++++++++++++ zensical.toml | 2 + 7 files changed, 534 insertions(+), 89 deletions(-) create mode 100644 docs/topics/nextjs.md create mode 100644 docs/topics/react.md 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..94ca96d 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" = [ From 9e27bfdb3ece45fad8d0142fcacd2e79677b0b7a Mon Sep 17 00:00:00 2001 From: Andrei Smirnov <pinebit@gmail.com> Date: Tue, 6 Oct 2026 12:35:47 +0300 Subject: [PATCH 2/2] Add GitHub, LinkedIn, and Buy Me a Coffee links to the site footer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- zensical.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/zensical.toml b/zensical.toml index 94ca96d..3f94ad8 100644 --- a/zensical.toml +++ b/zensical.toml @@ -45,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"