Topic 10 of 47
Performance Optimization
Overview
Next.js has many built-in performance features, but you must know how to use them correctly. Understanding code splitting, streaming, Suspense boundaries, dynamic imports, and caching strategies is key to building fast apps.
Syntax
tsx
// Dynamic import — code splitting
import dynamic from 'next/dynamic';
// Don't load heavy chart library until needed
const ChartComponent = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // disable SSR for browser-only components
});
// Streaming with Suspense
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
{/* Static content renders immediately */}
<h1>Dashboard</h1>
{/* Slow data streams in when ready */}
<Suspense fallback={<StatsSkeleton />}>
<SlowStats /> {/* fetches data independently */}
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* also independent */}
</Suspense>
</div>
);
}
// Font optimization
import { Inter, Poppins } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
// Eliminates FOUT — fonts are preloaded at build timeCommon Pitfalls
- Without Suspense boundaries, a slow Server Component blocks the entire page — wrap slow components in <Suspense> for streaming.
- dynamic(() => import('./Component'), { ssr: false }) is needed for components that use window, document, or other browser globals.
- Interview tip: Next.js uses the Island Architecture — static HTML + selective JS hydration. Server Components never hydrate (no JS sent to client).
Real-World Example
Optimizing a slow dashboard with streaming and Suspense:
example
tsx
// Each section fetches data independently — no waterfall!
export default function AnalyticsDashboard() {
return (
<DashboardLayout>
<div className="grid grid-cols-3 gap-4">
{/* Fast data — renders first */}
<Suspense fallback={<MetricSkeleton />}>
<TodaysOrders />
</Suspense>
{/* Slow aggregation — streams in when ready */}
<Suspense fallback={<MetricSkeleton />}>
<MonthlyRevenue />
</Suspense>
<Suspense fallback={<MetricSkeleton />}>
<ConversionRate />
</Suspense>
</div>
{/* Heavy chart — lazy loaded */}
<Suspense fallback={<ChartSkeleton height={400} />}>
<RevenueChart />
</Suspense>
</DashboardLayout>
);
}
// Server Component — async, direct DB
async function MonthlyRevenue() {
// This slow query doesn't block other sections!
const revenue = await db.orders.aggregate({ /* complex query */ });
return <MetricCard title="Monthly Revenue" value={revenue._sum.amount} />;
}
// generateStaticParams for all product pages
export async function generateStaticParams() {
const products = await prisma.product.findMany({ select: { slug: true } });
return products.map(p => ({ slug: p.slug }));
}