Skip to main content
← ブログに戻る

Optimizing Web Performance: Core Web Vitals and Beyond

5 min read著者: pycabbage
GuidePerformance

Optimizing Web Performance: Core Web Vitals and Beyond

Website performance directly impacts user experience, conversion rates, and SEO rankings. Let's explore practical techniques to optimize your web applications for peak performance.

Understanding Core Web Vitals

Google's Core Web Vitals measure real-world user experience:

Largest Contentful Paint (LCP)

Measures loading performance. LCP should occur within 2.5 seconds.

// Monitor LCP
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });

First Input Delay (FID) / Interaction to Next Paint (INP)

Measures interactivity. FID should be less than 100 milliseconds.

// Monitor FID
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('FID:', entry.processingStart - entry.startTime);
  }
}).observe({ entryTypes: ['first-input'] });

Cumulative Layout Shift (CLS)

Measures visual stability. CLS should be less than 0.1.

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

Image Optimization

Images often account for most of a page's weight:

Next.js Image Component

import Image from 'next/image';

function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority // Load immediately for LCP
      placeholder="blur"
      blurDataURL={blurDataUrl}
    />
  );
}

Responsive Images

<picture>
  <source
    media="(max-width: 640px)"
    srcset="hero-mobile.webp"
    type="image/webp"
  />
  <source
    media="(max-width: 1024px)"
    srcset="hero-tablet.webp"
    type="image/webp"
  />
  <img
    src="hero-desktop.jpg"
    alt="Hero"
    loading="lazy"
    decoding="async"
  />
</picture>

Image Optimization Checklist

  • ✅ Use modern formats (WebP, AVIF)
  • ✅ Implement responsive images
  • ✅ Lazy load below-the-fold images
  • ✅ Add explicit width/height
  • ✅ Use CDN with image optimization

JavaScript Optimization

Reduce JavaScript impact on performance:

Code Splitting

// Dynamic imports
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <LoadingSpinner />,
  ssr: false
});

// Route-based splitting (automatic in Next.js)
// pages/about.js only loads when visiting /about

Tree Shaking

// Bad - imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);

// Good - imports only what's needed
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);

Bundle Analysis

# Next.js bundle analyzer
npm install @next/bundle-analyzer

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

module.exports = withBundleAnalyzer({});

CSS Optimization

Optimize CSS delivery and rendering:

Critical CSS

// Extract and inline critical CSS
import { getCssText } from '@stitches/react';

export default function Document() {
  return (
    <Html>
      <Head>
        <style
          id="stitches"
          dangerouslySetInnerHTML={{ __html: getCssText() }}
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

CSS Containment

/* Improve rendering performance */
.card {
  contain: layout style paint;
}

.sidebar {
  contain: strict; /* All containment types */
}

Loading Strategies

Implement smart loading patterns:

Lazy Loading Components

import { lazy, Suspense } from 'react';

const Comments = lazy(() => import('./Comments'));

function BlogPost() {
  return (
    <article>
      <h1>My Blog Post</h1>
      <p>Content...</p>
      
      <Suspense fallback={<div>Loading comments...</div>}>
        <Comments />
      </Suspense>
    </article>
  );
}

Intersection Observer

// Lazy load when element enters viewport
function useLazyLoad(ref, callback) {
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          callback();
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

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

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

Resource Hints

<!-- DNS prefetch for external domains -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />

<!-- Preconnect for critical resources -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

<!-- Preload critical resources -->
<link 
  rel="preload" 
  href="/fonts/inter.woff2" 
  as="font" 
  type="font/woff2" 
  crossorigin 
/>

Font Optimization

Optimize web fonts for better performance:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately */
  unicode-range: U+0000-00FF; /* Latin characters only */
}

/* Preload critical characters */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-subset.woff2') format('woff2');
  font-display: block; /* Block for critical text */
  unicode-range: U+0041-005A; /* A-Z only */
}

Performance Monitoring

Implement monitoring to track improvements:

// Custom performance monitoring
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
  }

  measure(name, fn) {
    const start = performance.now();
    const result = fn();
    const duration = performance.now() - start;
    
    this.metrics[name] = duration;
    
    // Send to analytics
    if (window.gtag) {
      gtag('event', 'timing_complete', {
        name,
        value: Math.round(duration)
      });
    }
    
    return result;
  }

  reportWebVitals() {
    if ('web-vitals' in window) {
      const { getCLS, getFID, getLCP } = window['web-vitals'];
      
      getCLS(this.sendToAnalytics);
      getFID(this.sendToAnalytics);
      getLCP(this.sendToAnalytics);
    }
  }

  sendToAnalytics({ name, value }) {
    // Send to your analytics service
    console.log(`${name}: ${value}`);
  }
}

Server-Side Optimizations

Caching Headers

// Next.js API route with caching
export default function handler(req, res) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  );
  
  res.json({ data: 'cached response' });
}

Compression

// Enable compression in Next.js
// next.config.js
module.exports = {
  compress: true,
  
  // Custom headers
  async headers() {
    return [
      {
        source: '/:all*(js|css|jpg|png)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ];
  },
};

Performance Checklist

  • [ ] Images: WebP/AVIF, lazy loading, proper sizing
  • [ ] JavaScript: Code splitting, tree shaking, minimal bundles
  • [ ] CSS: Critical CSS, unused CSS removal
  • [ ] Fonts: Subset, preload, font-display
  • [ ] Caching: Proper headers, CDN usage
  • [ ] Monitoring: RUM, synthetic monitoring
  • [ ] SEO: Meta tags, structured data, sitemap

Tools for Performance Testing

  1. Lighthouse: Built into Chrome DevTools
  2. WebPageTest: Detailed performance analysis
  3. PageSpeed Insights: Real-world data from Chrome users
  4. Chrome DevTools: Performance profiling
  5. Bundle Analyzer: Visualize JavaScript bundles

Conclusion

Web performance optimization is an ongoing process. Focus on Core Web Vitals as a baseline, but don't stop there. Implement comprehensive monitoring, iterate based on real user data, and always test performance impacts before deploying changes.

Remember: every millisecond counts. A faster website means happier users, better conversions, and improved search rankings. Start optimizing today!