Next.js Performance Optimization
Answer
Next.js provides many built-in optimizations, but understanding and applying advanced techniques can significantly improve your application's performance.
Core Web Vitals
Image Optimization
import Image from 'next/image';
// ✅ Use Next.js Image
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // LCP image - preload
placeholder="blur"
/>
// ✅ Responsive images with sizes
<Image
src="/product.jpg"
alt="Product"
fill
sizes="(max-width: 768px) 100vw, 50vw"
/>
Font Optimization
// app/layout.js
import { Inter, Roboto_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap", // Avoid FOIT
preload: true,
});
const robotoMono = Roboto_Mono({
subsets: ["latin"],
variable: "--font-mono",
});
export default function RootLayout({ children }) {
return (
<html className={`${inter.className} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
Script Optimization
import Script from 'next/script';
// Load after page is interactive
<Script
src="https://analytics.example.com/script.js"
strategy="afterInteractive"
/>
// Load during idle time
<Script
src="https://third-party.com/widget.js"
strategy="lazyOnload"
/>
// Inline critical JS
<Script id="critical-js" strategy="beforeInteractive">
{`window.config = { apiUrl: '...' }`}
</Script>
Bundle Analysis
# Install analyzer
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
# Run analysis
ANALYZE=true npm run build
Dynamic Imports
import dynamic from "next/dynamic";
// Code split heavy components
const Chart = dynamic(() => import("./Chart"), {
loading: () => <ChartSkeleton />,
ssr: false, // Client-only component
});
// Named exports
const MDEditor = dynamic(() => import("md-editor").then((mod) => mod.Editor), {
ssr: false,
});
Route Segment Config
// Optimize data fetching per route
export const revalidate = 3600; // ISR every hour
export const dynamic = "force-static"; // Force SSG
export const fetchCache = "force-cache";
// Prevent dynamic rendering
export const preferredRegion = "auto";
export const maxDuration = 30;
Parallel Data Fetching
// ✅ Parallel - faster
async function Page() {
const [products, categories] = await Promise.all([
getProducts(),
getCategories(),
]);
return <Shop products={products} categories={categories} />;
}
// ❌ Sequential - slower
async function Page() {
const products = await getProducts();
const categories = await getCategories();
}
Streaming with Suspense
import { Suspense } from "react";
export default function Page() {
return (
<>
<Header /> {/* Immediate */}
<Suspense fallback={<ProductsSkeleton />}>
<Products /> {/* Streamed */}
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews /> {/* Streamed independently */}
</Suspense>
</>
);
}
Prefetching
import Link from 'next/link';
// Auto-prefetch links in viewport (production)
<Link href="/products">Products</Link>
// Disable prefetch for rarely visited pages
<Link href="/terms" prefetch={false}>Terms</Link>
// Programmatic prefetch
import { useRouter } from 'next/navigation';
function SearchInput() {
const router = useRouter();
const handleFocus = () => {
router.prefetch('/search-results');
};
return <input onFocus={handleFocus} />;
}
Server Components Benefits
// Heavy dependencies stay on server
import { parse } from "marked"; // 50kb
import { highlight } from "prism"; // 30kb
import { format } from "date-fns"; // 20kb
// Server Component - these don't add to client bundle
export default async function BlogPost({ content }) {
const html = parse(content);
const highlighted = highlight(html);
return <article dangerouslySetInnerHTML={{ __html: highlighted }} />;
}
Performance Checklist
| Area | Optimization |
|---|---|
| Images | Use next/image with proper sizes |
| Fonts | Use next/font, font-display: swap |
| JS | Dynamic imports, tree shaking |
| CSS | CSS Modules, minimal CSS-in-JS |
| Data | Parallel fetching, caching |
| Rendering | Streaming, Suspense |
| Third-party | afterInteractive, lazyOnload |
Key Points
- Use
next/imagewith proper sizing and priority - Use
next/fontfor optimized font loading - Dynamic import heavy components
- Prefetch important routes
- Stream content with Suspense
- Keep dependencies in Server Components
- Analyze bundle and eliminate unused code