Core Web Vitals: jak zdobyć 90+ w PageSpeed na Next.js
Praktyczny przewodnik optymalizacji Core Web Vitals w Next.js. LCP, FID, CLS - jak osiągnąć wynik 90+ w Google PageSpeed Insights.
Core Web Vitals to kluczowe metryki wpływające na pozycjonowanie w Google. W tym przewodniku dowiesz się, jak zoptymalizować Next.js i osiągnąć wynik powyżej 90 punktów w PageSpeed Insights.
Co to są Core Web Vitals?
Google wprowadził Core Web Vitals jako ranking factor w 2021 roku. To trzy kluczowe metryki, które mierzą różne aspekty user experience. Largest Contentful Paint mierzy czas ładowania największego elementu na stronie i powinien wynosić mniej niż 2.5 sekundy. First Input Delay (zastępowany przez Interaction to Next Paint) mierzy responsywność interakcji i powinien być poniżej 100ms dla FID lub 200ms dla INP. Cumulative Layout Shift mierzy stabilność układu wizualnego i powinien być poniżej 0.1. Każda z tych metryk ma znaczący wpływ na ogólny wynik PageSpeed.
Optymalizacja LCP w Next.js
1. Optymalizacja obrazów z next/image
Komponent next/image automatycznie optymalizuje obrazy, ale wymaga odpowiedniej konfiguracji:
import Image from 'next/image' // ✅ Poprawnie - z priority dla hero image <Image src="/hero-image.jpg" alt="Hero image" width={1200} height={600} priority={true} placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..." /> // ❌ Źle - bez optymalizacji <img src="/hero-image.jpg" alt="Hero" />
2. Preload krytycznych zasobów
Preloadowanie krytycznych zasobów przyspiesza ładowanie strony:
// pages/_document.js import { Head, Html, Main, NextScript } from "next/document"; export default function Document() { return ( <Html> <Head> <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossOrigin="" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="dns-prefetch" href="//analytics.google.com" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); }
3. Optymalizacja fontów
Prawidłowa konfiguracja fontów wpływa na szybkość renderowania:
/* ✅ Poprawnie - system fonts fallback */ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; /* next.config.js - optymalizacja Google Fonts */ module.exports = { optimizeFonts: true, experimental: { fontLoaders: [ { loader: '@next/font/google', options: { subsets: ['latin'] } }, ], }, }
Poprawa FID/INP - responsywność
1. Code splitting i lazy loading
Dzielenie kodu na mniejsze części i ładowanie ich na żądanie:
import dynamic from "next/dynamic"; // ✅ Lazy loading komponentów const HeavyComponent = dynamic(() => import("./HeavyComponent"), { loading: () => <p>Ładowanie...</p>, ssr: false, }); // ✅ Conditional loading const AdminPanel = dynamic(() => import("./AdminPanel"), { ssr: false, }); export default function Page({ user }) { return ( <div> <h1>Strona główna</h1> {user?.isAdmin && <AdminPanel />} <HeavyComponent /> </div> ); }
2. Optymalizacja JavaScript
Optymalizacja event handlerów i redukcja obciążenia głównego wątku:
// ✅ Debounce dla input handlers import { useCallback, useMemo } from "react"; import { debounce } from "lodash"; const SearchInput = () => { const debouncedSearch = useMemo( () => debounce(query => { // API call searchAPI(query); }, 300), [], ); const handleChange = useCallback( e => { debouncedSearch(e.target.value); }, [debouncedSearch], ); return <input onChange={handleChange} />; };
3. Web Workers dla ciężkich obliczeń
Przeniesienie ciężkich obliczeń do Web Workers:
// Component import { useEffect, useState } from "react"; // worker.js self.onmessage = function (e) { const { data } = e.data; // Ciężkie obliczenia const result = processLargeDataset(data); self.postMessage(result); }; const HeavyCalculation = ({ data }) => { const [result, setResult] = useState(null); useEffect(() => { const worker = new Worker("/worker.js"); worker.postMessage({ data }); worker.onmessage = e => setResult(e.data); return () => worker.terminate(); }, [data]); return <div>{result}</div>; };
Eliminacja CLS - stabilność układu
1. Rezerwacja miejsca dla obrazów
Zapobieganie przesuwaniu się layoutu podczas ładowania obrazów:
// ✅ Z określonymi wymiarami <Image src="/product.jpg" width={400} height={300} alt="Product" style={{ width: '100%', height: 'auto', }} /> // ✅ Z aspect-ratio w CSS <div style={{ aspectRatio: '16/9' }}> <Image src="/banner.jpg" fill alt="Banner" style={{ objectFit: 'cover' }} /> </div>
2. Placeholder dla dynamicznej treści
Używanie skeleton loaders dla dynamicznie ładowanej treści:
const ProductCard = ({ productId }) => { const { data: product, isLoading } = useProduct(productId); if (isLoading) { return ( <div className="h-64 w-full animate-pulse rounded-lg bg-gray-200"> <div className="space-y-3 p-4"> <div className="h-4 w-3/4 rounded bg-gray-300"></div> <div className="h-4 w-1/2 rounded bg-gray-300"></div> </div> </div> ); } return ( <div className="h-64 w-full rounded-lg border"> <Image src={product.image} alt={product.name} /> <h3>{product.name}</h3> <p>{product.price}</p> </div> ); };
3. CSS Container Queries
Stabilne layouty z wykorzystaniem Container Queries:
/* Stabilne layouty z container queries */ .card-container { container-type: inline-size; } @container (min-width: 300px) { .card { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem; } }
Konfiguracja Next.js dla wydajności
next.config.js - kompletna konfiguracja
Pełna konfiguracja Next.js zoptymalizowana pod kątem wydajności:
/** @type {import('next').NextConfig} */ const nextConfig = { // Kompresja compress: true, // Optymalizacja obrazów images: { formats: ["image/avif", "image/webp"], minimumCacheTTL: 60 * 60 * 24 * 30, // 30 dni deviceSizes: [640, 750, 828, 1080, 1200, 1920], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, // Experimental features experimental: { optimizeCss: true, scrollRestoration: true, }, // Webpack optymalizacje webpack: (config, { dev, isServer }) => { if (!dev && !isServer) { // Bundle analyzer w produkcji config.optimization.splitChunks.chunks = "all"; } return config; }, // Headers dla cachowania async headers() { return [ { source: "/images/:path*", headers: [ { key: "Cache-Control", value: "public, max-age=31536000, immutable", }, ], }, ]; }, }; module.exports = nextConfig;
Monitoring i pomiary
1. Real User Monitoring (RUM)
Implementacja monitoringu Core Web Vitals w aplikacji:
// _app.js import { getCLS, getFCP, getFID, getLCP, getTTFB } from "web-vitals"; function sendToAnalytics(metric) { // Wyślij do Google Analytics 4 gtag("event", metric.name, { event_category: "Web Vitals", event_label: metric.id, value: Math.round(metric.name === "CLS" ? metric.value * 1000 : metric.value), non_interaction: true, }); } export function reportWebVitals(metric) { sendToAnalytics(metric); } export default function MyApp({ Component, pageProps }) { return <Component {...pageProps} />; }
2. Performance API
Wykorzystanie Performance API do monitorowania niestandardowych metryk:
// Monitoring custom metrics const measurePageLoad = () => { if (typeof window !== "undefined") { window.addEventListener("load", () => { const navigation = performance.getEntriesByType("navigation")[0]; const loadTime = navigation.loadEventEnd - navigation.fetchStart; console.log(`Czas ładowania strony: ${loadTime}ms`); // Wyślij do analytics gtag("event", "page_load_time", { value: loadTime, event_category: "Performance", }); }); } };
Kluczowe obszary optymalizacji
Optymalizacja obrazów i mediów wymaga użycia komponentu next/image z flagą priority dla hero images, implementacji formatów AVIF/WebP z fallback oraz odpowiednich rozmiarów i srcset. Lazy loading dla obrazów poniżej fold znacząco poprawia initial loading time.
Fonty i CSS wymagają preloadingu krytycznych fontów, używania system fonts jako fallback, inline critical CSS oraz usunięcia nieużywanego kodu. Te zmiany mają bezpośredni wpływ na First Contentful Paint.
JavaScript optimization obejmuje code splitting i dynamic imports, tree shaking dla bibliotek, minifikację i kompresję oraz implementację Service Worker dla cachowania. Każda z tych technik przyczynia się do lepszego First Input Delay.
Serwer i hosting powinny wykorzystywać CDN dla statycznych zasobów, Gzip/Brotli compression, HTTP/2 lub HTTP/3 oraz proper caching headers. Te optymalizacje server-side mają kluczowy wpływ na wszystkie Core Web Vitals.
Rezultaty optymalizacji
Praktyczne wdrożenie tych technik przynosi mierzalne rezultaty. LCP poprawia się z 4.2 sekundy do 1.8 sekundy, co oznacza 57% poprawę. FID spada ze 180ms do 45ms (75% poprawa), a CLS z 0.25 do 0.05 (80% poprawa). Ogólny PageSpeed Score wzrasta z 67 do 94 punktów, co daje przyrost o 27 punktów.
Podsumowanie
Optymalizacja Core Web Vitals w Next.js wymaga systematycznego podejścia obejmującego obrazy z właściwą konfiguracją next/image, JavaScript z code splitting i lazy loading, stabilność layoutu z placeholder oraz ciągłe monitoring i poprawki. Dzięki tym technikom osiągniesz wynik powyżej 90 punktów w PageSpeed i poprawisz pozycjonowanie w Google.
Potrzebujesz pomocy z optymalizacją? Skontaktuj się z nami - zoptymalizujemy Twoją stronę Next.js dla maksymalnej wydajności.
Tagi:
Gotowy na start swojego projektu?
Skontaktuj się ze mną, aby omówić Twoje potrzeby i otrzymać bezpłatną konsultację.
