mirror of
https://github.com/greedychipmunk/agent-skills.git
synced 2026-09-14 20:46:42 +08:00
8.1 KiB
8.1 KiB
Next.js Performance Optimization
Overview
Next.js provides built-in performance optimizations. This guide covers best practices for maximizing application performance.
Image Optimization
Using next/image
import Image from "next/image"
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load immediately for LCP
/>
)
}
Responsive Images
<Image
src="/photo.jpg"
alt="Photo"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
Remote Images
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/images/**",
},
],
},
}
Image Props Reference
| Prop | Purpose |
|---|---|
priority |
Preload image (use for LCP) |
loading="lazy" |
Lazy load (default for non-priority) |
placeholder="blur" |
Show blur while loading |
quality={75} |
Image quality (1-100) |
fill |
Fill parent container |
sizes |
Responsive size hints |
Font Optimization
Using next/font
// app/layout.tsx
import { Inter, Roboto_Mono } from "next/font/google"
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
})
const robotoMono = Roboto_Mono({
subsets: ["latin"],
display: "swap",
variable: "--font-roboto-mono",
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
Local Fonts
import localFont from "next/font/local"
const myFont = localFont({
src: "./my-font.woff2",
display: "swap",
})
export default function Layout({ children }) {
return (
<html className={myFont.className}>
<body>{children}</body>
</html>
)
}
Script Optimization
Using next/script
import Script from "next/script"
export default function Page() {
return (
<>
{/* Load after page is interactive */}
<Script
src="https://analytics.example.com/script.js"
strategy="afterInteractive"
/>
{/* Load during browser idle time */}
<Script
src="https://widget.example.com/script.js"
strategy="lazyOnload"
/>
{/* Block page load (rarely needed) */}
<Script
src="https://critical.example.com/script.js"
strategy="beforeInteractive"
/>
</>
)
}
Script Strategies
| Strategy | When to Use |
|---|---|
beforeInteractive |
Critical scripts that must load before hydration |
afterInteractive |
Analytics, tracking (default) |
lazyOnload |
Low-priority scripts |
worker |
Load in web worker (experimental) |
Caching Strategies
Static Data (Default)
// Cached indefinitely
async function getData() {
const res = await fetch("https://api.example.com/data")
return res.json()
}
Time-Based Revalidation
// Revalidate every hour
async function getData() {
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 3600 },
})
return res.json()
}
On-Demand Revalidation
// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from "next/cache"
export async function POST(request: Request) {
const { tag, path } = await request.json()
if (tag) {
revalidateTag(tag)
} else if (path) {
revalidatePath(path)
}
return Response.json({ revalidated: true })
}
Route Segment Config
// Force dynamic rendering
export const dynamic = "force-dynamic"
// Force static rendering
export const dynamic = "force-static"
// Set revalidation time
export const revalidate = 3600
Bundle Optimization
Dynamic Imports
import dynamic from "next/dynamic"
// Load component only when needed
const HeavyChart = dynamic(() => import("./heavy-chart"), {
loading: () => <div>Loading chart...</div>,
ssr: false, // Disable SSR for client-only components
})
export default function Dashboard() {
return <HeavyChart />
}
Named Exports
const Chart = dynamic(
() => import("./charts").then((mod) => mod.LineChart),
{ loading: () => <p>Loading...</p> }
)
Code Splitting by Route
Routes are automatically code-split. Each page only loads its dependencies.
Streaming and Suspense
Loading UI
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />
}
Granular Suspense
import { Suspense } from "react"
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<Chart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable />
</Suspense>
</div>
)
}
Server Components
Benefits
- Zero JavaScript sent to client
- Direct backend access
- Improved initial load time
- Better SEO
Best Practices
// GOOD: Server Component (default)
export default async function PostList() {
const posts = await db.post.findMany()
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
// Only use "use client" when needed
"use client"
export function InteractiveButton() {
const [clicked, setClicked] = useState(false)
return <button onClick={() => setClicked(true)}>Click</button>
}
Prefetching
Automatic Prefetching
Links in viewport are automatically prefetched:
import Link from "next/link"
// Prefetched automatically when visible
<Link href="/about">About</Link>
// Disable prefetching
<Link href="/heavy-page" prefetch={false}>Heavy Page</Link>
Manual Prefetching
"use client"
import { useRouter } from "next/navigation"
export function PrefetchButton() {
const router = useRouter()
return (
<button
onMouseEnter={() => router.prefetch("/dashboard")}
onClick={() => router.push("/dashboard")}
>
Go to Dashboard
</button>
)
}
Metadata Optimization
Static Metadata
export const metadata = {
title: "My Site",
description: "Welcome to my site",
openGraph: {
title: "My Site",
description: "Welcome to my site",
images: ["/og-image.jpg"],
},
}
Dynamic Metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.id)
return {
title: post.title,
description: post.excerpt,
}
}
Build Optimization
Analyze Bundle
# 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
Optimize Dependencies
// next.config.js
module.exports = {
// Reduce moment.js bundle size
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
"moment/locale": false,
}
return config
},
}
Performance Checklist
Images
- Use
next/imagefor all images - Add
priorityto LCP image - Provide
sizesfor responsive images - Use appropriate quality settings
Fonts
- Use
next/fontfor all fonts - Specify
display: swap - Subset fonts to needed characters
JavaScript
- Use Server Components by default
- Dynamic import heavy components
- Analyze bundle size regularly
Data Fetching
- Implement appropriate caching
- Use parallel data fetching
- Stream content with Suspense
Core Web Vitals
- Optimize LCP (largest contentful paint)
- Minimize CLS (cumulative layout shift)
- Reduce FID/INP (interaction delay)
Last Updated: January 2026