# Next.js Optimization

Performance optimization techniques for images, fonts, scripts, and bundles.

## Image Optimization

### Next.js Image Component

Automatic optimization with modern formats (WebP, AVIF):

```tsx
import Image from "next/image";

export default function Page() {
  return (
    <>
      {/* Local image */}
      <Image
        src="/hero.jpg"
        alt="Hero image"
        width={1200}
        height={600}
        priority // Load immediately, no lazy loading
      />

      {/* Remote image */}
      <Image
        src="https://example.com/photo.jpg"
        alt="Photo"
        width={800}
        height={600}
        quality={90} // 1-100, default 75
      />

      {/* Responsive fill */}
      <div style={{ position: "relative", width: "100%", height: "400px" }}>
        <Image
          src="/background.jpg"
          alt="Background"
          fill
          style={{ objectFit: "cover" }}
          sizes="100vw"
        />
      </div>

      {/* With blur placeholder */}
      <Image
        src="/profile.jpg"
        alt="Profile"
        width={200}
        height={200}
        placeholder="blur"
        blurDataURL="data:image/jpeg;base64,..." // Or use static import
      />
    </>
  );
}
```

### Image Props Reference

**Required:**

- `src` - Image path (string or static import)
- `alt` - Alt text for accessibility
- `width`, `height` - Dimensions (required unless using `fill`)

**Optional:**

- `fill` - Fill parent container (makes width/height optional)
- `sizes` - Responsive sizes hint for srcset
- `quality` - 1-100 (default 75)
- `priority` - Disable lazy loading, preload image
- `placeholder` - 'blur' | 'empty' (default 'empty')
- `blurDataURL` - Data URL for blur placeholder
- `loading` - 'lazy' | 'eager' (default 'lazy')
- `style` - CSS styles
- `className` - CSS class
- `onLoad` - Callback when loaded

### Responsive Images with Sizes

```tsx
<Image
  src="/hero.jpg"
  alt="Hero"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
```

This tells browser:

- Mobile (<768px): Use 100% viewport width
- Tablet (768-1200px): Use 50% viewport width
- Desktop (>1200px): Use 33% viewport width

### Static Import for Local Images

```tsx
import heroImage from "@/public/hero.jpg";

<Image
  src={heroImage}
  alt="Hero"
  placeholder="blur" // Automatically generated
  // No width/height needed - inferred from import
/>;
```

### Remote Image Configuration

```js
// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "example.com",
        pathname: "/images/**",
      },
      {
        protocol: "https",
        hostname: "cdn.example.com",
      },
    ],
    // Device sizes for srcset
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    // Image sizes for srcset
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    // Supported formats
    formats: ["image/webp"],
    // Cache optimization images for 60 days
    minimumCacheTTL: 60 * 60 * 24 * 60,
  },
};
```

## Font Optimization

### Google Fonts

Automatic optimization with zero layout shift:

```tsx
// app/layout.tsx
import { Inter, Roboto_Mono, Playfair_Display } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-inter",
});

const robotoMono = Roboto_Mono({
  subsets: ["latin"],
  display: "swap",
  weight: ["400", "700"],
  variable: "--font-roboto-mono",
});

const playfair = Playfair_Display({
  subsets: ["latin"],
  display: "swap",
  weight: ["400", "700", "900"],
  style: ["normal", "italic"],
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
      <body className={inter.className}>{children}</body>
    </html>
  );
}
```

Use CSS variables:

```css
.code {
  font-family: var(--font-roboto-mono);
}
```

### Local Fonts

```tsx
import localFont from "next/font/local";

const myFont = localFont({
  src: [
    {
      path: "./fonts/my-font-regular.woff2",
      weight: "400",
      style: "normal",
    },
    {
      path: "./fonts/my-font-bold.woff2",
      weight: "700",
      style: "normal",
    },
  ],
  variable: "--font-my-font",
  display: "swap",
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={myFont.variable}>
      <body>{children}</body>
    </html>
  );
}
```

### Font Display Strategies

```tsx
const font = Inter({
  display: "swap", // Show fallback immediately, swap when loaded (recommended)
  // display: 'optional', // Only use font if available immediately
  // display: 'block', // Hide text until font loads (max 3s)
  // display: 'fallback', // Show fallback briefly, swap if loaded quickly
  // display: 'auto', // Browser default
});
```

## Script Optimization

### Script Component

Control loading behavior:

```tsx
import Script from "next/script";

export default function Page() {
  return (
    <>
      {/* Load after page is interactive (recommended for analytics) */}
      <Script
        src="https://www.googletagmanager.com/gtag/js"
        strategy="afterInteractive"
      />

      {/* Load while page is idle (lowest priority) */}
      <Script
        src="https://connect.facebook.net/en_US/sdk.js"
        strategy="lazyOnload"
      />

      {/* Load before page is interactive (use sparingly) */}
      <Script
        src="https://maps.googleapis.com/maps/api/js"
        strategy="beforeInteractive"
      />

      {/* Inline script with strategy */}
      <Script id="analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
        `}
      </Script>

      {/* With onLoad callback */}
      <Script
        src="https://example.com/sdk.js"
        onLoad={() => console.log("Script loaded")}
        onError={(e) => console.error("Script failed", e)}
      />
    </>
  );
}
```

**Strategy options:**

- `beforeInteractive` - Load before page interactive (blocking)
- `afterInteractive` - Load after page interactive (default)
- `lazyOnload` - Load during idle time
- `worker` - Load in web worker (experimental)

## Bundle Optimization

### Analyzing Bundle Size

```bash
# Install bundle analyzer
npm install @next/bundle-analyzer

# Create next.config.js wrapper
```

```js
// next.config.js
const withBundleAnalyzer = require("@next/bundle-analyzer")({
  enabled: process.env.ANALYZE === "true",
});

module.exports = withBundleAnalyzer({
  // Your Next.js config
});
```

```bash
# Run analysis
ANALYZE=true npm run build
```

### Dynamic Import (Code Splitting)

Split code and load on-demand:

```tsx
import dynamic from "next/dynamic";

// Dynamic import with loading state
const DynamicChart = dynamic(() => import("@/components/chart"), {
  loading: () => <div>Loading chart...</div>,
  ssr: false, // Disable SSR for this component
});

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <DynamicChart />
    </div>
  );
}
```

Named exports:

```tsx
const DynamicComponent = dynamic(() =>
  import("@/components/hello").then((mod) => mod.Hello),
);
```

Multiple components:

```tsx
const DynamicHeader = dynamic(() => import("@/components/header"));
const DynamicFooter = dynamic(() => import("@/components/footer"));
```

### Tree Shaking

Import only what you need:

```tsx
// ❌ Bad - imports entire library
import _ from "lodash";
const result = _.debounce(fn, 300);

// ✅ Good - imports only debounce
import debounce from "lodash/debounce";
const result = debounce(fn, 300);

// ❌ Bad
import * as Icons from "react-icons/fa";
<Icons.FaHome />;

// ✅ Good
import { FaHome } from "react-icons/fa";
<FaHome />;
```

## Partial Prerendering (PPR)

Experimental: Combine static and dynamic rendering in same route.

```js
// next.config.js
module.exports = {
  experimental: {
    ppr: true,
  },
};
```

```tsx
// app/page.tsx
import { Suspense } from "react";

// Static shell
export default function Page() {
  return (
    <div>
      <header>Static Header</header>

      {/* Dynamic content with Suspense boundary */}
      <Suspense fallback={<div>Loading...</div>}>
        <DynamicContent />
      </Suspense>

      <footer>Static Footer</footer>
    </div>
  );
}

// Dynamic component
async function DynamicContent() {
  const data = await fetch("https://api.example.com/data", {
    cache: "no-store",
  }).then((r) => r.json());

  return <div>{data.content}</div>;
}
```

Static shell loads instantly, dynamic content streams in.

## Metadata Optimization

### Static Metadata

```tsx
// app/page.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "My Page",
  description: "Page description",
  keywords: ["next.js", "react", "javascript"],
  openGraph: {
    title: "My Page",
    description: "Page description",
    images: ["/og-image.jpg"],
  },
  twitter: {
    card: "summary_large_image",
    title: "My Page",
    description: "Page description",
    images: ["/twitter-image.jpg"],
  },
  alternates: {
    canonical: "https://example.com/page",
  },
  robots: {
    index: true,
    follow: true,
  },
};
```

### Dynamic Metadata

```tsx
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
      type: "article",
      publishedTime: post.publishedAt,
      authors: [post.author.name],
    },
  };
}
```

### Metadata Files

Create these files in `app/` directory:

- `favicon.ico` - Favicon
- `icon.png` / `icon.jpg` - App icon
- `apple-icon.png` - Apple touch icon
- `opengraph-image.png` - Open Graph image
- `twitter-image.png` - Twitter card image
- `robots.txt` - Robots file
- `sitemap.xml` - Sitemap

Or generate dynamically:

```tsx
// app/sitemap.ts
export default async function sitemap() {
  const posts = await getPosts();

  return [
    {
      url: "https://example.com",
      lastModified: new Date(),
    },
    ...posts.map((post) => ({
      url: `https://example.com/blog/${post.slug}`,
      lastModified: post.updatedAt,
    })),
  ];
}
```

## Performance Best Practices

1. **Use Image component** - Automatic optimization, lazy loading, modern formats
2. **Optimize fonts** - Use next/font to eliminate layout shift
3. **Dynamic imports** - Code split large components and third-party libraries
4. **Analyze bundle** - Identify and eliminate large dependencies
5. **Proper caching** - Use ISR for semi-static content
6. **Streaming with Suspense** - Load fast content first, stream slow content
7. **Minimize JavaScript** - Default to Server Components
8. **Prefetch links** - Next.js prefetches Link components in viewport
9. **Use Script component** - Control third-party script loading
10. **Compress assets** - Enable compression in hosting platform
11. **Use CDN** - Deploy to edge network (Vercel, Cloudflare)
12. **Monitor metrics** - Track Core Web Vitals (LCP, FID, CLS)
