Next.js 的性能优化是构建高性能 Web 应用的关键。Next.js 提供了多种内置优化功能,同时也需要开发者掌握各种优化技巧。
核心性能优化策略
1. 图片优化
javascript// 使用 next/image 组件 import Image from 'next/image'; // 基础用法 <Image src="/hero.jpg" alt="Hero image" width={1920} height={1080} priority // 首屏图片使用 priority /> // 响应式图片 <Image src="/hero.jpg" alt="Hero image" fill sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." /> // 远程图片配置 // next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com', port: '', pathname: '/images/**', }, ], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], formats: ['image/webp', 'image/avif'], }, }; // 动态图片 async function getImages() { const images = await fetch('https://api.example.com/images').then(r => r.json()); return images; } export default async function Gallery() { const images = await getImages(); return ( <div className="grid"> {images.map((image) => ( <Image key={image.id} src={image.url} alt={image.alt} width={image.width} height={image.height} loading="lazy" // 非首屏图片使用 lazy /> ))} </div> ); }
2. 字体优化
javascript// 使用 next/font 优化字体加载 import { Inter, Roboto } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-inter', preload: true, }); const roboto = Roboto({ weight: ['400', '500', '700'], subsets: ['latin'], display: 'swap', variable: '--font-roboto', }); // 在 layout.js 中应用 export default function RootLayout({ children }) { return ( <html lang="en" className={`${inter.variable} ${roboto.variable}`}> <body className={inter.className}>{children}</body> </html> ); } // 使用本地字体 import localFont from 'next/font/local'; const myFont = localFont({ src: [ { path: './fonts/MyFont-Regular.woff2', weight: '400', style: 'normal', }, { path: './fonts/MyFont-Bold.woff2', weight: '700', style: 'normal', }, ], display: 'swap', variable: '--font-my-font', });
3. 代码分割和懒加载
javascript// 动态导入组件 import dynamic from 'next/dynamic'; // 基础动态导入 const DynamicComponent = dynamic(() => import('../components/HeavyComponent')); // 带加载状态的动态导入 const DynamicComponentWithLoading = dynamic( () => import('../components/HeavyComponent'), { loading: () => <p>Loading...</p>, ssr: false, // 禁用服务端渲染 } ); // 条件加载 const ConditionalComponent = dynamic( () => import('../components/ConditionalComponent'), { ssr: false } ); export default function Page() { const [showComponent, setShowComponent] = useState(false); return ( <div> <button onClick={() => setShowComponent(true)}> Load Component </button> {showComponent && <ConditionalComponent />} </div> ); } // 路由级别的代码分割 // Next.js 自动为每个路由创建独立的代码块 // app/page.js -> page.js // app/about/page.js -> about/page.js
4. 数据获取优化
javascript// 使用 fetch API 的缓存选项 async function getData() { // 强制缓存(默认) const res1 = await fetch('https://api.example.com/data', { cache: 'force-cache', }); // 不缓存 const res2 = await fetch('https://api.example.com/data', { cache: 'no-store', }); // 重新验证缓存 const res3 = await fetch('https://api.example.com/data', { next: { revalidate: 3600 }, // 1小时后重新验证 }); // 按需重新验证 const res4 = await fetch('https://api.example.com/data', { next: { tags: ['posts'] }, }); return res.json(); } // 使用 React Query 进行客户端数据缓存 'use client'; import { useQuery } from '@tanstack/react-query'; function usePosts() { return useQuery({ queryKey: ['posts'], queryFn: () => fetch('/api/posts').then(r => r.json()), staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜 cacheTime: 10 * 60 * 1000, // 10分钟后清除缓存 }); } // 使用 SWR 进行数据获取 'use client'; import useSWR from 'swr'; const fetcher = (url) => fetch(url).then((r) => r.json()); function usePosts() { const { data, error, isLoading } = useSWR('/api/posts', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, dedupingInterval: 60000, // 1分钟内去重请求 }); return { data, error, isLoading }; }
5. 缓存策略
javascript// 使用 Redis 进行缓存 import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); async function getCachedData(key, fetcher, ttl = 3600) { const cached = await redis.get(key); if (cached) { return JSON.parse(cached); } const data = await fetcher(); await redis.set(key, JSON.stringify(data), { ex: ttl }); return data; } // 使用示例 async function getPosts() { return getCachedData( 'posts', () => fetch('https://api.example.com/posts').then(r => r.json()), 3600 ); } // 使用 Vercel KV 缓存 import { kv } from '@vercel/kv'; async function getCachedPosts() { const cached = await kv.get('posts'); if (cached) { return cached; } const posts = await fetch('https://api.example.com/posts').then(r => r.json()); await kv.set('posts', posts, { ex: 3600 }); return posts; }
6. 预加载和预取
javascript// 预加载页面 import Link from 'next/link'; export default function Navigation() { return ( <nav> <Link href="/about" prefetch={true}> About </Link> <Link href="/contact" prefetch={true}> Contact </Link> </nav> ); } // 预加载资源 export default function Page() { return ( <> <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossOrigin="" /> <link rel="preload" href="/hero.jpg" as="image" /> <link rel="preconnect" href="https://api.example.com" /> </> ); } // 使用 prefetch 进行数据预取 'use client'; import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; export default function ProductCard({ product }) { const router = useRouter(); const handleMouseEnter = () => { router.prefetch(`/products/${product.id}`); }; return ( <div onMouseEnter={handleMouseEnter}> <h3>{product.name}</h3> <Link href={`/products/${product.id}`}> View Details </Link> </div> ); }
7. 构建优化
javascript// next.config.js module.exports = { // 压缩输出 compress: true, // 生产环境优化 productionBrowserSourceMaps: false, // SWC 压缩器(比 Terser 更快) swcMinify: true, // 实验性功能 experimental: { // 优化 CSS optimizeCss: true, // 优化包导入 optimizePackageImports: ['lucide-react', '@radix-ui/react-icons'], }, // Webpack 优化 webpack: (config, { isServer }) => { if (!isServer) { config.resolve.fallback = { ...config.resolve.fallback, fs: false, net: false, tls: false, }; } return config; }, // 模块联邦(微前端) webpack: (config) => { config.optimization = { ...config.optimization, splitChunks: { chunks: 'all', cacheGroups: { default: false, vendors: false, vendor: { name: 'vendor', chunks: 'all', test: /node_modules/, priority: 20, }, common: { name: 'common', minChunks: 2, chunks: 'all', priority: 10, reuseExistingChunk: true, enforce: true, }, }, }, }; return config; }, };
8. 性能监控
javascript// 使用 Web Vitals 监控 // app/layout.js 'use client'; import { useReportWebVitals } from 'next/web-vitals'; export function WebVitals() { useReportWebVitals((metric) => { // 发送到分析服务 fetch('/api/analytics', { method: 'POST', body: JSON.stringify(metric), }); }); return null; } // 自定义性能监控 'use client'; import { useEffect } from 'react'; export function PerformanceMonitor() { useEffect(() => { if (typeof window !== 'undefined' && 'PerformanceObserver' in window) { // 监控 LCP const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('LCP:', entry.startTime); } }); observer.observe({ entryTypes: ['largest-contentful-paint'] }); // 监控 FID const fidObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('FID:', entry.processingStart - entry.startTime); } }); fidObserver.observe({ entryTypes: ['first-input'] }); // 监控 CLS const clsObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('CLS:', entry.value); } }); clsObserver.observe({ entryTypes: ['layout-shift'] }); } }, []); return null; }
9. 服务端优化
javascript// 使用 Edge Runtime export const runtime = 'edge'; export default async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 60 }, }).then(r => r.json()); return <div>{data.title}</div>; } // 使用 ISR(增量静态再生) export const revalidate = 3600; // 1小时 export default async function BlogPage() { const posts = await getPosts(); return ( <div> {posts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.excerpt}</p> </article> ))} </div> ); } // 使用 On-Demand Revalidation import { revalidatePath } from 'next/cache'; export async function POST(request) { const res = await request.json(); await updatePost(res.id, res.data); // 重新验证特定页面 revalidatePath(`/blog/${res.id}`); revalidatePath('/blog'); return Response.json({ success: true }); }
10. 客户端优化
javascript// 使用 React.memo 避免不必要的重新渲染 'use client'; import { memo } from 'react'; const ExpensiveComponent = memo(({ data }) => { return <div>{/* 复杂渲染逻辑 */}</div>; }); // 使用 useMemo 缓存计算结果 'use client'; import { useMemo } from 'react'; function Component({ items }) { const sortedItems = useMemo(() => { return items.sort((a, b) => a.value - b.value); }, [items]); return <div>{/* 使用 sortedItems */}</div>; } // 使用 useCallback 缓存函数 'use client'; import { useCallback } from 'react'; function ParentComponent() { const handleClick = useCallback(() => { console.log('Clicked'); }, []); return <ChildComponent onClick={handleClick} />; } // 虚拟化长列表 'use client'; import { useVirtualizer } from '@tanstack/react-virtual'; function VirtualList({ items }) { const parentRef = useRef(); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, }); return ( <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}> <div style={{ height: `${virtualizer.getTotalSize()}px` }}> {virtualizer.getVirtualItems().map((virtualItem) => ( <div key={virtualItem.key} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: `${virtualItem.size}px`, transform: `translateY(${virtualItem.start}px)`, }} > {items[virtualItem.index]} </div> ))} </div> </div> ); }
性能检查清单
Core Web Vitals
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
优化检查项
- ✅ 使用
next/image优化图片 - ✅ 使用
next/font优化字体加载 - ✅ 实现代码分割和懒加载
- ✅ 优化数据获取和缓存策略
- ✅ 使用 ISR 和 On-Demand Revalidation
- ✅ 实现预加载和预取
- ✅ 优化构建配置
- ✅ 监控 Web Vitals
- ✅ 使用 Edge Runtime
- ✅ 客户端性能优化(memo、useMemo、useCallback)
通过综合运用这些优化策略,可以显著提升 Next.js 应用的性能表现。