performance-optimization

作成者: sanity-io

アプリケーションのパフォーマンスを最適化します。パフォーマンス要件がある場合、パフォーマンスの低下が疑われる場合、またはCore Web Vitalsや読み込み時間が…

npx skills add https://github.com/sanity-io/sanity --skill performance-optimization

Performance Optimization

Overview

Measure before optimizing. Performance work without measurement is guessing — and guessing leads to premature optimization that adds complexity without improving what matters. Profile first, identify the actual bottleneck, fix it, measure again. Optimize only what measurements prove matters.

When to Use

  • Performance requirements exist in the spec (load time budgets, response time SLAs)
  • Users or monitoring report slow behavior
  • Core Web Vitals scores are below thresholds
  • You suspect a change introduced a regression
  • Building features that handle large datasets or high traffic

When NOT to use: Don't optimize before you have evidence of a problem. Premature optimization adds complexity that costs more than the performance it gains.

Core Web Vitals Targets

MetricGoodNeeds ImprovementPoor
LCP (Largest Contentful Paint)≤ 2.5s≤ 4.0s> 4.0s
INP (Interaction to Next Paint)≤ 200ms≤ 500ms> 500ms
CLS (Cumulative Layout Shift)≤ 0.1≤ 0.25> 0.25

The Optimization Workflow

1. MEASURE  → Establish baseline with real data
2. IDENTIFY → Find the actual bottleneck (not assumed)
3. FIX      → Address the specific bottleneck
4. VERIFY   → Measure again, confirm improvement
5. GUARD    → Add monitoring or tests to prevent regression

Step 1: Measure

Two complementary approaches — use both:

  • Synthetic (Lighthouse, DevTools Performance tab): Controlled conditions, reproducible. Best for CI regression detection and isolating specific issues.
  • RUM (web-vitals library, CrUX): Real user data in real conditions. Required to validate that a fix actually improved user experience.

Frontend:

# Synthetic: Lighthouse in Chrome DevTools (or CI)
# Chrome DevTools → Performance tab → Record
# Chrome DevTools MCP → Performance trace

# RUM: Web Vitals library in code
import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

Backend:

# Response time logging
# Application Performance Monitoring (APM)
# Database query logging with timing

# Simple timing
console.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');

Where to Start Measuring

Use the symptom to decide what to measure first:

What is slow?
├── First page load
│   ├── Large bundle? --> Measure bundle size, check code splitting
│   ├── Slow server response? --> Measure TTFB in DevTools Network waterfall
│   │   ├── DNS long? --> Add dns-prefetch / preconnect for known origins
│   │   ├── TCP/TLS long? --> Enable HTTP/2, check edge deployment, keep-alive
│   │   └── Waiting (server) long? --> Profile backend, check queries and caching
│   └── Render-blocking resources? --> Check network waterfall for CSS/JS blocking
├── Interaction feels sluggish
│   ├── UI freezes on click? --> Profile main thread, look for long tasks (>50ms)
│   ├── Form input lag? --> Check re-renders, controlled component overhead
│   └── Animation jank? --> Check layout thrashing, forced reflows
├── Page after navigation
│   ├── Data loading? --> Measure API response times, check for waterfalls
│   └── Client rendering? --> Profile component render time, check for N+1 fetches
└── Backend / API
    ├── Single endpoint slow? --> Profile database queries, check indexes
    ├── All endpoints slow? --> Check connection pool, memory, CPU
    └── Intermittent slowness? --> Check for lock contention, GC pauses, external deps

Step 2: Identify the Bottleneck

Common bottlenecks by category:

Frontend:

SymptomLikely CauseInvestigation
Slow LCPLarge images, render-blocking resources, slow serverCheck network waterfall, image sizes
High CLSImages without dimensions, late-loading content, font shiftsCheck layout shift attribution
Poor INPHeavy JavaScript on main thread, large DOM updatesCheck long tasks in Performance trace
Slow initial loadLarge bundle, many network requestsCheck bundle size, code splitting

Backend:

SymptomLikely CauseInvestigation
Slow API responsesN+1 queries, missing indexes, unoptimized queriesCheck database query log
Memory growthLeaked references, unbounded caches, large payloadsHeap snapshot analysis
CPU spikesSynchronous heavy computation, regex backtrackingCPU profiling
High latencyMissing caching, redundant computation, network hopsTrace requests through the stack

Step 3: Fix Common Anti-Patterns

N+1 Queries (Backend)

// BAD: N+1 — one query per task for the owner
const tasks = await db.tasks.findMany()
for (const task of tasks) {
  task.owner = await db.users.findUnique({where: {id: task.ownerId}})
}

// GOOD: Single query with join/include
const tasks = await db.tasks.findMany({
  include: {owner: true},
})

Unbounded Data Fetching

// BAD: Fetching all records
const allTasks = await db.tasks.findMany()

// GOOD: Paginated with limits
const tasks = await db.tasks.findMany({
  take: 20,
  skip: (page - 1) * 20,
  orderBy: {createdAt: 'desc'},
})

Missing Image Optimization (Frontend)

<!-- BAD: No dimensions, no format optimization -->
<img src="/hero.jpg" />

<!-- GOOD: Hero / LCP image — art direction + resolution switching, high priority -->
<!--
  Two techniques combined:
  - Art direction (media): different crop/composition per breakpoint
  - Resolution switching (srcset + sizes): right file size per screen density
-->
<picture>
  <!-- Mobile: portrait crop (8:10) -->
  <source
    media="(max-width: 767px)"
    srcset="/hero-mobile-400.avif 400w, /hero-mobile-800.avif 800w"
    sizes="100vw"
    width="800"
    height="1000"
    type="image/avif"
  />
  <source
    media="(max-width: 767px)"
    srcset="/hero-mobile-400.webp 400w, /hero-mobile-800.webp 800w"
    sizes="100vw"
    width="800"
    height="1000"
    type="image/webp"
  />
  <!-- Desktop: landscape crop (2:1) -->
  <source
    srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-1600.avif 1600w"
    sizes="(max-width: 1200px) 100vw, 1200px"
    width="1200"
    height="600"
    type="image/avif"
  />
  <source
    srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
    sizes="(max-width: 1200px) 100vw, 1200px"
    width="1200"
    height="600"
    type="image/webp"
  />
  <img
    src="/hero-desktop.jpg"
    width="1200"
    height="600"
    fetchpriority="high"
    alt="Hero image description"
  />
</picture>

<!-- GOOD: Below-the-fold image — lazy loaded + async decoding -->
<img
  src="/content.webp"
  width="800"
  height="400"
  loading="lazy"
  decoding="async"
  alt="Content image description"
/>

Unnecessary Re-renders (React)

// BAD: Creates new object on every render, causing children to re-render
function TaskList() {
  return <TaskFilters options={{sortBy: 'date', order: 'desc'}} />
}

// GOOD: Stable reference
const DEFAULT_OPTIONS = {sortBy: 'date', order: 'desc'} as const
function TaskList() {
  return <TaskFilters options={DEFAULT_OPTIONS} />
}

// Use React.memo for expensive components
const TaskItem = React.memo(function TaskItem({task}: Props) {
  return <div>{/* expensive render */}</div>
})

// Use useMemo for expensive computations
function TaskStats({tasks}: Props) {
  const stats = useMemo(() => calculateStats(tasks), [tasks])
  return (
    <div>
      {stats.completed} / {stats.total}
    </div>
  )
}

Large Bundle Size

// Modern bundlers (Vite, webpack 5+) handle named imports with tree-shaking automatically,
// provided the dependency ships ESM and is marked `sideEffects: false` in package.json.
// Profile before changing import styles — the real gains come from splitting and lazy loading.

// GOOD: Dynamic import for heavy, rarely-used features
const ChartLibrary = lazy(() => import('./ChartLibrary'));

// GOOD: Route-level code splitting wrapped in Suspense
const SettingsPage = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <SettingsPage />
    </Suspense>
  );
}

Missing Caching (Backend)

// Cache frequently-read, rarely-changed data
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
let cachedConfig: AppConfig | null = null
let cacheExpiry = 0

async function getAppConfig(): Promise<AppConfig> {
  if (cachedConfig && Date.now() < cacheExpiry) {
    return cachedConfig
  }
  cachedConfig = await db.config.findFirst()
  cacheExpiry = Date.now() + CACHE_TTL
  return cachedConfig
}

// HTTP caching headers for static assets
app.use(
  '/static',
  express.static('public', {
    maxAge: '1y', // Cache for 1 year
    immutable: true, // Never revalidate (use content hashing in filenames)
  }),
)

// Cache-Control for API responses
res.set('Cache-Control', 'public, max-age=300') // 5 minutes

Performance Budget

Set budgets and enforce them:

JavaScript bundle: < 200KB gzipped (initial load)
CSS: < 50KB gzipped
Images: < 200KB per image (above the fold)
Fonts: < 100KB total
API response time: < 200ms (p95)
Time to Interactive: < 3.5s on 4G
Lighthouse Performance score: ≥ 90

Enforce in CI:

# Bundle size check
npx bundlesize --config bundlesize.config.json

# Lighthouse CI
npx lhci autorun

See Also

For detailed performance checklists, optimization commands, and anti-pattern reference, see references/performance-checklist.md.

Common Rationalizations

RationalizationReality
"We'll optimize later"Performance debt compounds. Fix obvious anti-patterns now, defer micro-optimizations.
"It's fast on my machine"Your machine isn't the user's. Profile on representative hardware and networks.
"This optimization is obvious"If you didn't measure, you don't know. Profile first.
"Users won't notice 100ms"Research shows 100ms delays impact conversion rates. Users notice more than you think.
"The framework handles performance"Frameworks prevent some issues but can't fix N+1 queries or oversized bundles.

Red Flags

  • Optimization without profiling data to justify it
  • N+1 query patterns in data fetching
  • List endpoints without pagination
  • Images without dimensions, lazy loading, or responsive sizes
  • Bundle size growing without review
  • No performance monitoring in production
  • React.memo and useMemo everywhere (overusing is as bad as underusing)

Verification

After any performance-related change:

  • Before and after measurements exist (specific numbers)
  • The specific bottleneck is identified and addressed
  • Core Web Vitals are within "Good" thresholds
  • Bundle size hasn't increased significantly
  • No N+1 queries in new data fetching code
  • Performance budget passes in CI (if configured)
  • Existing tests still pass (optimization didn't break behavior)

sanity-ioのその他のスキル

tdd
sanity-io
テスト駆動開発のレッド・グリーン・リファクタリングループ。ユーザーがTDDを使って機能を構築したりバグを修正したい場合、「レッド・グリーン・リファクタリング」に言及した場合、または…
content-experimentation-best-practices
sanity-io
コンテンツ実験の設計、実行、分析に関する構造化されたガイダンスで、コンバージョンとエンゲージメントを向上させます。仮説フレームワーク、指標の選択、サンプルサイズの計算、A/Bテストや多変量実験における統計的有意性検定を網羅。p値、信頼区間、検出力分析、結果解釈のためのベイズ手法に関する詳細なリソースを提供。フィールドレベルでバリアントを管理し、外部と接続するためのCMS統合パターンを含みます。
content-modeling-best-practices
sanity-io
構造化コンテンツモデリングのガイダンス:スキーマ設計、再利用性、マルチチャネル配信に対応。コンテンツをページではなくデータとして扱う、単一情報源の維持、将来のチャネルに対応した設計、編集者ワークフローの最適化といった基本原則を網羅。参照と埋め込みオブジェクトの判断基準、関心の分離、コンテンツ再利用パターンを提供。フラット、階層的、ファセット型アプローチのタクソノミーと分類ガイダンスを提示。対象範囲:...
portable-text-conversion
sanity-io
HTMLおよびMarkdownコンテンツをSanity用のPortable Textブロックに変換します。レガシーCMSからのコンテンツ移行時や、HTMLやMarkdownをSanityにインポートする際に使用します。
portable-text-serialization
sanity-io
Portable TextをReact、Svelte、Vue、Astro、HTML、Markdown、プレーンテキストにレンダリングおよびシリアライズします。任意のフロントエンドでPortable Textレンダリングを実装する際に使用します…
sanity-best-practices
sanity-io
Sanity CMS開発におけるフレームワーク横断的な包括的なベストプラクティスと統合ガイド。Next.js、Nuxt、Astro、Remix、SvelteKit、Angularを含む10以上のフレームワーク統合を、フレームワーク固有のパターンとセットアップガイダンスとともにカバー。スキーマ設計、GROQクエリ最適化、ビジュアル編集、Portable Text、画像、TypeGen、ローカライゼーション、コンテンツ移行に関するトピックガイドを含む。タスクタイプに基づいて関連ガイドのみを読み込むためのクイックリファレンス構造を提供。
sanity-migration
sanity-io
他のCMSやコンテンツシステムからSanityへの移行を計画、実施、レビューします。AEM、Adobe Experience Manager、Contentful、Strapi、Webflow、WordPress、Payload、Drupal、Markdown/MDX/frontmatterファイル、WXR/XMLエクスポート、CMS API、データベースダンプ、静的HTMLからの移行やSanityへのリプラットフォーム時、または抽出、変換、Portable Text変換、アセット移行、リダイレクト、検証、カットオーバーワークフローの設計時に使用します。
data-analysisdatabasedevelopment
seo-aeo-best-practices
sanity-io
包括メタデータ、構造化データ、EEAT、AI対応を網羅した包括的なSEOおよびAEO最適化ガイダンス。Google、ChatGPT、Perplexity、および類似プラットフォームにおける従来の検索エンジン最適化(SEO)とAI回答エンジン最適化(AEO)の両方をカバー。メタデータ、Open Graphタグ、サイトマップ、robots.txt、hreflang、国際SEO設定などの技術的SEOの基本を提供。Article、FAQ、Breadcrumb、ProductスキーマのJSON-LD構造化データパターンを提供。実装...