SEO

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.

Adam Noszczyński
10 min czytania

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:

Core Web Vitals
Next.js
PageSpeed
SEO
Optymalizacja

Gotowy na start swojego projektu?

Skontaktuj się ze mną, aby omówić Twoje potrzeby i otrzymać bezpłatną konsultację.

Ailo client logoCledar client logoMiohome client logoPlenti client logoWebiso client logo+10
Realizuję projekty dla klientów od 6 lat