完整的 Next.js 性能与 SEO 优化指南

发布: (2025年12月16日 GMT+8 03:27)
10 min read
原文: Dev.to

Source: Dev.to

内置组件优化

Next.js 提供了经过优化的内置组件,自动遵循性能最佳实践,省去手动优化的步骤,降低常见的陷阱。

next/image

Image 组件是 Next.js 最强大的性能工具之一。它提供:

  • 自动懒加载
  • 根据设备自动调整图片尺寸
  • 格式优化(WebP、AVIF)
  • 防止累计布局偏移(CLS)
  • 减少带宽消耗

基础实现

import Image from 'next/image';

export default function ProductCard() {
  return (
    
      
    
  );
}

高级配置(next.config.js

module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    domains: ['example.com', 'cdn.example.com'], // 允许的外部域名
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
        pathname: '/images/**',
      },
    ],
    formats: ['image/webp', 'image/avif'],
    minimumCacheTTL: 60,
    dangerouslyAllowSVG: true,
    contentSecurityPolicy:
      "default-src 'self'; script-src 'none'; sandbox;",
  },
};

注意要点

  • 对首屏图片使用 priority={true}
  • 必须指定 widthheight 以防止布局偏移。
  • 对响应式容器使用 fill 属性。
  • 外部图片需要在域名白名单中。
  • WebP 格式比 JPEG 小约 30 %。

实现客户端导航,无需完整页面刷新。

import Link from 'next/link';

export default function Navigation() {
  return (
    
      About
      Blog
    
  );
}

注意要点

  • 对可能不会被访问的链接设置 prefetch={false}

脚本加载策略

优化第三方脚本加载可以避免阻塞主线程,提升页面加载性能。Script 组件提供了对脚本何时以及如何加载的细粒度控制。

关键脚本 – beforeInteractive

在任何 Next.js 代码和页面 hydration 之前加载。用于必须在用户交互前运行的脚本。

import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    
      
        
        {children}
      
    
  );
}

分析与非关键脚本 – afterInteractive

在页面变为可交互后加载。适合分析、广告等非必需脚本。

import Script from 'next/script';

export default function Analytics() {
  return (
     console.log('Analytics loaded')}
    />
  );
}

懒加载脚本 – lazyOnload

在浏览器空闲时间加载。非常适合聊天小部件、社交媒体按钮或其他非关键脚本。

import Script from 'next/script';

export default function ChatWidget() {
  return (
     console.log('Chat widget ready')}
    />
  );
}

Worker 策略 – worker

在 Web Worker 中运行脚本,以获得更好性能。

import Script from 'next/script';

export default function Page() {
  return (
    
  );
}

注意要点

  • 仅对真正关键的脚本使用 beforeInteractive
  • 大多数分析应使用 afterInteractive
  • 社交媒体小部件和聊天应使用 lazyOnload
  • 优先使用 onLoad / onReady 回调,而不是内联脚本。

依赖管理

移除未使用的依赖可以减小 bundle 大小、提升构建速度,并降低安全风险。

识别未使用的包

# 全局安装 depcheck
npm install -g depcheck   # 或: yarn global add depcheck

# 在项目根目录运行
depcheck

# 带选项
depcheck --ignores="eslint-*,@types/*"

# 指定目录
depcheck ./src --ignore-dirs=build,dist

示例输出

Unused dependencies
* lodash
* moment
* axios

Missing dependencies
* react-icons

Unused devDependencies
* @testing-library/jest-dom

自动化清理脚本(package.json)

{
  "scripts": {
    "deps:check": "depcheck",
    "deps:clean": "depcheck --json | jq -r '.dependencies[]' | xargs npm uninstall"
  }
}

注意要点

  • 定期(如每月或大版本发布前)运行 depcheck
  • 在移除之前确认只在配置文件中使用的包。
  • 类型声明包(@types/*)可能不会出现在搜索结果中,如有需要请保留。
  • 移除依赖后务必彻底测试。
  • devDependenciesdependencies 分开管理。
  • 记录为何保留看似未使用的包。

缓存与增量静态再生成(ISR)

智能缓存策略可以更快地提供内容,降低服务器负载,并保持数据新鲜。ISR 允许在构建后更新静态页面。

基础 ISR 实现

// app/blog/[slug]/page.js
export const revalidate = 3600; // 每小时重新验证

export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map(post => ({ slug: post.slug }));
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return (
    
      
## {post.title}

      
{post.content}

      {post.publishedAt}
    
  );
}

按需重新验证

// app/api/revalidate/route.js
import { revalidatePath } from 'next/cache';
import { NextResponse } from 'next/server';

export async function POST(request) {
  const secret = request.nextUrl.searchParams.get('secret');
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
  }

  const path = request.nextUrl.searchParams.get('path');
  if (path) {
    revalidatePath(path);
    return NextResponse.json({ revalidated: true, now: Date.now() });
  }

  return NextResponse.json({ message: 'Missing path' }, { status: 400 });
}

基于标签的重新验证

// Fetch with cache tags
export default async function ProductList() {
  const products = await fetch('https://api.example.com/products', {
    next: { tags: ['products'], revalidate: 3600 }
  });
  return {/* Render products */};
}

// app/api/revalidate-products/route.js
import { revalidateTag } from 'next/cache';
import { NextResponse } from 'next/server';

export async function POST() {
  revalidateTag('products');
  return NextResponse.json({ revalidated: true });
}

Cache‑Control 响应头

// app/api/data/route.js
export async function GET() {
  const data = await fetchData();
  return NextResponse.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
    }
  });
}

注意要点

  • ISR 适用于定期更新的内容(博客、商品列表)。
  • 对频繁变化的数据使用更短的重新验证间隔(例如 60 秒)。
  • 将 ISR 与基于标签的重新验证结合使用,可实现细粒度缓存失效。

字体优化

使用内置的 next/font API 时,Next.js 会自动优化字体。

import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  weight: ['400', '700'],
  variable: '--font-inter',
});

export default function RootLayout({ children }) {
  return (
    
      {children}
    
  );
}

技巧

  • 只加载所需的字符子集。
  • 优先使用可变字体,以减少请求次数。
  • 避免在 CSS 中使用 @import,改用 next/font API。

懒加载与代码拆分

动态导入

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => 
Loading…
,
  ssr: false, // 仅在客户端加载
});
  • 将大型库(如图表、地图)拆分为独立块。
  • 对依赖浏览器 API 的组件使用 ssr: false

列表虚拟化

对于长列表,使用 react-windowreact-virtualized 等库,仅渲染可见项。

import { FixedSizeList as List } from 'react-window';

export default function VirtualizedList({ items }) {
  return (
    
      {({ index, style }) => (
        {items[index].title}
      )}
    
  );
}

数据获取优化

客户端使用 SWR

import useSWR from 'swr';

const fetcher = url => fetch(url).then(res => res.json());

export default function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: false,
    dedupingInterval: 60000,
  });

  if (isLoading) return 
Loading…
;
  if (error) return 
Error loading profile.
;

  return {data.name};
}
  • 对不需要频繁更新的数据将 revalidateOnFocus 设为 false
  • 调整 dedupingInterval 以控制请求去重频率。

JavaScript 包体优化

  • Tree‑shaking:只导入需要的函数(import { foo } from 'lodash' 而不是 import _ from 'lodash')。
  • 分析包体:使用 next build && next analyzewebpack-bundle-analyzer 等工具。
  • 压缩资源:在 CDN 或 Vercel Edge 上启用 gzip/Brotli。

CSS 优化

  • 使用 CSS 模块或 styled‑components 对样式进行作用域化,避免全局膨胀。
  • 使用 purgecss(在 Next.js 中通过 next-purgecss 集成)移除未使用的 CSS。
  • 对响应式设计优先使用 @media 查询,而不是拆分多个 CSS 文件。

API 路由缓存

在 Edge 端缓存耗时的 API 响应。

// app/api/products/route.js
import { NextResponse } from 'next/server';
import { fetchProducts } from '@/lib/db';

export async function GET() {
  const products = await fetchProducts();
  return NextResponse.json(products, {
    headers: {
      'Cache-Control': 'public, s-maxage=120, stale-while-revalidate=300',
    },
  });
}

Edge Runtime

将计算密集型逻辑部署到 Edge Runtime,以获得低延迟。

// app/api/geo/route.js
export const runtime = 'edge';

export async function GET(request) {
  const ip = request.headers.get('x-forwarded-for') ?? 'unknown';
  // 快速进行地理位置查询
  return new Response(`Your IP: ${ip}`);
}

核心网络指标(Core Web Vitals)

  • Largest Contentful Paint (LCP):通过优化首屏内容保持 LCP 较低。
export default function BlogPost({ post }) {
  return (
    
      {post.title} – My Blog
      
      {/* Post content */}
    
  );
}

安全优化

  • 在 API 路由或中间件中设置 Content‑Security‑PolicyX‑Content‑Type‑OptionsReferrer‑Policy 等响应头。
  • 使用 next-safe 或类似库对用户生成的 HTML 进行消毒。
  • 保持依赖最新;定期运行 npm audit
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; img-src https: data:; script-src 'self' 'unsafe-inline';"
  );
  return response;
}
Back to Blog

相关文章

阅读更多 »