TL;DR
Font z Google Fonts przez <link> w <head> to w 2026 roku regresja wydajności na autopilocie — dodatkowy DNS lookup, render-blocking CSS i CLS, gdy tekst „skacze" po załadowaniu wagi 600. next/font self-hostuje pliki w bundlu, ustawia font-display: swap i generuje CSS ze zmiennymi — bez requestu do fonts.googleapis.com. Subsetting do latin + latin-ext (polskie znaki ą, ę, ł) obcina 60–80% rozmiaru pliku względem pełnego unicode. Rezerwacja miejsca przez size-adjust lub stałe line-height na hero zapobiega layout shift. Hosting na DevStudioIT Cloud serwuje fonty z tego samego origin co HTML — jeden TLS, cache długoterminowy po buildzie.
Dla kogo to jest
- Stron firmowych z custom typography w Figma — dev musi to odwzorować bez psucia LCP
- Zespołów Next.js widzących CLS 0,15+ w Lighthouse mimo zoptymalizowanych obrazów
- Projektów wielojęzycznych PL/DE z latin-ext — pełny charset bez zbędnych glifów CJK
- Designerów wybierających 4 wagi fontu „bo wygląda" — trzeba uzasadnić budżet KB
- Każdego po audycie CWV, gdzie „font loading" pojawia się w diagnostyce
Fraza (SEO)
next/font subsetting, web fonty wydajność, cls fonty nextjs, font-display swap, typografia strona firmowa, google fonts self hosting 2026
Problem: zewnętrzne fonty a Core Web Vitals
Klasyczny import:
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">Łańcuch requestów:
| Krok | Opóźnienie | Wpływ |
|---|---|---|
| DNS fonts.googleapis.com | 20–80 ms | TTFB HTML już minął |
| CSS font-face | blocking lub FOIT | Opóźniony pierwszy tekst |
| DNS fonts.gstatic.com | kolejny RTT | LCP na tekście = gorsze |
| Pobranie .woff2 400 + 600 + 700 | 150–400 KB | Main thread decode |
CLS powstaje, gdy fallback (Arial) ma inne metryki niż Inter — nagłówek hero zmienia wysokość o 4–12 px po swap.
next/font — self-hosting w Next.js App Router
// app/fonts.ts
import { Inter } from 'next/font/google';
export const inter = Inter({
subsets: ['latin', 'latin-ext'],
weight: ['400', '600'],
display: 'swap',
variable: '--font-inter',
preload: true,
adjustFontFallback: true,
});Layout:
import { inter } from './fonts';
export default function RootLayout({ children }) {
return (
<html lang="pl" className={inter.variable}>
<body className="font-sans antialiased">{children}</body>
</html>
);
}Tailwind (tailwind.config.ts):
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
},
},
},| Opcja | Efekt |
|---|---|
subsets: ['latin-ext'] |
Polskie i niemieckie znaki diakrytyczne |
weight: ['400','600'] |
Tylko używane wagi — każda waga = osobny plik |
adjustFontFallback: true |
Automatyczny size-adjust na fallback |
variable |
Jedna klasa CSS, brak osobnych @font-face w komponentach |
Subsetting — co włączyć, co pominąć
| Subset | Kiedy | Przybliżony rozmiar Inter 400 |
|---|---|---|
latin |
EN-only landing | ~15 KB woff2 |
latin-ext |
PL, DE, CS, RO | +8–12 KB |
cyrillic |
Rynek UA/RU | +20 KB — tylko jeśli locale |
| Pełny unicode | ❌ unikaj | 200 KB+ |
Dla DevStudio.it (pl/en/de) wystarczy ['latin', 'latin-ext']. Nie importuj vietnamese „na wszelki wypadek".
Lokalny font (brand z licencją OTF):
import localFont from 'next/font/local';
export const brandSans = localFont({
src: [
{ path: '../public/fonts/Brand-Regular.woff2', weight: '400' },
{ path: '../public/fonts/Brand-SemiBold.woff2', weight: '600' },
],
display: 'swap',
variable: '--font-brand',
});Konwersja OTF → woff2: fonttools lub pipeline CI przed commitem.
CLS — rezerwacja miejsca poza next/font
adjustFontFallback pomaga, ale hero z dużym text-5xl i custom line-height nadal może shiftować:
.hero-title {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.15;
min-height: 2.3em; /* rezerwa na 2 linie */
}| Technika | CLS | Uwagi |
|---|---|---|
font-display: optional |
Najniższy | Ryzyko braku custom fontu na wolnej sieci |
swap + size-adjust |
Niski | Rekomendacja B2B |
| Preload tylko critical weight | LCP lepsze | preload: true w next/font domyślnie |
| System font stack na mobile | Zero CLS | Tradeoff brand vs metryki |
Testuj mobile 4G throttling w Lighthouse — desktop ukrywa problem.
Typografia a LCP — gdy LCP to tekst
Na stronie z minimalnym hero LCP elementem może być nagłówek H1, nie obraz. Wtedy:
- Preload tylko waga użyta w H1 (400 lub 600)
- Unikaj
@importfontów w CSS modułach — ładuje się późno - Server Component na hero — HTML z fontem w pierwszym bajcie streamu
Font nie powinien być w osobnym Client Component lazy-loaded — to opóźnia LCP tekstu.
Wiele fontów — heading + body bez eksplozji KB
| Pattern | Pliki wwoff2 | Rekomendacja |
|---|---|---|
| 1 rodzina, 2 wagi | 2 × ~20 KB | ✅ domyślny wybór |
| Body + display (2 rodziny) | 4 pliki | OK jeśli display tylko H1–H2 |
| 4 wagi + 2 rodziny | 8+ plików | Review w performance budget |
Display font na nagłówkach ładowany przez next/font z preload: false jeśli H1 poniżej fold na mobile — kontrowersyjne; lepiej zmniejszyć rozmiar display font subset.
Monitoring po deploy na DevStudioIT Cloud
Po wdrożeniu porównaj:
| Źródło | Metryka font-related |
|---|---|
| Lighthouse CI | CLS, LCP, „Font display" audit |
| CrUX / Search Console | Field CLS po 28 dniach |
| Web Vitals RUM | layout-shift attributions |
Regresja: designer dodał wagę 700 i italic „dla cytatu" — bundle +40 KB, CLS +0,05. Performance budget w CI powinien to złapać przy overall score; assert CLS bezpośrednio.
Handoff design → dev — typografia w Figma bez niespodzianek
Zanim developer implementuje fonty z mockupu, ustalcie tabelę decyzji:
| Element Figma | Implementacja Next.js | Pytanie do designu |
|---|---|---|
| Inter 400 body | next/font weight 400 |
Czy wystarczy? |
| Inter 600 nagłówki | weight 600 | Czy 700 jest konieczny? |
| Inter 700 CTA | Osobny plik woff2 | Czy 600 wystarczy na button? |
| Italic cytaty | +plik italic | Czy <em> w system font? |
Design token w Figma powinien mapować 1:1 na fontFamily w Tailwind — unikacie sytuacji „w Figma jest Satoshi, na produkcji Arial bo licencja". Jeśli brand font wymaga licencji web, woff2 w repo przed startem sprintu, nie w ostatnim dniu przed go-live.
Line-height z Figma (np. 120%) przenieś na CSS z min-height rezerwą na wieloliniowe H1 — Figma nie mierzy CLS.
Typografia a dostępność — nie tylko KB
| Wymóg | Minimum | Wpływ na fonty |
|---|---|---|
| Kontrast WCAG AA | 4,5:1 body, 3:1 large text | Cienka waga 300 na jasnym tle — unikaj |
prefers-reduced-motion |
Brak animacji tekstu | Nie animuj font-weight |
| Zoom 200% | Tekst czytelny bez horizontal scroll | rem zamiast sztywnego px na font-size |
Custom font nie usprawiedliwia niskiego kontrastu — wybierz wagę 500/600 zamiast 300 na szarym tle.
FAQ
next/font google vs self-host pliki w public/?
next/font/google pobiera przy build i self-hostuje — równoważnik ręcznego public/, ale z automatycznym hash w nazwie pliku i zero konfiguracji CORS. Ręczny public/ tylko przy fontach spoza Google (brand OTF).
Czy variable fonts (.woff2 variable) zawsze mniejsze?
Często tak przy 3+ wagach — jeden plik zamiast trzech. Nie każda rodzina ma dobrze hintowaną zmienną wersję; testuj rozmiar i render na Windows.
Fonty z CDN third-party (Adobe Fonts, Fontshare)?
Każdy zewnętrzny origin to DNS + cache osobno. Dla CWV self-host po licencji. Adobe Fonts na stronie marketingowej z twardym budżetem LCP — ryzykowne.
Inter vs system-ui — kiedy rezygnować z custom?
Landing A/B testowany Ads z LCP > 3 s na mobile — wariant ze system-ui jako test. Jeśli konwersja bez różnicy, zostaw system stack.
Chcesz typografię bez regresji CLS?
- Skontaktuj się z nami — dobierzemy font stack, next/font i progi w Lighthouse CI
- Performance budget CWV — egzekwowanie CLS w PR
- Optymalizacja obrazów Next.js — gdy LCP to hero image, nie font
O autorze
Budujemy szybkie strony WWW, aplikacje web/mobile, chatboty AI i hosting — z naciskiem na SEO i konwersję.
Przydatne linki
Od teorii do produkcji — Branchly, hosting i realizacje.
