The Next.js 15 App Router on one page, with the Pages Router equivalents interviewers still ask about. Checked against a Next.js 15.5 build with React 19.
App Router file conventions
File
Role
Remember
page.tsx
UI for the URL
without it a folder is not routable
layout.tsx
wraps children, persists across navigations
root layout must render html and body
template.tsx
like layout, but remounts per navigation
for enter animations, per-page effects
loading.tsx
Suspense fallback for the page
shown instantly; prefetched with Link
error.tsx
error boundary
must be 'use client'; does not catch its own segment’s layout
global-error.tsx
catches root layout errors
renders its own html and body
not-found.tsx
UI for notFound() and unmatched URLs
route.ts
HTTP endpoint (GET, POST, …)
cannot share a segment with page.tsx
default.tsx
fallback for a parallel slot
add one per @slot
middleware.ts
runs before routing, project root
proxy.ts in Next.js 16
Nesting order inside a segment: layout > template > error > loading > not-found > page.
Folder
URL effect
[slug]
dynamic segment, params.slug
[...slug]
catch-all, slug is an array, needs at least one segment
[[...slug]]
optional catch-all, also matches the parent
(group)
not in the URL; separate layouts
_private
excluded from routing
@slot
parallel route, passed to the layout as a prop
(.)x, (..)x, (...)x
intercepting route (modal pattern), soft navigation only
Server vs Client Components
Server Component (default)
Client Component ('use client')
Runs
server only (build or request)
server pre-render, then browser
async / await data
yes
no; use use() or a data library
State, effects, handlers
no
yes
Secrets, DB clients
yes (import 'server-only')
never
JS shipped
none
its module and everything it imports
'use client' marks a module boundary: everything that file imports becomes client code.
Client code cannot import a Server Component, but can render one passed as children.
Props crossing the boundary must be serializable. Functions fail (“Event handlers cannot be passed to Client Component props”) unless they are Server Actions.
next/dynamic with ssr: false must be called inside a Client Component in Next.js 15.
Static vs dynamic rendering
Build symbol
Meaning
○
static, prerendered at build
●
SSG via generateStaticParams
ƒ
dynamic, rendered per request
Revalidate column
ISR window, e.g. 1m
A route becomes dynamic when it uses cookies(), headers(), draftMode(), connection(), searchParams, a fetch with cache: 'no-store', or dynamic = 'force-dynamic'. A plain fetch does not: the route is still prerendered and the result frozen until revalidation.
An action is a public POST endpoint identified by an action ID. Anyone can call it with curl.
Forms work before hydration (progressive enhancement). useActionState returns state and isPending; useFormStatus reads pending state inside the form.
Next checks Origin against the host (serverActions.allowedOrigins behind proxies), encrypts closure values, and drops unused actions. Default body limit 1 MB (serverActions.bodySizeLimit).
Use a Route Handler instead for webhooks, other clients, custom status codes or cacheable GETs.
Middleware
// middleware.tsimport { NextResponse, type NextRequest } from 'next/server';export function middleware(req: NextRequest) { if (!req.cookies.has('session')) return NextResponse.redirect(new URL('/login', req.url)); // 307 return NextResponse.next();}export const config = { matcher: ['/dashboard/:path*'] }; // matches /dashboard too, not /dashboards
TypeScript
Runs before routing and caches on every matched request, including prefetches. Edge runtime by default; runtime: 'nodejs' is stable from 15.5.
Optimistic checks only. Authorise again next to the data: CVE-2025-29927 bypassed middleware with an x-middleware-subrequest header (fixed in 15.2.3).
Streaming, errors and hydration
loading.tsx or Suspense streams the shell first. Once streaming starts the status code is sent, so call notFound() before any Suspense boundary.
Production hides Server Component error messages; match the digest with server logs.
Static page + useSearchParams() without Suspense fails the build.
Hydration mismatch: React 19 error #418. Causes: Date.now(), Math.random(), locale dates, typeof window in render, localStorage, invalid nesting (div in p), browser extensions. Fix with useEffect, fixed time zones, or suppressHydrationWarning on one text node.
Environment, images, links
Variables are server-only unless prefixed NEXT_PUBLIC_, which is inlined at build time: rebuild to change it, and never put secrets there.
Load order: real env > .env.$NODE_ENV.local > .env.local (skipped in test) > .env.$NODE_ENV > .env.
next/image: srcset, lazy by default, priority for the LCP image, width/height or fill + sizes, images.remotePatterns for remote hosts.
next/link: real a element, viewport prefetch in production only, dynamic routes prefetch down to loading.tsx.
Metadata: export const metadata or generateMetadata in Server Components; title.template, metadataBase, sitemap.ts, robots.ts, opengraph-image.tsx.
Deployment
next start supports everything; output: 'standalone' for Docker images.
Several instances: shared cacheHandler for ISR and the Data Cache, the same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY and build ID everywhere.
output: 'export': static files only. No Server Actions, middleware, ISR, cookies or default image optimisation.