Technology · Next.js
Next.js Performance Optimization
Profile apps, optimize bundle size, and implement advanced caching and streaming.
TL;DR
- 01Use dynamic imports to split code and reduce bundles.
- 02Enable compression and minification in production.
- 03Implement streaming for faster first contentful paint.
Bundle Size Analysis
- Check bundle size with @next/bundle-analyzer.
npm install -D @next/bundle-analyzer - Configure next.config.js to analyze bundles.
const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }); module.exports = withBundleAnalyzer({}); - Run analysis to find large dependencies.
ANALYZE=true npm run build - Look for duplicate or unused dependencies to remove.
- Use
ANALYZE=true npm run buildto open an interactive bundle treemap in the browser.
Code Splitting Strategies
- Use dynamic imports to split code at route level.
import dynamic from "next/dynamic"; const HeavyComponent = dynamic(() => import("./HeavyComponent")); export default function Page() { return <HeavyComponent />; } - Split large components loaded conditionally.
const Editor = dynamic(() => import("./Editor"), { loading: () => <p>Loading editor...</p>, }); - Lazy load images with the Image component.
import Image from "next/image"; <Image src="/hero.jpg" alt="Hero" loading="lazy" width={1200} height={600} /> - Mark third-party libraries as external to exclude them from the bundle.
// next.config.js module.exports = { experimental: { optimizePackageImports: ["lodash", "date-fns"] } }; - Use barrel file tree-shaking to avoid importing entire libraries.
// Bad: imports entire lodash import _ from "lodash"; // Good: imports only what you need import debounce from "lodash/debounce";
Caching Optimization
- Use immutable cache headers for static assets.
// next.config.js module.exports = { onDemandEntries: { maxInactiveAge: 60 * 1000, pagesBufferLength: 5, }, }; - Configure stale-while-revalidate for optimal freshness.
export const revalidate = 3600; // 1 hour // ISR with background updates const res = await fetch("https://api.example.com/data", { next: { revalidate: 60 } }); - Set cache-control headers for API routes.
export async function GET(request: Request) { return Response.json(data, { headers: { "Cache-Control": "public, s-maxage=3600" } }); } - Use
unstable_cacheto cache server-side function results across requests.import { unstable_cache } from "next/cache"; const getCachedUser = unstable_cache( async (id: string) => db.user.findUnique({ where: { id } }), ["user"], { revalidate: 300 } ); - Tag cached data so you can revalidate it on demand with revalidateTag.
const data = await unstable_cache(fetchPosts, ["posts"], { tags: ["posts"] })(); // elsewhere: revalidateTag("posts") clears this cache
Compression and Minification
- Enable Gzip compression for responses.
// next.config.js module.exports = { compress: true, // enabled by default }; - Use React strict mode to catch performance issues.
// next.config.js module.exports = { reactStrictMode: true, }; - Minify HTML, CSS, and JavaScript automatically.
npm run build - Check production bundle size after optimization.
- Remove console statements in production with compiler options.
// next.config.js module.exports = { compiler: { removeConsole: { exclude: ["error"] } } };
Streaming and Advanced Techniques
- Use streaming to send content progressively.
import { Suspense } from "react"; export default function Page() { return ( <div> <Header /> <Suspense fallback={<p>Loading...</p>}> <SlowComponent /> </Suspense> </div> ); } - Prefetch important routes and links.
import Link from "next/link"; <Link href="/dashboard" prefetch={true}> Dashboard </Link> - Use the Image component for automatic optimization.
import Image from "next/image"; <Image src="/photo.png" alt="Photo" width={500} height={300} quality={80} /> - Monitor Core Web Vitals with web-vitals library.
import { getCLS, getFID, getFCP, getLCP, getTTFB } from "web-vitals"; getCLS(console.log); getFID(console.log); - Export reportWebVitals from app to send metrics to an analytics endpoint.
// app/layout.tsx export function reportWebVitals(metric: NextWebVitalsMetric) { fetch("/api/vitals", { method: "POST", body: JSON.stringify(metric) }); }
Tip: Use Next.js's built-in performance features like Image optimization, code splitting, and ISR before implementing custom solutions.
Warning: Don't over-optimize prematurely — measure performance with real-world tools and focus on the metrics that matter most to users.