Webdev 6 min read

Jak zdobyliśmy 100/100/100/100 w PageSpeed? Kompletne Case Study (2026)

Totalny przewodnik po optymalizacji. Astro, Partytown, WCAG, Self-hosted Fonts i walka z Cloudflare AI Bots. Kod, strategia i wnioski.

To nie jest kolejny teoretyczny artykuł. To jest zapis z pola bitwy. Dziś, 31 stycznia 2026 roku, osiągnęliśmy Święty Graal Web Developmentu.

0
Performance
0
Accessibility
0
Best Practices
0
SEO

Co to oznacza? Że nasza strona jest szybsza, bardziej dostępna i lepiej zoptymalizowana niż 99% internetu. Nie zrobiliśmy tego na pustej wizytówce. To żywy, dynamiczny ekosystem z:

  • Analityką (GA4 + GTM)
  • Wielojęzycznością (PL/EN)
  • Dynamicznym Contentem (MDX + Collections)
  • Agresywnym Designem (Neonowe kolory)

Oto ostateczny przewodnik, jak to zrobiliśmy. Krok po kroku. Z kodem.


Dlaczego ludzie nie mają 100/100?

W 2026 roku web development jest… gruby.

  • Next.js / React: Hydracja po stronie klienta (Client-Side Hydration) sprawia, że nawet prosta strona pobiera setki kilobajtów JavaScriptu.
  • Marketing Tag Bloat: Każdy dział marketingu chce HotJar, Meta Pixel, LinkedIn Insight i GA4. To zabija główny wątek przeglądarki.
  • Fonty z CDN: Pobieranie fontów z Google Fonts to dodatkowe handshake’i DNS i opóźnienia.
  • Cloudflare: Domyślne ustawienia “bezpieczeństwa” potrafią zablokować boty SEO (o tym później - to nasz “Final Boss”).

My powiedzieliśmy “NIE”.

Filar 1: Architektura “Zero Bloat” (Astro)

Zamiast popularnego Next.js, wybraliśmy Astro. Dlaczego?

0kb JavaScript by Default

Astro renderuje wszystko na serwerze (lub podczas buildu) do czystego HTML. Jeśli masz przycisk, który nie robi nic interaktywnego - Astro wyśle go jako Running HTML. Zero JavaScriptu.

Jeśli potrzebujemy interakcji (np. nasz animowany licznik powyżej), używamy dyrektywy client:visible. To tzw. Islands Architecture. Ładujemy JS tylko dla tej jednej małej “wyspy”, reszta oceanu to statyczny HTML.

<!-- To jest czysty HTML -->
<Header />

<!-- To ładuje JS dopiero jak użytkownik to zobaczy -->
<LighthouseScore client:visible />

<!-- To znowu czysty HTML -->
<Footer />

Wynik? Total Blocking Time (TBT) na poziomie 0 milisekund.


Filar 2: Fonty (Zabójca LCP)

Najczęstszy błąd? <link href="https://fonts.googleapis.com...">

Dlaczego to zło?

  1. Przeglądarka musi połączyć się z serwerem Google (DNS + SSL).
  2. Musi pobrać plik CSS.
  3. Dopiero z tego CSS dowiaduje się, że musi pobrać plik fontu (kolejne zapytanie). To opóźnia Largest Contentful Paint (LCP) o kluczowe milisekundy.

Rozwiązanie: Self-Hosting (@fontsource)

Zainstalowaliśmy fonty jako pakiety NPM.

npm install @fontsource/space-grotesk @fontsource/roboto-mono

W naszym BaseLayout.astro importujemy je bezpośrednio. Astro (i Vite) są tak sprytne, że:

  1. Wrzucają definicje fontów prosto do naszego CSS.
  2. Pliki fontów (woff2) są serwowane z naszej domeny.
  3. Zero zapytań do zewnętrznych serwerów.

Tekst pojawia się natychmiast. Flash of Unstyled Text (FOUT) jest zminimalizowany.


Filar 3: Analityka w Web Workerze (Partytown)

Chcieliśmy mieć Google Analytics 4. Ale GA4 to “ciężka krowa”. Jego skrypt parsuje zdarzenia, wysyła requesty… to wszystko blokuje Main Thread. Jeśli Main Thread jest zajęty, użytkownik klika, a strona “nie reaguje”.

Rozwiązanie: Partytown

Partytown to biblioteka (rozwijana przez twórców Builder.io), która przenosi skrypty 3rd party (GA4, GTM, Pixel) do Web Workera. Web Worker to “drugi mózg” przeglądarki. Działa w tle, na osobnym wątku procesora.

Dzięki temu:

  1. Główny wątek (UI) rysuje stronę i obsługuje kliknięcia.
  2. Wątek w tle (Worker) mieli dane analityczne.

Konfiguracja w astro.config.mjs:

import partytown from '@astrojs/partytown';

export default defineConfig({
  integrations: [
    partytown({
      config: {
        forward: ["dataLayer.push"], // Przekazujemy zdarzenia do Workera
      },
    })
  ]
});

Dla Google PageSpeed Insights nasza analityka de facto nie istnieje. Nie obciąża procesora w fazie ładowania. Magia.


Filar 4: Walka o Kontrast (Accessibility 100)

Nasz design system to “Neon Sunset” – ostre pomarańcze (#ff9900) na białym tle. Wygląda to świetnie, ale… Neonowy pomarańcz na bieli ma kontrast 2.15:1. Norma WCAG AA (wymagana dla 100 Accessibility) to 4.5:1.

Początkowo mieliśmy wynik 92/100. Lighthouse bezlitośnie punktował nasze przyciski i linki.

Błąd kontrastu w Lighthouse

Strategia “Smart Contrast”

Nie chcieliśmy rezygnować z neonu i robić nudnej, brązowej strony. Zastosowaliśmy “Split Strategy”:

  1. Elementy Dekoracyjne: Ramki, tła, kropki, glow – zostały Neonowe (#ff9900). (Elementy niebędące tekstem nie muszą spełniać normy 4.5:1).
  2. Tekst (Accent Text): Stworzyliśmy nową zmienną CSS --text-accent: Deep Amber (#b45309). To ciemniejszy odcień pomarańczu, który na białym tle ma kontrast > 4.5.
  3. Przyciski: Zamiast białego tekstu na neonowym tle (błąd), daliśmy czarny tekst na neonowym tle.

Kod CSS:

:root {
  --accent-amber: #ff9900;      /* Neon (Dekoracje) */
  --text-accent: #b45309;       /* Ciemny (Tekst) */
}

/* Przyciski - max kontrast */
.btn-primary {
  background: var(--accent-amber);
  color: #000000; /* Czarny tekst jest super czytelny */
}

Dla oka? Strona nadal “świeci” i jest neonowa. Dla algorytmu? Wszystko jest czytelne. 100/100.


Filar 5: Final Boss – Cloudflare i Robots.txt (100 SEO)

Mieliśmy już 100 Performance, 100 Accessibility, 100 Best Practices. Ale SEO stało na 92. Lighthouse krzyczał: Unknown directive: Content-Signal w pliku robots.txt.

Byliśmy skonsternowani. Nasz plik robots.txt w repozytorium był czysty! Okazało się, że Cloudflare (nasz CDN) “uszczęśliwiał nas na siłę”. Funkcja AI Crawl Control automatycznie wstrzykiwała do pliku robots.txt instrukcje dla botów AI (np. Content-Signal: ai-train=no). Google PageSpeed (jeszcze) nie rozumie tej dyrektywy i traktuje ją jako błąd składni.

Ustawienia Cloudflare Robots.txt

Dodatkowo, włączony Bot Fight Mode (Super Bot Fight Mode) potrafi blokować “dobre” boty AI, co w erze GEO (Generative Engine Optimization) jest strzałem w kolano.

Cloudflare Bot Fight Mode

Rozwiązanie Nuklearne (The Server Hack)

Zamiast walczyć z ustawieniami panelu, postanowiliśmy przejąć całkowitą kontrolę nad robots.txt. Usunęliśmy statyczny plik public/robots.txt. Stworzyliśmy endpoint API src/pages/robots.txt.ts, który generuje ten plik dynamicznie po stronie serwera.

// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';

const robotsTxt = `
User-agent: *
Allow: /
Sitemap: https://tripletesting.pl/sitemap-index.xml
`.trim();

export const GET: APIRoute = () => {
    return new Response(robotsTxt, {
        headers: {
            'Content-Type': 'text/plain; charset=utf-8',
        },
    });
};

Dzięki temu to NASZ KOD decyduje co widzi bot, a nie automaty Cloudflare’a. Dodatkowo w panelu Cloudflare wyłączyliśmy:

  • “Manage your robots.txt”: OFF
  • “Bot Fight Mode”: OFF

Wynik? Błąd zniknął. Czyste 100 punktów SEO.

Wynik 100/100/100/100


Podsumowanie i Checklist (TL;DR)

Oto twoja lista do zrobienia, jeśli chcesz 100/100:

  1. Framework: Wybierz SSG (Astro, 11ty). Unikaj Next.js jeśli nie musisz.
  2. Obrazy: Tylko WebP/AVIF. Zawsze z jawnym width i height.
  3. Fonty: Self-host (@fontsource). Żadnych linków do Google Fonts.
  4. Skrypty: Wszystkie trackery (GA4, FB Pixel) wrzuć do Partytown.
  5. Kolory: Sprawdź kontrast tekstów. Używaj narzędzi typu “Color Contrast Checker”. Jeśli neon - przyciemnij tekst.
  6. Robots.txt: Uważaj na Cloudflare AI features. Jeśli psują walidację - serwuj plik programistycznie.

To laboratorium udowadnia, że w 2026 roku można mieć piękną i szybką stronę. Wymaga to jednak inżynierskiego podejścia do każdego kilobajta.

Witaj w TripleTesting.


Chcesz sprawdzić jak to zrobiliśmy w kodzie? Cały ten projekt jest [Open Source] (jeśli tak zdecydujemy ;)).