1.1.0: R2 storage, media preconnect/normalization, favicon + SEO structured data, x-default hreflang
This commit is contained in:
+211
-1
@@ -254,6 +254,19 @@ części — dodasz, gdyby realnie było ich tyle. Jedna sitemapa wystarcza do ~
|
||||
Favicon i structured data wpływają na to, jak strona wygląda w wynikach Google.
|
||||
Plugin generuje jedno i drugie z panelu — projekt tylko wpina w root layout.
|
||||
|
||||
### Favicon — format: PNG, nie SVG (ważne)
|
||||
|
||||
**Dla Google użyj PNG (≥48×48), nie SVG.** Zweryfikowane: Google niezawodnie
|
||||
wspiera PNG i ICO, ale **SVG w wynikach Google jest zawodny** — często pokazuje
|
||||
glob mimo że w karcie przeglądarki favicon renderuje się dobrze. Oficjalna
|
||||
dokumentacja Google nie wymienia SVG. Jeśli zależy Ci na faviconie w wyszukiwarce
|
||||
— wgraj PNG.
|
||||
|
||||
- **PNG ≥48×48** (idealnie 96 lub 192), kwadratowy → działa w Google ✓
|
||||
- **SVG** → działa w przeglądarce, ale w Google glob (zawodne) ✗
|
||||
- Walidacja pola favicon OSTRZEGA, gdy wgrasz SVG (żebyś wiedział, że dla search
|
||||
potrzebny PNG).
|
||||
|
||||
### Favicon — dlaczego się nie pokazywał
|
||||
|
||||
Google ma twarde wymogi: `<link rel="icon">` w `<head>`, kwadratowy, **≥48×48px**,
|
||||
@@ -326,4 +339,201 @@ favicon URL jest publiczny (nie za auth).
|
||||
z poprawnym URL.
|
||||
2. Otwórz sam URL favicon w przeglądarce — obraz się pokazuje, ≥48×48.
|
||||
3. Rich Results Test (Google) — wklej URL strony, sprawdź Organization.
|
||||
4. Search Console → poproś o ponowne indeksowanie strony głównej.
|
||||
4. Search Console → poproś o ponowne indeksowanie strony głównej.
|
||||
|
||||
## Ręczne rozszerzenia SEO/PWA (manifest itp.) — z panelu, NIE hardkod
|
||||
|
||||
Niektóre rzeczy SEO/PWA są na tyle projekt-specyficzne i jednorazowe, że plugin
|
||||
ich nie dostarcza (byłoby przeinżynierowaniem). Robisz je w projekcie — ALE
|
||||
poprawnie: czytając z panelu/env, nie zaszywając wartości klienta.
|
||||
|
||||
> **Zasada:** nawet gdy coś robisz ręcznie w projekcie, dane (nazwa, kolory,
|
||||
> opis, logo) czytaj z panelu (SiteSettings) albo env. Zaszyta nazwa/kolor
|
||||
> klienta = antywzorzec (patrz standardy-kodu.md). Manifest „R Custom Cars" z
|
||||
> hardkodem zadziała tylko dla jednego klienta.
|
||||
|
||||
### Web App Manifest (PWA) — jak zrobić DOBRZE
|
||||
|
||||
```ts
|
||||
// app/manifest.ts
|
||||
import type { MetadataRoute } from 'next'
|
||||
import { getCachedPayload } from '@/lib/content'
|
||||
import { getSiteSettings } from '@intecion/ipal-kit'
|
||||
import type { SiteSetting } from '@/payload-types'
|
||||
|
||||
export default async function manifest(): Promise<MetadataRoute.Manifest> {
|
||||
const payload = await getCachedPayload()
|
||||
const settings = await getSiteSettings<SiteSetting>(payload, { locale: 'pl' as never })
|
||||
|
||||
// Wszystko z panelu — zero hardkodu. Ikona z pola logo/favicon (upload),
|
||||
// nie ze statycznej ścieżki.
|
||||
const iconUrl =
|
||||
typeof settings.logo === 'object' && settings.logo?.url ? settings.logo.url : undefined
|
||||
|
||||
return {
|
||||
name: settings.siteName ?? '',
|
||||
short_name: settings.siteName ?? '', // albo osobne pole, jeśli dodasz
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
...(iconUrl
|
||||
? { icons: [{ src: iconUrl, sizes: 'any', type: 'image/svg+xml' }] }
|
||||
: {}),
|
||||
// description / theme_color / background_color:
|
||||
// jeśli klient ich potrzebuje, DODAJ POLA w SiteSettings i czytaj stąd —
|
||||
// NIE wpisuj '#d4af37' na sztywno. Bez pól — pomiń (manifest działa bez nich).
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Kontrast — czego NIE robić** (realny błąd z sesji):
|
||||
|
||||
```ts
|
||||
// ŹLE — wszystko zaszyte, zadziała tylko dla jednego klienta
|
||||
let name = 'R Custom Cars' // hardkod nazwy
|
||||
short_name: 'RCC', // hardkod
|
||||
description: 'Custom car styling...', // hardkod
|
||||
background_color: '#08080a', theme_color: '#d4af37', // hardkod kolorów
|
||||
icons: [{ src: '/logo/rcc-logo.svg' }] // statyczna ścieżka, nie panel
|
||||
```
|
||||
|
||||
Jeśli klient potrzebuje kolorów motywu / opisu w manifeście — **dodaj pola**
|
||||
`themeColor`, `manifestDescription` w SiteSettings (SiteSettingsFields przez
|
||||
opcje pluginu) i czytaj z panelu. Wtedy redaktor je zmienia, i nie są zaszyte.
|
||||
|
||||
### Inne ręczne rozszerzenia — ta sama zasada
|
||||
|
||||
Cokolwiek dodajesz ręcznie (dodatkowe meta tagi, structured data konkretnego
|
||||
typu, itp.):
|
||||
- dane z panelu (SiteSettings / pola strony) albo env
|
||||
- nic zaszytego per klient (nazwa, kolor, adres, domena)
|
||||
- jeśli to uniwersalne i powtarzalne → rozważ zgłoszenie do pluginu zamiast
|
||||
ręcznie (patrz ANTIGRAVITY-ZASADY-AGENT.md A0)
|
||||
|
||||
## SEO wielojęzyczne — hreflang, x-default, redirect roota
|
||||
|
||||
Przekierowanie `/` → `/pl` (negocjacja locale) może wpływać na SEO. Kluczowe:
|
||||
plugin generuje hreflang, więc Google rozumie, że `/pl` i `/en` to wersje
|
||||
językowe (nie duplikaty). Ale są niuanse.
|
||||
|
||||
### hreflang + x-default (generowane przez plugin)
|
||||
|
||||
`buildHreflangAlternates` generuje `alternates.languages` z wpisami per locale
|
||||
ORAZ **`x-default`** wskazujący na defaultLocale. x-default mówi Google: „gdy
|
||||
język/region użytkownika nie pasuje do żadnej wersji, użyj TEJ" — co pokrywa
|
||||
sytuację roota (Googlebot bez preferencji językowej). Bez x-default Google
|
||||
zgadywałby; z nim dostaje jasną wskazówkę (domyślnie pl).
|
||||
|
||||
Działa automatycznie przez createPageMetadata (canonical + hreflang + x-default).
|
||||
|
||||
### Redirect roota — na co uważać
|
||||
|
||||
- **307 (temporary)** na `/` → `/pl` — plugin tak robi. Dla warunkowego redirectu
|
||||
(zależnego od negocjacji) to obronne. Google i tak podąża.
|
||||
- **Negocjacja Accept-Language** — Googlebot bywa z `Accept-Language: en` albo
|
||||
bez. Może trafić na `/en`. x-default (→ pl) łagodzi to: Google wie, że
|
||||
domyślna wersja to polska.
|
||||
- **Root nie ma własnej treści** — cała moc idzie przez redirect na locale. To
|
||||
normalne dla i18n stron, hreflang to obsługuje.
|
||||
|
||||
### Weryfikacja SEO wielojęzycznego
|
||||
|
||||
1. Search Console → Inspekcja URL dla `/` — zobacz, na co Google przekierowuje
|
||||
i co indeksuje.
|
||||
2. Sprawdź, czy `/pl` i `/en` są indeksowane osobno (nie jako duplikaty).
|
||||
3. Rich Results / źródło strony → potwierdź `<link rel="alternate" hreflang="...">`
|
||||
z wpisami per locale + `hreflang="x-default"`.
|
||||
4. Search Console → raport Międzynarodowe targetowanie (jeśli dostępny) — błędy
|
||||
hreflang.
|
||||
|
||||
### Częste błędy (nie rób tak)
|
||||
|
||||
- Brak hreflang → Google traktuje wersje jako duplikaty (plugin to ma, nie usuwaj).
|
||||
- Zaszyta mapa ścieżek zamiast getLocalizedSlugs → hreflang się rozjedzie z bazą.
|
||||
- `noindex` na `/pl` przez pomyłkę → wypada z indeksu. Sprawdź robots meta.
|
||||
- Redirect roota na twardo 301 do jednego języka → tracisz negocjację i drugą
|
||||
wersję. Zostaw negocjację + hreflang.
|
||||
|
||||
## Sitelinks i structured data (branding w wynikach Google)
|
||||
|
||||
Cel: żeby wyszukanie marki („rcustomcars") pokazało stronę główną + podlinki
|
||||
(sitelinks) z opisami. Ważne — **sitelinków NIE DA SIĘ wymusić.** Google
|
||||
generuje je algorytmicznie ze struktury strony, linkowania wewnętrznego, jasnych
|
||||
tytułów i rankingu. Żaden kod ich nie włączy. Plugin dostarcza SYGNAŁY, które
|
||||
zwiększają szansę — nie gwarancję.
|
||||
|
||||
### Co realnie wpływa na sitelinki (kolejność wg wagi)
|
||||
|
||||
1. **Ranking na 1. stronie Google** — bez tego sitelinków nie ma. To robota SEO
|
||||
(treść, linki), nie kodu.
|
||||
2. **Czysta struktura + jasne tytuły** — logiczna hierarchia stron, opisowe title
|
||||
(nie „Strona 1"). Patrz fundamenty-projektu.md.
|
||||
3. **Linkowanie wewnętrzne** — ważne strony podlinkowane z głównej.
|
||||
4. **Structured data** (poniżej) — sygnał pomocniczy, nie przełącznik.
|
||||
5. **Sitemap + robots** — żeby Google w ogóle widział wszystkie strony (patrz
|
||||
niżej — to fundament, sprawdź czy działa!).
|
||||
|
||||
### Structured data z pluginu — 3 helpery
|
||||
|
||||
Wszystkie emitowane jako `<script type="application/ld+json">`, dane z panelu.
|
||||
|
||||
**1. WebSite + SearchAction (największy realny efekt)** — może dać sitelinks
|
||||
searchbox (pole wyszukiwania pod wynikiem marki). RAZ w root layout:
|
||||
```tsx
|
||||
import { buildWebSiteJsonLd } from '@intecion/ipal-kit'
|
||||
const jsonLd = buildWebSiteJsonLd({
|
||||
name: settings.siteName,
|
||||
url: baseUrl,
|
||||
// TYLKO jeśli masz działającą stronę wyszukiwania:
|
||||
search: { target: `${baseUrl}/szukaj?q={search_term_string}` },
|
||||
})
|
||||
```
|
||||
Pomiń `search`, jeśli nie ma realnej wyszukiwarki — SearchAction wskazujący na
|
||||
nieistniejącą stronę szkodzi.
|
||||
|
||||
**2. BreadcrumbList (realny efekt)** — okruszki w wynikach (Dom › Usługi ›
|
||||
Detailing) + Google rozumie hierarchię. PER STRONA, z pozycji strony:
|
||||
```tsx
|
||||
import { buildBreadcrumbJsonLd } from '@intecion/ipal-kit'
|
||||
const jsonLd = buildBreadcrumbJsonLd([
|
||||
{ name: 'Strona główna', url: `${base}/pl` },
|
||||
{ name: 'Usługi', url: `${base}/pl/uslugi` },
|
||||
{ name: 'Detailing', url: `${base}/pl/uslugi/detailing` },
|
||||
])
|
||||
```
|
||||
Okruszki buduj z RZECZYWISTEJ pozycji strony (resolveRoute / ścieżka URL), NIE z
|
||||
zaszytej listy.
|
||||
|
||||
**3. SiteNavigationElement (słabszy, tani)** — nawigacja jako dane. RAZ, z tych
|
||||
samych pozycji co menu w headerze:
|
||||
```tsx
|
||||
import { buildSiteNavigationJsonLd } from '@intecion/ipal-kit'
|
||||
const jsonLd = buildSiteNavigationJsonLd(
|
||||
navItems.map(i => ({ name: i.label, url: `${base}${i.href}` }))
|
||||
)
|
||||
```
|
||||
Dane z tego samego źródła co widoczne menu — nie osobna zaszyta lista.
|
||||
|
||||
### Realne oczekiwania (ważne)
|
||||
|
||||
- Structured data **nie gwarantuje** sitelinków — to sygnał wśród wielu.
|
||||
- Efekt (jeśli będzie) pojawia się **po tygodniach**, gdy Google przecrawluje i
|
||||
strona rankuje.
|
||||
- Największy wpływ ma **ranking + struktura + linkowanie**, nie schema. Schema
|
||||
pomaga Google zrozumieć, ale nie zastąpi bycia na 1. stronie.
|
||||
- Weryfikuj: Google Rich Results Test (czy schema poprawna) + Search Console
|
||||
(co Google pokazuje dla marki).
|
||||
|
||||
### To, co ZALEŻY OD PROJEKTU (obowiązki wpięcia)
|
||||
|
||||
Plugin dostarcza helpery — projekt MUSI je wpiąć i podać dane z panelu:
|
||||
|
||||
- [ ] `buildWebSiteJsonLd` w root layout (search tylko jeśli jest wyszukiwarka)
|
||||
- [ ] `buildOrganizationJsonLd` w root layout (logo, nazwa)
|
||||
- [ ] `buildBreadcrumbJsonLd` na podstronach (z realnej ścieżki)
|
||||
- [ ] `buildSiteNavigationJsonLd` z pozycji menu (jeśli jest header nav)
|
||||
- [ ] `app/robots.ts` i `app/sitemap.ts` wystawione (patrz niżej — bez tego
|
||||
Google nie widzi stron!)
|
||||
- [ ] Jasne, opisowe tytuły stron (nie generyczne)
|
||||
- [ ] Logiczna hierarchia + linkowanie wewnętrzne z głównej
|
||||
|
||||
Dane WSZĘDZIE z panelu (siteName, nav, logo), nigdy zaszyte.
|
||||
Reference in New Issue
Block a user