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

在现代 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 提供了丰富的性能优化工具和特性。在实际应用中,需要注意以下几点:

  1. 合理使用图片和字体优化
  2. 实施有效的代码分割策略
  3. 优化数据缓存和预加载
  4. 监控和优化 Core Web Vitals
  5. 持续进行构建优化

在下一篇文章中,我们将深入探讨 Next.js 14 的部署与运维策略。如果你有任何问题或建议,欢迎在评论区讨论!

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

相关推荐
六卿2 分钟前
react防止页面崩溃
前端·react.js·前端框架
z千鑫29 分钟前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
小白学前端6662 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
outstanding木槿3 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
等一场春雨4 小时前
springboot 3 websocket react 系统提示,选手实时数据更新监控
spring boot·websocket·react.js
风无雨4 小时前
react杂乱笔记(一)
前端·笔记·react.js
北海天空4 小时前
reactHooks到底钩到了什么?
前端·react.js
飞翔的渴望4 小时前
react18与react17有哪些区别
前端·javascript·react.js
m0_748238785 小时前
3D架构图软件 iCraft Editor 正式发布 @icraftplayer-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
前端·react.js·前端框架
阿卡基YUAN5 小时前
react useCallback
前端·javascript·react.js