Images & Icons Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
- Prefer next/image Over img: Use
next/imageso Next.js automatically serves WebP/AVIF, lazy-loads offscreen images, and generates responsive sizes. The raw<img>tag ships no optimization and hurts LCP. - Always Pair fill With sizes: Using
fillwithout asizesprop makes the browser download the largest variant for every device. Provide a breakpoint-awaresizesstring that matches your actual layout columns. - Set position: relative on fill Parents: A
fillimage's parent must haveposition: relative(or absolute/fixed) plus defined dimensions, or the image collapses to zero height. Use an aspect-ratio wrapper to lock the layout. - Reserve priority for LCP Images: Only add
priorityto above-the-fold hero images (typically one or two per page). Overusing it cancels the lazy-loading benefit ofnext/image. - Configure remotePatterns Explicitly: Add every external image host (e.g.,
images.unsplash.com,images.pexels.com,cdn.pixabay.com) toimages.remotePatternsinnext.config.tsand restart the dev server. Missing entries cause 400 errors at runtime, not at build time. - Prefer Static Imports for Local Images: Static imports return
StaticImageDatawithwidth,height, and an auto-generatedblurDataURL, eliminating manual boilerplate. They also enableplaceholder="blur"without extra work. - Use width/height Only for Aspect Ratio: The
widthandheightprops define the requested size and aspect ratio — they do not resize or crop the rendered element. Control visual sizing with CSS classes. - Never Expose API Keys in the Browser: Unsplash, Pexels, and Pixabay keys must stay server-side; fetch from Server Components or Route Handlers and store secrets in
.env.local. Pixabay is especially risky because the key is a query parameter. - Cache API Responses With revalidate: Wrap stock-photo fetches with
next: { revalidate: 3600 }so you stay inside hourly rate limits (Unsplash 50/5000, Pexels 200, Pixabay 100/min) and keep pages fast. This also avoids hammering third-party APIs on every request. - Use the Right Auth Header Format: Unsplash requires
Authorization: Client-ID <key>, Pexels requires the raw key with noBearerprefix, and Pixabay uses akeyquery parameter. Mixing these up returns a 401 and wastes debugging time. - Pick the Right URL Size: Use
regular/webformatURL/largefor display and reserveraw/original/fullHDURLfor downloads, since originals can exceed 10MB. Serving oversized images undoes the point of an optimization pipeline. - Honor Attribution and Download Tracking: Unsplash requires photographer credit with UTM params and a call to
download_locationwhenever a photo is used. Pexels and Pixabay strongly encourage attribution even when not strictly required. - Treat API alt Text as Untrusted: Fields like Pexels
altor Unsplashalt_descriptioncan be empty, generic, or null. Provide a meaningful fallback to keep images accessible. - Never Hotlink Pixabay in Production: Pixabay's license permits only temporary CDN hotlinking; for production, download and re-host images on your own infrastructure. Plan a build step or ingestion pipeline accordingly.
- Import Icons From Family Sub-Paths: Always import
react-iconsfrom a family path likereact-icons/fa— importing from the package root can defeat tree-shaking and bundle thousands of unused icons. The same discipline applies to any barrel-style icon import. - Style Heroicons With className Only: Heroicons ignore
sizeandcolorprops; set dimensions and color via Tailwind utilities such ash-6 w-6 text-blue-500. Alias imports when using both outline and solid variants of the same icon in one file. - Type Icons as Components, Not Elements: Use
LucideIcon,IconType, orReact.ComponentType<SVGProps<SVGSVGElement>>to type icon props so you can render them with<Icon size={…} />. Passing a rendered element instead of a component type breaks reusable button wrappers. - Mark IconContext Providers as Client:
IconContext.Providerfromreact-iconsuses React Context and must live in a Client Component in the App Router. Place it high enough in the tree to cover the subtree you want to style. - Use currentColor for Custom SVGs: Set
stroke="currentColor"(orfill="currentColor") on inline SVG icons so they inherit color via parenttext-*classes. This makes a hand-rolled icon set behave like Lucide or Heroicons. - Make SVG Accessibility Explicit: Decorative icons should have
aria-hidden="true"and norole; informative icons needrole="img"plus anaria-label. Also write SVG attributes in camelCase (viewBox,strokeWidth) and keepviewBoxeven after SVGO optimization. - Set fill and stroke Defaults Intentionally: SVG defaults
fillandstroketo black, so stroke-based icons needfill="none"and filled icons needstroke="none"to avoid surprise rendering. Lucide's stroke-only behavior is a concrete example —fillhas no effect there. - Generate blurDataURLs at Build Time: Produce BlurHash or tiny JPEG
blurDataURLs (around 8x8 pixels) ahead of time and cache them with your image metadata. Generating them per request withsharpadds latency and can't run on the Edge Runtime. - Match Skeleton Dimensions to Final Content: Shimmer or aspect-ratio skeletons must use the same aspect ratio as the loaded image so the layout does not shift (CLS). Apply CSS
blur()only to tiny placeholders, never to full-resolution images. - Follow Next.js Metadata File Conventions: Place
favicon.icoinapp/root, and dropicon.*,apple-icon.*,opengraph-image.*, andtwitter-image.*into the route segments where they should apply. Next.js wires them into<head>automatically and scopes OG images per route. - Design OG Images Around Satori Limits:
ImageResponsefromnext/oguses Satori, which supports only flexbox (no grid), nobackground-image: url(), and requires fonts loaded asArrayBuffer. Target 1200x630, export analt, and preferruntime = "edge"for fast, cacheable per-page images.