Updated docs

This commit is contained in:
2026-09-09 00:06:18 +02:00
parent fce17654f8
commit 99490bfc4d
3 changed files with 107 additions and 26 deletions
+74 -25
View File
@@ -482,54 +482,103 @@ typu, itp.):
- jeśli to uniwersalne i powtarzalne → rozważ zgłoszenie do pluginu zamiast
ręcznie (patrz ANTIGRAVITY-ZASADY-AGENT.md A0)
## KRYTYCZNE: metadata w <head> dla Google (htmlLimitedBots)
## KRYTYCZNE: metadata w <head> dla Google
**Największa pułapka SEO w Next.js — dotyczy KAŻDEGO projektu.** Dla dynamicznie
renderowanych stron (SSR) Next.js **streamuje metadata do `<body>`**, nie `<head>`,
i przenosi ją do head skryptem JS. Skutek: canonical, hreflang, title, favicon
lądują w body w surowym HTML. Crawlery, które nie wykonują JS (Screaming Frog,
część botów), widzą je poza head → ignorują → utrata SEO.
lądują w body w surowym HTML. Crawlery bez JS (Screaming Frog, część botów) widzą
je poza head → ignorują → utrata SEO.
Google *twierdzi*, że wykonuje JS i widzi przeniesione tagi, ale praktyka
(i audyty) pokazują realne problemy z indeksacją canonical. Bezpieczniej wymusić
metadata do head dla crawlerów.
**To wyścig czasowy (race condition):** gdy baza odpowie szybko, metadata zdąży
do head; gdy wolniej (albo crawler odpytuje wiele stron naraz, obciążając bazę),
Next zamyka `</head>` i dokleja metadata w `<body>`. Dlatego pojedynczy `curl`
może pokazać head OK, a test 10 zapytań — 5/10 w body. **Testuj wielokrotnie.**
### Rozwiązanie — htmlLimitedBots w next.config
### Rozwiązanie GŁÓWNE — ISR (revalidate) w stronach
Najskuteczniejsze: **cache całej strony (ISR)**. Strona generowana raz z gotowym
`<head>`, kolejne żądania serwują cache — zero zapytań do bazy przy renderowaniu,
więc race condition ZNIKA (metadata zawsze w head). Bonus: TTFB spada z ~500ms do
~20ms, znikają sporadyczne 503 (cold start).
```ts
// app/(frontend)/[locale]/[[...slug]]/page.tsx
export const revalidate = 3600 // cache 1h, regeneracja w tle
```
> **UWAGA — ISR a treść z panelu:** strona cache'owana `revalidate` sekund NIE
> pokaże zmian redaktora od razu (czeka do rewalidacji). Dla treści zmienianej
> rzadko OK. Jeśli redaktor ma widzieć zmiany natychmiast — użyj **on-demand
> revalidation**: hook `afterChange` w kolekcji → `revalidatePath(path)` (patrz
> HOOKS.md). Albo krótszy `revalidate` (np. 300 = 5 min). NIE łącz ISR z
> `force-dynamic` — wykluczają się.
### Rozwiązanie DRUGIE — usuń jawny <head> z layoutu
Sztywny `<head>` w layoucie App Router wymusza przedwczesne zamknięcie head —
zanim strona wygeneruje metadane. To pcha metadata do body. NIE deklaruj `<head>`:
```tsx
// ŹLE — jawny <head> zamyka head za wcześnie
<html lang={locale}>
<head><MediaPreconnect /></head>
<body>{children}</body>
</html>
// DOBRZE — MediaPreconnect w body, React 19 hoistuje link do head
<html lang={locale}>
<body>
<MediaPreconnect />
{children}
</body>
</html>
```
React 19 sam przenosi `<link rel="preconnect">` do head. Jawny `<head>` jest
zbędny i szkodliwy (wymusza wczesne zamknięcie).
### Rozwiązanie TRZECIE — htmlLimitedBots (uzupełnienie)
Dodatkowo można wymusić blocking metadata dla crawlerów (przydatne, gdy strona
z jakiegoś powodu nie może być ISR):
```ts
// next.config.ts
const nextConfig: NextConfig = {
// Wymusza blocking metadata (canonical, hreflang, title, favicon) w <head>
// dla crawlerów SEO — zamiast streamingu do <body>.
htmlLimitedBots:
/Googlebot|Google-InspectionTool|Storebot-Google|Bingbot|Yandex|DuckDuckBot|Baiduspider|Screaming Frog|AhrefsBot|SemrushBot/i,
// ...reszta
}
```
`htmlLimitedBots` mówi Next: dla tych User-Agentów wyłącz streaming, wstaw
metadata do `<head>` w surowym HTML (blocking). Użytkownicy dalej dostają
streaming (szybkie ładowanie); crawlery dostają poprawny head.
`htmlLimitedBots` wyłącza streaming dla tych User-Agentów (metadata w head). ALE
to słabsze niż ISR — bo strona dalej renderuje dynamicznie (zapytanie do bazy →
wolniej, ryzyko race). **ISR eliminuje przyczynę, htmlLimitedBots łagodzi objaw.**
Najlepiej: ISR + brak jawnego head. htmlLimitedBots jako dodatkowa warstwa.
### Objawy (że masz ten problem)
- Screaming Frog: „canonical/hreflang/title outside <head>"
- Search Console: „brak canonical", favicon nie pokazuje się (glob)
- W surowym HTML canonical/title są PO `</head>`, na końcu body, ze skryptem
`document.querySelectorAll('body link[rel=icon]')...appendChild`
- Screaming Frog: „canonical/hreflang/title outside <head>" (na wielu podstronach)
- Search Console: „brak canonical", favicon glob
- Surowy HTML: canonical/title PO `</head>`, na końcu body, ze skryptem appendChild
- Test 10 zapytań: część w head, część w body (race condition)
### Weryfikacja
### Weryfikacja — TESTUJ WIELOKROTNIE (nie pojedynczo)
Pojedynczy `curl` może trafić w „szczęśliwy" timing. Testuj 10 razy:
```bash
# jako Googlebot — metadata MUSI być w <head>
curl -A "Googlebot" https://twojadomena.pl/pl/strona | grep -o '<head>.*</head>' | grep canonical
# jako user — streaming (metadata w body — OK dla ludzi wykonujących JS)
curl -A "Mozilla/5.0" https://twojadomena.pl/pl/strona
# 10 zapytań jako Googlebot — ile ma canonical w <head>
for i in $(seq 1 10); do
curl -s -A "Googlebot" https://twojadomena.pl/pl/strona | \
python3 -c "import sys; h=sys.stdin.read(); e=h.find('</head>'); c=h.find('rel=\"canonical\"'); print('head' if 0<c<e else 'BODY')"
done
# Cel: 10x 'head'. Jeśli część 'BODY' → race condition, dodaj ISR.
```
Bez htmlLimitedBots ten sam problem dotknie favicon (glob w Google), canonical
(„User-declared canonical: None"), hreflang i title. Jedna linia w config
naprawia wszystko naraz.
Testuj też PODSTRONY (nie tylko główną) — race częściej dotyka podstron.
DevTools (F12) NIE nadaje się do testu — hoistuje tagi do head automatycznie,
pokazując fałszywie poprawny head. Używaj `curl` / Ctrl+U (surowe źródło).
## SEO wielojęzyczne — hreflang, x-default, redirect roota