REACT & NEXT.JS / 9. PERFORMANCE & PRODUCTION

Performance & Production

Image optimization, fonts, bundle analysis — shipping fast apps


EXPLANATION

Next.js has several built-in optimizations but you need to use them correctly.

next/image:
• Automatic WebP/AVIF conversion
• Lazy loading by default
• Prevents layout shift (requires width/height or fill)
• Resizes on the server — never serve a 4000px image on mobile

next/font:
• Downloads fonts at build time (no external network request)
• Automatically applies font-display: swap
• Eliminates layout shift from font loading

next/link:
• Prefetches pages when link enters viewport
• Client-side navigation (no full page reload)

Bundle optimization:
• Dynamic imports → code splitting. Don't load heavy libraries until needed
• Server Components → zero client JS for static content
• Analyze with: ANALYZE=true bun run build

Performance checklist:
• Images: use next/image, always specify sizes
• Fonts: use next/font, subset to needed characters
• Data: fetch in Server Components where possible
• Code: dynamic import heavy client libraries
• Cache: revalidate strategies for dynamic data

DIAGRAM

Without next/image:
  Client → requests 4MB PNG
  → browser downloads full size
  → layout shift while loading

  With next/image:
  Client → requests image with screen width hint
  → Next.js resizes/converts to WebP on server
  → serves optimized version
  → placeholder prevents layout shift

  Bundle splitting:
  Without dynamic import:
  page.js → [all code including heavy chart library]

  With dynamic import:
  page.js → [core code only]
  chart.js → [loaded only when chart renders]

CODE

TSX
1// ── next/image: always use this for images ───────────────────────
2import Image from "next/image";
3
4// Fixed size image
5export function Avatar({ src, name }: { src: string; name: string }) {
6 return (
7 <Image
8 src={src}
9 alt={name}
10 width={48}
11 height={48}
12 className="rounded-full"
13 />
14 );
15}
16
17// Full-width responsive image (fill parent)
18export function HeroBanner({ src }: { src: string }) {
19 return (
20 <div className="relative h-64 w-full">
21 <Image
22 src={src}
23 alt="Hero"
24 fill // fills parent container
25 priority // LCP image don't lazy load
26 sizes="100vw" // hint for responsive sizing
27 className="object-cover"
28 />
29 </div>
30 );
31}
32
33// ── next/font: no layout shift, no external request ───────────────
34// app/layout.tsx
35import { Inter, JetBrains_Mono } from "next/font/google";
36
37const inter = Inter({
38 subsets: ["latin"],
39 variable: "--font-inter",
40 display: "swap",
41});
42
43const mono = JetBrains_Mono({
44 subsets: ["latin"],
45 variable: "--font-mono",
46});
47
48export default function RootLayout({ children }: { children: React.ReactNode }) {
49 return (
50 <html lang="en" className={`${inter.variable} ${mono.variable}`}>
51 <body className="font-sans">{children}</body>
52 </html>
53 );
54}
55
56// ── Dynamic import: code splitting ────────────────────────────────
57import dynamic from "next/dynamic";
58
59// Don't load the chart library until this component renders
60const ChartComponent = dynamic(() => import("./ChartComponent"), {
61 loading: () => <div className="animate-pulse h-64 bg-gray-800 rounded" />,
62 ssr: false, // don't render on server (browser-only library)
63});
64
65// Don't load the Monaco editor until user clicks "Edit"
66const CodeEditor = dynamic(() => import("@monaco-editor/react"), {
67 ssr: false,
68});
69
70// ── next/link: prefetching ────────────────────────────────────────
71import Link from "next/link";
72
73export function NavLinks() {
74 return (
75 <nav>
76 <Link href="/rag" prefetch={true}>RAG & LLMs</Link>
77 <Link href="/deep-learning">Deep Learning</Link>
78 {/* prefetch={false} for links unlikely to be clicked */}
79 <Link href="/settings" prefetch={false}>Settings</Link>
80 </nav>
81 );
82}
83
84// ── Bundle analyzer setup ─────────────────────────────────────────
85// next.config.ts
86import bundleAnalyzer from "@next/bundle-analyzer";
87const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === "true" });
88export default withBundleAnalyzer({ /* your config */ });
89// Run: ANALYZE=true bun run build opens visual bundle map
90
91// ── Metadata for SEO ──────────────────────────────────────────────
92import type { Metadata } from "next";
93
94export const metadata: Metadata = {
95 title: "deepdocs",
96 description: "Personal AI/ML learning documentation",
97 openGraph: {
98 title: "deepdocs",
99 description: "Everything I learn, documented.",
100 images: [{ url: "/og-image.png", width: 1200, height: 630 }],
101 },
102 twitter: {
103 card: "summary_large_image",
104 creator: "@KAMRANKHANALWI",
105 },
106};
← PREV8. API Routes & Middleware