Next.js optimization
Why next/image needs width and height
next/image wraps the browser <img> and adds resizing, modern formats, lazy loading, and layout-shift protection. That last job is why the component demands size information. width and height are the image's intrinsic pixel size, and Next uses them only to compute the aspect ratio so the browser reserves the box before the file arrives. They do not set the rendered size; your CSS does that. Omit them and the page reflows when the image loads, which is Cumulative Layout Shift (CLS).
You can leave off width and height in two cases: a static import (import hero from "./hero.jpg"), where Next reads the dimensions from the file at build, or fill, where the image absolutely fills the nearest positioned ancestor (give that parent position: relative). A plain string src such as "/hero.jpg" is neither, so it requires both props.
Images lazy-load by default. For the one hero above the fold, add priority so Next preloads it and skips lazy loading. In Next 16 this prop is renamed; the senior notes cover that.
next/font self-hosts your fonts at build time
import { Inter } from "next/font/google" fetches nothing from Google at runtime. Next downloads the font files at build and serves them from your own origin, so the browser never contacts a third party. It also generates a size-matched fallback, which is why swapping in the real font causes zero layout shift.
The loader is a compile-time transform, so call it once at module scope and assign it to a const with static option literals:
const inter = Inter({ subsets: ["latin"] }); // ok: module scope, literal options
// Inter({ subsets: [userChoice] }) inside a component -> build error
Reuse that one instance across the app. display defaults to swap, and non-variable fonts require an explicit weight.
Choosing a next/script loading strategy
next/script controls when a third-party script loads through its strategy prop:
| strategy | when it loads | use for |
|---|---|---|
| beforeInteractive | before any Next.js code, injected into head | consent, bot detection |
| afterInteractive (default) | after hydration begins | analytics, tag managers |
| lazyOnload | during browser idle time | chat widgets, embeds |
Wrong "load analytics with beforeInteractive so it is ready first." That strategy runs before your first-party JavaScript and delays interactivity, and it is only honored from the root app/layout. Right leave analytics on the default afterInteractive, or lazyOnload for anything non-essential.
Static metadata versus generateMetadata
The Metadata API replaces the Pages Router next/head; Next emits the head tags for you. Export a static object when the tags are known at build:
export const metadata = { title: "Pricing", description: "Plans and pricing" };
Use generateMetadata when tags depend on route params or fetched data. Both live only in layout or page files and only in Server Components; exporting metadata from a "use client" file is disallowed and fails the build ("You are attempting to export 'metadata' from a component marked with 'use client'"). Keep interactivity in a child Client Component while the page or layout stays a Server Component. You cannot export both metadata and generateMetadata from the same segment.
next/image formats, sizes, and the priority rename
Two defaults trip people up. First, the optimizer serves WebP by default (formats: ["image/webp"]); AVIF is opt-in through next.config (formats: ["image/avif", "image/webp"], where order matters because the first match against the request Accept header wins). Second, external hosts are blocked until you allowlist them under images.remotePatterns; the older domains option is deprecated.
The sizes prop does more than pick a candidate. Without it, the browser assumes the image occupies 100vw and downloads an oversized file, and Next emits only a small device-pixel-ratio srcset. Supply sizes (for example "(max-width: 768px) 100vw, 33vw") and Next generates a full width-based srcset, so each viewport gets an appropriately sized file. Always pair fill, or any CSS-responsive width, with sizes.
Version caveat: in Next 15, priority marks the LCP image, injecting a preload link and disabling lazy loading. Next 16 deprecates priority in favor of preload, with loading="eager" and fetchPriority="high" now recommended for most cases.
How metadata segments merge
Metadata resolves from the root layout down to the leaf, and objects are shallow-merged with duplicate keys replaced.
Wrong "the child sets openGraph.title, so it keeps the parent's openGraph.description." A child that sets openGraph at all replaces the entire parent openGraph object, dropping og:description unless you spread the parent value back in. A child that omits openGraph does inherit the parent's.
Relative URLs in openGraph.images or alternates.canonical need metadataBase (new URL(...) set once in the root layout); without it a relative URL is a build error. title.template applies only to child segments, never the segment that declares it, and requires a default.
Edge runtime is a Web API subset, not Node
Set the runtime per route with export const runtime = "edge" (the default is nodejs). Edge trades capability for fast global cold starts.
| capability | nodejs | edge |
|---|---|---|
| fs, native modules | yes | no |
| require (CommonJS) | yes | no, ESM only |
| eval, new Function, WASM compile | yes | no |
| Web crypto (SubtleCrypto), fetch, streams | yes | yes |
| ISR | yes | no |
A package that touches the filesystem or ships native bindings breaks on edge. One current nuance: middleware historically ran edge-only, but a Node.js middleware runtime became stable in Next 15.5 (export const config = { runtime: "nodejs" }), while edge stays the middleware default.
Server Components and bundle size
A Server Component ships zero JavaScript to the client; the browser gets its serialized output plus the JS for any Client Components below it. Moving a heavy library (a syntax highlighter, a markdown parser) from a Client Component into a Server Component keeps that library off the client bundle entirely.
For code that must run on the client, next/dynamic splits a Client Component into its own chunk. Its ssr: false option is not allowed inside a Server Component in Next 15; it has to live in a Client Component. For large libraries with barrel files, optimizePackageImports loads only the modules you reference; many packages (lucide-react, date-fns, @mui/material) are optimized automatically.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.