在现代 Web 应用中,性能优化直接影响用户体验和业务转化。Next.js 14 提供了多种内置的性能优化特性,今天我们就来深入探讨如何充分利用这些特性,以及一些实用的优化技巧。
图片和字体优化
1. 图片优化
Next.js 的 Image 组件供了强大的图片优化功能:
typescript
// components/OptimizedImage.tsx
import Image from 'next/image';
import { useState } from 'react';
interface OptimizedImageProps {
src: string;
alt: string;
width: number;
height: number;
}
export function OptimizedImage({
src,
alt,
width,
height
}: OptimizedImageProps) {
const [isLoading, setLoading] = useState(true);
return (
<div className="relative overflow-hidden">
<Image
src={src}
alt={alt}
width={width}
height={height}
quality={75} // 默认图片质量
placeholder="blur" // 使用模糊占位
blurDataURL="data:image/jpeg;base64,..." // 生成的 base64 图片
className={`
duration-700 ease-in-out
${isLoading ? 'scale-110 blur-2xl' : 'scale-100 blur-0'}
`}
onLoadingComplete={() => setLoading(false)}
priority={false} // 是否优先加载
/>
</div>
);
}
// 使用自定义图片加载器
const imageLoader = ({ src, width, quality }) => {
return `https://your-cdn.com/${src}?w=${width}&q=${quality || 75}`;
};
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
loader: 'custom',
loaderFile: './lib/imageLoader.ts',
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/webp'],
},
};
2. 字体优化
Next.js 14 提供了内置的字体优化:
typescript
// app/fonts.ts
import { Inter, Roboto_Mono } from 'next/font/google';
export const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
fallback: ['system-ui', 'arial'],
adjustFontFallback: true, // 自动调整回退字体
});
export const roboto_mono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
});
// app/layout.tsx
import { inter } from './fonts';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
// 自定义字体加载
import localFont from 'next/font/local';
const myFont = localFont({
src: [
{
path: '../public/fonts/font-regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../public/fonts/font-bold.woff2',
weight: '700',
style: 'normal',
},
],
display: 'swap',
preload: true,
});
动态导入和代码分割
1. 组件动态导入
typescript
// components/DynamicComponent.tsx
import dynamic from 'next/dynamic';
import { Suspense } from 'react';
// 基础动态导入
const DynamicHeader = dynamic(() => import('./Header'), {
loading: () => <p>Loading...</p>,
ssr: true, // 是否服务端渲染
});
// 带有自定义加载的动态导入
const DynamicChart = dynamic(
() => import('./Chart').then(mod => mod.Chart),
{
loading: () => <ChartSkeleton />,
ssr: false, // 禁用服务端渲染
}
);
// 使用 Suspense 包裹动态组件
export function DashboardPage() {
return (
<div>
<DynamicHeader />
<Suspense fallback={<ChartSkeleton />}>
<DynamicChart />
</Suspense>
</div>
);
}
2. 路由分组和懒加载
typescript
// app/(marketing)/layout.tsx
import { Suspense } from 'react';
// 营销相关页面的布局
export default function MarketingLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<div className="marketing-layout">
<Suspense fallback={<NavSkeleton />}>
<Navigation />
</Suspense>
{children}
</div>
);
}
// app/(dashboard)/layout.tsx
// 仪表板相关页面的布局
export default function DashboardLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard-layout">
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<main>{children}</main>
</div>
);
}
缓存策略优化
1. 数据缓存
typescript
// lib/cache.ts
import { cache } from 'react';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.REDIS_URL,
token: process.env.REDIS_TOKEN,
});
// 使用 React 缓存
export const getCachedData = cache(async (key: string) => {
// 首先尝试从 Redis 获取
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
// 如果没有缓存,则获取新数据
const data = await fetchData(key);
// 存入 Redis
await redis.set(key, JSON.stringify(data), {
ex: 3600 // 1小时过期
});
return data;
});
// 使用示例
async function ProductPage({ id }: { id: string }) {
const product = await getCachedData(`product:${id}`);
return <ProductDetails product={product} />;
}
2. 静态生成优化
typescript
// app/products/[id]/page.tsx
import { generateMetadata } from 'next';
// 生成静态路由
export async function generateStaticParams() {
const products = await getTopProducts();
return products.map((product) => ({
id: product.id,
}));
}
// 静态元数据
export async function generateMetadata({ params }: {
params: { id: string }
}): Promise<Metadata> {
const product = await getProduct(params.id);
return {
title: product.name,
description: product.description,
openGraph: {
images: [product.image],
},
};
}
// 页面组件
export default async function ProductPage({
params
}: {
params: { id: string }
}) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<ProductDetails product={product} />
</div>
);
}
首屏加载优化
1. 流式渲染
typescript
// app/page.tsx
import { Suspense } from 'react';
import { headers } from 'next/headers';
async function SlowComponent() {
const headersList = headers();
const userAgent = headersList.get('user-agent');
// 模拟慢速数据加载
await new Promise(resolve => setTimeout(resolve, 2000));
return (
<div>
<p>User Agent: {userAgent}</p>
</div>
);
}
export default function HomePage() {
return (
<div>
<h1>即时加载的内容</h1>
<Suspense fallback={<LoadingSkeleton />}>
<SlowComponent />
</Suspense>
<Suspense fallback={<CardsSkeleton />}>
<PopularProducts />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<RecentActivity />
</Suspense>
</div>
);
}
2. 预加载数据
typescript
// lib/prefetch.ts
export async function prefetchData() {
// 预加载关键数据
const promises = [
prefetchNavigation(),
prefetchUserData(),
prefetchPopularProducts(),
];
await Promise.all(promises);
}
// app/layout.tsx
export default async function RootLayout({
children
}: {
children: React.ReactNode
}) {
// 在布局组件中预加载数据
await prefetchData();
return (
<html>
<body>{children}</body>
</html>
);
}
Core Web Vitals 优化
1. 性能监控
typescript
// lib/analytics.ts
export function reportWebVitals({
id,
name,
label,
value,
}: {
id: string;
name: string;
label: string;
value: number;
}) {
// 发送性能指标到分析服务
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
id,
name,
label,
value,
// 添加其他上下文信息
page: window.location.pathname,
timestamp: Date.now(),
}),
});
}
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
};
// instrumentation.ts
export function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// 服务端监控
require('./monitoring/server').setup();
}
}
2. 性能优化实践
typescript
// components/OptimizedList.tsx
import { useVirtualizer } from '@tanstack/react-virtual';
import { useIntersectionObserver } from '@/hooks/useIntersectionObserver';
// 虚拟列表优化
export function OptimizedList({ items }: { items: any[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} className="h-[500px] overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{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)`,
}}
>
<ListItem item={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}
// 图片懒加载优化
export function LazyImage({ src, alt }: { src: string; alt: string }) {
const imgRef = useRef<HTMLImageElement>(null);
const { isIntersecting } = useIntersectionObserver(imgRef);
return (
<img
ref={imgRef}
src={isIntersecting ? src : ''}
alt={alt}
loading="lazy"
decoding="async"
/>
);
}
3. 构建优化
typescript
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
/** @type {import('next').NextConfig} */
const nextConfig = {
// 优化构建配置
poweredByHeader: false,
compress: true,
productionBrowserSourceMaps: false,
// 优化图片配置
images: {
minimumCacheTTL: 60,
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
// 实验性功能
experimental: {
optimizeCss: true, // 启用 CSS 优化
scrollRestoration: true, // 启用滚动位置恢复
serverActions: true, // 启用服务端操作
},
// webpack 配置
webpack: (config, { dev, isServer }) => {
// 优化生产环境构建
if (!dev && !isServer) {
config.optimization.splitChunks = {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
};
}
return config;
},
};
module.exports = withBundleAnalyzer(nextConfig);
写在最后
Next.js 14 提供了丰富的性能优化工具和特性。在实际应用中,需要注意以下几点:
- 合理使用图片和字体优化
- 实施有效的代码分割策略
- 优化数据缓存和预加载
- 监控和优化 Core Web Vitals
- 持续进行构建优化
在下一篇文章中,我们将深入探讨 Next.js 14 的部署与运维策略。如果你有任何问题或建议,欢迎在评论区讨论!
如果觉得这篇文章对你有帮助,别忘了点个赞 👍