Next.js 14 性能优化:从首屏加载到运行时优化的最佳实践

[复制链接]
发表于 2025-11-16 23:38:42 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在今世 Web 应用中,性能优化直接影响用户体验和业务转化。Next.js 14 提供了多种内置的性能优化特性,本日我们就来深入探究怎样充实利用这些特性,以及一些实用的优化本领。
图片和字体优化

1. 图片优化

Next.js 的 Image 组件供了强盛的图片优化功能:
  1. // components/OptimizedImage.tsx
  2. import Image from 'next/image';
  3. import { useState } from 'react';
  4. interface OptimizedImageProps {
  5.   src: string;
  6.   alt: string;
  7.   width: number;
  8.   height: number;
  9. }
  10. export function OptimizedImage({
  11.   src,
  12.   alt,
  13.   width,
  14.   height
  15. }: OptimizedImageProps) {
  16.   const [isLoading, setLoading] = useState(true);
  17.   return (
  18.     <div className="relative overflow-hidden">
  19.       <Image
  20.         src={src}
  21.         alt={alt}
  22.         width={width}
  23.         height={height}
  24.         quality={75} // 默认图片质量
  25.         placeholder="blur" // 使用模糊占位
  26.         blurDataURL="data:image/jpeg;base64,..." // 生成的 base64 图片
  27.         className={`
  28.           duration-700 ease-in-out
  29.           ${isLoading ? 'scale-110 blur-2xl' : 'scale-100 blur-0'}
  30.         `}
  31.         onLoadingComplete={() => setLoading(false)}
  32.         priority={false} // 是否优先加载
  33.       />
  34.     </div>
  35.   );
  36. }
  37. // 使用自定义图片加载器
  38. const imageLoader = ({ src, width, quality }) => {
  39.   return `https://your-cdn.com/${src}?w=${width}&q=${quality || 75}`;
  40. };
  41. // next.config.js
  42. /** @type {import('next').NextConfig} */
  43. const nextConfig = {
  44.   images: {
  45.     loader: 'custom',
  46.     loaderFile: './lib/imageLoader.ts',
  47.     deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  48.     imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  49.     formats: ['image/webp'],
  50.   },
  51. };
复制代码
2. 字体优化

Next.js 14 提供了内置的字体优化:
  1. // app/fonts.ts
  2. import { Inter, Roboto_Mono } from 'next/font/google';
  3. export const inter = Inter({
  4.   subsets: ['latin'],
  5.   display: 'swap',
  6.   preload: true,
  7.   fallback: ['system-ui', 'arial'],
  8.   adjustFontFallback: true, // 自动调整回退字体
  9. });
  10. export const roboto_mono = Roboto_Mono({
  11.   subsets: ['latin'],
  12.   display: 'swap',
  13. });
  14. // app/layout.tsx
  15. import { inter } from './fonts';
  16. export default function RootLayout({
  17.   children,
  18. }: {
  19.   children: React.ReactNode;
  20. }) {
  21.   return (
  22.     <html lang="en" className={inter.className}>
  23.       <body>{children}</body>
  24.     </html>
  25.   );
  26. }
  27. // 自定义字体加载
  28. import localFont from 'next/font/local';
  29. const myFont = localFont({
  30.   src: [
  31.     {
  32.       path: '../public/fonts/font-regular.woff2',
  33.       weight: '400',
  34.       style: 'normal',
  35.     },
  36.     {
  37.       path: '../public/fonts/font-bold.woff2',
  38.       weight: '700',
  39.       style: 'normal',
  40.     },
  41.   ],
  42.   display: 'swap',
  43.   preload: true,
  44. });
复制代码
动态导入和代码分割

1. 组件动态导入
  1. // components/DynamicComponent.tsx
  2. import dynamic from 'next/dynamic';
  3. import { Suspense } from 'react';
  4. // 基础动态导入
  5. const DynamicHeader = dynamic(() => import('./Header'), {
  6.   loading: () => <p>Loading...</p>,
  7.   ssr: true, // 是否服务端渲染
  8. });
  9. // 带有自定义加载的动态导入
  10. const DynamicChart = dynamic(
  11.   () => import('./Chart').then(mod => mod.Chart),
  12.   {
  13.     loading: () => <ChartSkeleton />,
  14.     ssr: false, // 禁用服务端渲染
  15.   }
  16. );
  17. // 使用 Suspense 包裹动态组件
  18. export function DashboardPage() {
  19.   return (
  20.     <div>
  21.       <DynamicHeader />
  22.       <Suspense fallback={<ChartSkeleton />}>
  23.         <DynamicChart />
  24.       </Suspense>
  25.     </div>
  26.   );
  27. }
复制代码
2. 路由分组和懒加载
  1. // app/(marketing)/layout.tsx
  2. import { Suspense } from 'react';
  3. // 营销相关页面的布局
  4. export default function MarketingLayout({
  5.   children
  6. }: {
  7.   children: React.ReactNode;
  8. }) {
  9.   return (
  10.     <div className="marketing-layout">
  11.       <Suspense fallback={<NavSkeleton />}>
  12.         <Navigation />
  13.       </Suspense>
  14.       {children}
  15.     </div>
  16.   );
  17. }
  18. // app/(dashboard)/layout.tsx
  19. // 仪表板相关页面的布局
  20. export default function DashboardLayout({
  21.   children
  22. }: {
  23.   children: React.ReactNode;
  24. }) {
  25.   return (
  26.     <div className="dashboard-layout">
  27.       <Suspense fallback={<SidebarSkeleton />}>
  28.         <Sidebar />
  29.       </Suspense>
  30.       <main>{children}</main>
  31.     </div>
  32.   );
  33. }
复制代码
缓存战略优化

1. 数据缓存
  1. // lib/cache.ts
  2. import { cache } from 'react';
  3. import { Redis } from '@upstash/redis';
  4. const redis = new Redis({
  5.   url: process.env.REDIS_URL,
  6.   token: process.env.REDIS_TOKEN,
  7. });
  8. // 使用 React 缓存
  9. export const getCachedData = cache(async (key: string) => {
  10.   // 首先尝试从 Redis 获取
  11.   const cached = await redis.get(key);
  12.   if (cached) return JSON.parse(cached);
  13.   // 如果没有缓存,则获取新数据
  14.   const data = await fetchData(key);
  15.   // 存入 Redis
  16.   await redis.set(key, JSON.stringify(data), {
  17.     ex: 3600 // 1小时过期
  18.   });
  19.   return data;
  20. });
  21. // 使用示例
  22. async function ProductPage({ id }: { id: string }) {
  23.   const product = await getCachedData(`product:${id}`);
  24.   return <ProductDetails product={product} />;
  25. }
复制代码
2. 静态天生优化
  1. // app/products/[id]/page.tsx
  2. import { generateMetadata } from 'next';
  3. // 生成静态路由
  4. export async function generateStaticParams() {
  5.   const products = await getTopProducts();
  6.   return products.map((product) => ({
  7.     id: product.id,
  8.   }));
  9. }
  10. // 静态元数据
  11. export async function generateMetadata({ params }: {
  12.   params: { id: string }
  13. }): Promise<Metadata> {
  14.   const product = await getProduct(params.id);
  15.   return {
  16.     title: product.name,
  17.     description: product.description,
  18.     openGraph: {
  19.       images: [product.image],
  20.     },
  21.   };
  22. }
  23. // 页面组件
  24. export default async function ProductPage({
  25.   params
  26. }: {
  27.   params: { id: string }
  28. }) {
  29.   const product = await getProduct(params.id);
  30.   return (
  31.     <div>
  32.       <h1>{product.name}</h1>
  33.       <ProductDetails product={product} />
  34.     </div>
  35.   );
  36. }
复制代码
首屏加载优化

1. 流式渲染
  1. // app/page.tsx
  2. import { Suspense } from 'react';
  3. import { headers } from 'next/headers';
  4. async function SlowComponent() {
  5.   const headersList = headers();
  6.   const userAgent = headersList.get('user-agent');
  7.   // 模拟慢速数据加载
  8.   await new Promise(resolve => setTimeout(resolve, 2000));
  9.   return (
  10.     <div>
  11.       <p>User Agent: {userAgent}</p>
  12.     </div>
  13.   );
  14. }
  15. export default function HomePage() {
  16.   return (
  17.     <div>
  18.       <h1>即时加载的内容</h1>
  19.       <Suspense fallback={<LoadingSkeleton />}>
  20.         <SlowComponent />
  21.       </Suspense>
  22.       <Suspense fallback={<CardsSkeleton />}>
  23.         <PopularProducts />
  24.       </Suspense>
  25.       <Suspense fallback={<FeedSkeleton />}>
  26.         <RecentActivity />
  27.       </Suspense>
  28.     </div>
  29.   );
  30. }
复制代码
2. 预加载数据
  1. // lib/prefetch.ts
  2. export async function prefetchData() {
  3.   // 预加载关键数据
  4.   const promises = [
  5.     prefetchNavigation(),
  6.     prefetchUserData(),
  7.     prefetchPopularProducts(),
  8.   ];
  9.   await Promise.all(promises);
  10. }
  11. // app/layout.tsx
  12. export default async function RootLayout({
  13.   children
  14. }: {
  15.   children: React.ReactNode
  16. }) {
  17.   // 在布局组件中预加载数据
  18.   await prefetchData();
  19.   return (
  20.     <html>
  21.       <body>{children}</body>
  22.     </html>
  23.   );
  24. }
复制代码
Core Web Vitals 优化

1. 性能监控监控
  1. // lib/analytics.ts
  2. export function reportWebVitals({
  3.   id,
  4.   name,
  5.   label,
  6.   value,
  7. }: {
  8.   id: string;
  9.   name: string;
  10.   label: string;
  11.   value: number;
  12. }) {
  13.   // 发送性能指标到分析服务
  14.   fetch('/api/analytics', {
  15.     method: 'POST',
  16.     body: JSON.stringify({
  17.       id,
  18.       name,
  19.       label,
  20.       value,
  21.       // 添加其他上下文信息
  22.       page: window.location.pathname,
  23.       timestamp: Date.now(),
  24.     }),
  25.   });
  26. }
  27. // next.config.js
  28. /** @type {import('next').NextConfig} */
  29. const nextConfig = {
  30.   experimental: {
  31.     instrumentationHook: true,
  32.   },
  33. };
  34. // instrumentation.ts
  35. export function register() {
  36.   if (process.env.NEXT_RUNTIME === 'nodejs') {
  37.     // 服务端监控监控
  38.     require('./monitoring/server').setup();
  39.   }
  40. }
复制代码
2. 性能优化实践
  1. // components/OptimizedList.tsx
  2. import { useVirtualizer } from '@tanstack/react-virtual';
  3. import { useIntersectionObserver } from '@/hooks/useIntersectionObserver';
  4. // 虚拟列表优化
  5. export function OptimizedList({ items }: { items: any[] }) {
  6.   const parentRef = useRef<HTMLDivElement>(null);
  7.   const virtualizer = useVirtualizer({
  8.     count: items.length,
  9.     getScrollElement: () => parentRef.current,
  10.     estimateSize: () => 50,
  11.   });
  12.   return (
  13.     <div ref={parentRef} className="h-[500px] overflow-auto">
  14.       <div
  15.         style={{
  16.           height: `${virtualizer.getTotalSize()}px`,
  17.           position: 'relative',
  18.         }}
  19.       >
  20.         {virtualizer.getVirtualItems().map((virtualItem) => (
  21.           <div
  22.             key={virtualItem.key}
  23.             style={{
  24.               position: 'absolute',
  25.               top: 0,
  26.               left: 0,
  27.               width: '100%',
  28.               height: `${virtualItem.size}px`,
  29.               transform: `translateY(${virtualItem.start}px)`,
  30.             }}
  31.           >
  32.             <ListItem item={items[virtualItem.index]} />
  33.           </div>
  34.         ))}
  35.       </div>
  36.     </div>
  37.   );
  38. }
  39. // 图片懒加载优化
  40. export function LazyImage({ src, alt }: { src: string; alt: string }) {
  41.   const imgRef = useRef<HTMLImageElement>(null);
  42.   const { isIntersecting } = useIntersectionObserver(imgRef);
  43.   return (
  44.     <img
  45.       ref={imgRef}
  46.       src={isIntersecting ? src : ''}
  47.       alt={alt}
  48.       loading="lazy"
  49.       decoding="async"
  50.     />
  51.   );
  52. }
复制代码
3. 构建优化
  1. // next.config.js
  2. const withBundleAnalyzer = require('@next/bundle-analyzer')({
  3.   enabled: process.env.ANALYZE === 'true',
  4. });
  5. /** @type {import('next').NextConfig} */
  6. const nextConfig = {
  7.   // 优化构建配置
  8.   poweredByHeader: false,
  9.   compress: true,
  10.   productionBrowserSourceMaps: false,
  11.   // 优化图片配置
  12.   images: {
  13.     minimumCacheTTL: 60,
  14.     deviceSizes: [640, 750, 828, 1080, 1200, 1920],
  15.     imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  16.   },
  17.   // 实验性功能
  18.   experimental: {
  19.     optimizeCss: true, // 启用 CSS 优化
  20.     scrollRestoration: true, // 启用滚动位置恢复
  21.     serverActions: true, // 启用服务端操作
  22.   },
  23.   // webpack 配置
  24.   webpack: (config, { dev, isServer }) => {
  25.     // 优化生产环境构建
  26.     if (!dev && !isServer) {
  27.       config.optimization.splitChunks = {
  28.         chunks: 'all',
  29.         minSize: 20000,
  30.         maxSize: 244000,
  31.         minChunks: 1,
  32.         maxAsyncRequests: 30,
  33.         maxInitialRequests: 30,
  34.         cacheGroups: {
  35.           defaultVendors: {
  36.             test: /[\\/]node_modules[\\/]/,
  37.             priority: -10,
  38.             reuseExistingChunk: true,
  39.           },
  40.           default: {
  41.             minChunks: 2,
  42.             priority: -20,
  43.             reuseExistingChunk: true,
  44.           },
  45.         },
  46.       };
  47.     }
  48.     return config;
  49.   },
  50. };
  51. module.exports = withBundleAnalyzer(nextConfig);
复制代码
写在末了

Next.js 14 提供了丰富的性能优化工具和特性。在现实应用中,须要注意以下几点:

  • 公道利用图片和字体优化
  • 实行有用的代码分割战略
  • 优化数据缓存和预加载
  • 监控监控和优化 Core Web Vitals
  • 连续举行构建优化
在下一篇文章中,我们将深入探究 Next.js 14 的摆设与运维战略。假如你有任何题目或发起,欢迎在批评区讨论!
假如以为这篇文章对你有资助,别忘了点个赞 👍
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表