mcpField
P
performance-optimization

performance-optimization

github.com/jmsktm开发1066 次安装41 周前更新
代码仓库

关于 performance-optimization

performance-optimization 是帮助开发者提升 Next.js 应用性能的技能,内容涵盖 Core Web Vitals、渲染、缓存、打包体积和数据库查询优化。

主要功能

  1. 01Core Web Vitals 优化建议
  2. 02图像与字体优化
  3. 03动态导入和代码拆分
  4. 04缓存与标签重新验证
  5. 05打包分析和 tree shaking
  6. 06数据库查询与渲染策略

适用场景

  1. 01排查页面加载缓慢问题
  2. 02减少 JavaScript 打包体积
  3. 03优化图像与字体加载
  4. 04调整数据获取和数据库查询

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jmsktm/claude-settings --skill performance-optimization

安装后在 Claude Code 中输入/performance-optimization确认已加载。

详细介绍

性能优化技能

概述

此技能可帮助你优化 Next.js 应用,以实现最佳性能。从图像优化到代码拆分,涵盖了取得出色 Core Web Vitals 评分所需的所有技巧。

Core Web Vitals

1. 最大内容绘制(LCP)

目标:< 2.5s

优化方法:

  • 使用 next/image 处理图像
  • 实施适当的缓存
  • 使用 CDN 提供静态资源
  • 优化服务器响应时间
  • 减少阻塞渲染的资源
2. 首次输入延迟(FID)/ 与下一次绘制的交互(INP)

目标:< 100ms / < 200ms

优化方法:

  • 尽量减少 JavaScript 执行
  • 对大型捆绑包进行代码拆分
  • 使用 Web Workers 处理繁重任务
  • 延迟加载非关键 JavaScript
  • 优化事件处理程序
3. 累积布局偏移(CLS)

目标:< 0.1

优化方法:

  • 设置图像尺寸
  • 为广告预留空间
  • 避免在现有内容上方插入内容
  • 使用 transform 代替布局属性

图像优化

Next.js 图像组件
import Image from 'next/image'

// ✅ Optimized
export function OptimizedImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority  // For above-fold images
      quality={85}  // Default: 75
      placeholder="blur"
      blurDataURL="data:image/..." // Or import for static
    />
  )
}

// For external images, configure domains
// next.config.js
module.exports = {
  images: {
    domains: ['example.com'],
    // Or use remotePatterns for more control
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
      },
    ],
  },
}
响应式图像
<Image
  src="/hero.jpg"
  alt="Hero"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  style={{ objectFit: 'cover' }}
  priority
/>
图像格式
// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

字体优化

使用 next/font
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
      <body className="font-sans">{children}</body>
    </html>
  )
}

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--font-inter)'],
        mono: ['var(--font-roboto-mono)'],
      },
    },
  },
}
本地字体
import localFont from 'next/font/local'

const customFont = localFont({
  src: './fonts/custom-font.woff2',
  display: 'swap',
  variable: '--font-custom',
})

代码拆分

动态导入
import dynamic from 'next/dynamic'

// Load component only when needed
const HeavyComponent = dynamic(() => import('@/components/heavy-component'), {
  loading: () => <p>Loading...</p>,
  ssr: false,  // Disable SSR for this component
})

export function Page() {
  return (
    <div>
      <HeavyComponent />
    </div>
  )
}
条件加载
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'

const Chart = dynamic(() => import('@/components/chart'), {
  ssr: false,
})

export function Dashboard() {
  const [showChart, setShowChart] = useState(false)

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && <Chart />}
    </div>
  )
}
命名导出
const ComponentA = dynamic(() =>
  import('@/components/bundle').then((mod) => mod.ComponentA)
)

React 优化

React.memo
import { memo } from 'react'

// Only re-renders if props change
const ExpensiveComponent = memo(function ExpensiveComponent({
  data,
}: {
  data: Data
}) {
  return <div>{/* Expensive rendering */}</div>
})

// Custom comparison
const MemoizedComponent = memo(
  Component,
  (prevProps, nextProps) => {
    return prevProps.id === nextProps.id
  }
)
useMemo
'use client'
import { useMemo } from 'react'

export function DataTable({ items }: { items: Item[] }) {
  // Only recalculate when items change
  const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.name.localeCompare(b.name))
  }, [items])

  return (
    <table>
      {sortedItems.map((item) => (
        <tr key={item.id}>
          <td>{item.name}</td>
        </tr>
      ))}
    </table>
  )
}
useCallback
'use client'
import { useCallback, useState } from 'react'

export function Parent() {
  const [count, setCount] = useState(0)

  // Stable function reference
  const handleClick = useCallback(() => {
    console.log('clicked')
  }, [])

  return <Child onClick={handleClick} />
}

缓存策略

API 路由缓存
// app/api/data/route.ts
export async function GET() {
  const data = await fetchData()

  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30',
    },
  })
}
数据获取缓存
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 },
})

// Never cache (always fresh)
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store',
})

// Cache forever
const data = await fetch('https://api.example.com/data', {
  cache: 'force-cache',
})
基于标签的重新验证
// Tag the fetch
const data = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] },
})

// Revalidate all 'posts' fetches
import { revalidateTag } from 'next/cache'

export async function POST() {
  // Mutate data
  await createPost()
  // Revalidate
  revalidateTag('posts')
}

捆绑包优化

分析捆绑包
# Add to package.json
{
  "scripts": {
    "analyze": "ANALYZE=true next build"
  }
}

# Install bundle analyzer
npm install @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // Your config
})
Tree Shaking
// ❌ Bad - Imports entire library
import _ from 'lodash'

// ✅ Good - Only imports what you need
import debounce from 'lodash/debounce'
移除未使用的依赖项
# Find unused dependencies
npx depcheck

# Remove them
npm uninstall unused-package

流式传输与 Suspense

流式传输组件
// app/dashboard/page.tsx
import { Suspense } from 'react'

async function SlowComponent() {
  const data = await slowFetch()
  return <div>{data}</div>
}

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<Loading />}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}
多个 Suspense 边界
export default function Page() {
  return (
    <div>
      <Suspense fallback={<HeaderSkeleton />}>
        <Header />
      </Suspense>

      <Suspense fallback={<ContentSkeleton />}>
        <Content />
      </Suspense>

      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
    </div>
  )
}

数据库查询优化

高效使用 Prisma
// ❌ Bad - N+1 query problem
const users = await prisma.user.findMany()
for (const user of users) {
  const posts = await prisma.post.findMany({ where: { userId: user.id } })
}

// ✅ Good - Single query with include
const users = await prisma.user.findMany({
  include: {
    posts: true,
  },
})

// ✅ Better - Select only what you need
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    posts: {
      select: {
        id: true,
        title: true,
      },
    },
  },
})
数据库索引
model Post {
  id        String   @id @default(cuid())
  title     String
  userId    String
  createdAt DateTime @default(now())

  // Add indexes for frequently queried fields
  @@index([userId])
  @@index([createdAt])
  @@index([userId, createdAt])
}

预渲染策略

静态站点生成(SSG)
// Fully static - generated at build time
export default async function Page() {
  const data = await fetch('https://api.example.com/data')
  return <div>{/* Render data */}</div>
}
增量静态再生成(ISR)
// Revalidate every 60 seconds
export const revalidate = 60

export default async function Page() {
  const data = await fetch('https://api.example.com/data')
  return <div>{/* Render data */}</div>
}
动态渲染
// Force dynamic rendering
export const dynamic = 'force-dynamic'

export default async function Page() {
  const data = await fetch('https://api.example.com/data', {
    cache: 'no-store',
  })
  return <div>{/* Render data */}</div>
}

懒加载

图像
<Image
  src="/image.jpg"
  alt="Image"
  width={500}
  height={300}
  loading="lazy"  // Default behavior
/>
滚动时加载组件
'use client'
import { useEffect, useState, useRef } from 'react'
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(() => import('@/components/heavy'))

export function LazySection() {
  const [isVisible, setIsVisible] = useState(false)
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true)
          observer.disconnect()
        }
      },
      { threshold: 0.1 }
    )

    if (ref.current) {
      observer.observe(ref.current)
    }

    return () => observer.disconnect()
  }, [])

  return (
    <div ref={ref}>
      {isVisible ? <HeavyComponent /> : <div>Loading...</div>}
    </div>
  )
}

性能监控

衡量性能
'use client'
import { useEffect } from 'react'

export function PerformanceMonitor() {
  useEffect(() => {
    // Measure LCP
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime)
    }).observe({ type: 'largest-contentful-paint', buffered: true })

    // Measure FID
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach((entry) => {
        console.log('FID:', entry.processingStart - entry.startTime)
      })
    }).observe({ type: 'first-input', buffered: true })

    // Measure CLS
    let clsScore = 0
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          clsScore += entry.value
        }
      }
      console.log('CLS:', clsScore)
    }).observe({ type: 'layout-shift', buffered: true })
  }, [])

  return null
}

最佳实践检查清单

  • 所有图像均使用 next/image
  • 使用 next/font 优化字体
  • 对大型组件实施代码拆分
  • 对开销较大的组件使用 React.memo
  • 实施适当的缓存策略
  • 设置 Suspense 边界
  • 优化数据库查询
  • 为半静态内容启用 ISR
  • 懒加载首屏以下的内容
  • 监控 Core Web Vitals
  • 尽量减小 JavaScript 捆绑包
  • 使用 CDN 提供静态资源
  • 实施适当的加载状态
  • 在慢速 3G 网络连接下进行测试

何时使用此技能

在以下情况调用此技能:

  • 优化页面加载时间
  • 提高 Core Web Vitals 评分
  • 减小捆绑包体积
  • 调试性能问题
  • 设置缓存策略
  • 实施懒加载
  • 优化图像或字体
  • 提升数据库查询性能

评论

支持 Markdown