Performance is not a feature — it's a foundation. Over the past few years building production Next.js apps, I've collected a set of practices that consistently improve Core Web Vitals, reduce bundle sizes, and keep apps snappy for real users.
1. Use next/image everywhere
The built-in Image component handles lazy loading, srcset, and format conversion automatically. Never use a raw tag for user-facing images.
import Image from 'next/image'
<Image src="/hero.png" alt="Hero" width={1200} height={630} priority />
2. Prefer Server Components by default
App Router defaults to Server Components. Keep 'use client' at the leaf level — only components that need interactivity should run in the browser.
3. Leverage route-level code splitting
Each route in the App Router is automatically code-split. Avoid importing heavy libraries inside shared layouts — they'll bloat every page.
4. Use unstable_cache or React cache for expensive queries
Deduplicating data fetches across components in the same render tree is free with React cache. For cross-request caching, use next/cache.
import { unstable_cache } from 'next/cache'
const getUser = unstable_cache(
async (id) => db.user.findUnique({ where: { id } }),
['user'],
{ revalidate: 60 }
)
5. Minimize client-side JavaScript
Audit your bundle with @next/bundle-analyzer. Replace large client libraries with server-rendered alternatives wherever possible.
Performance work is iterative. Start with Lighthouse, fix the highest-impact issues first, and measure before and after every change. These five patterns alone have cut LCP by 40%+ on multiple projects.
More posts


