Date structurate: Article, FAQPage și traseu pe fiecare pagină
Închide #19 (closed).
Până acum HTML-ul randat descria doar produsul și colecția. Blogul, pagina de întrebări frecvente și toate paginile statice plecau spre motoarele de căutare fără niciun bloc de date structurate — 27 din cele 42 de adrese din sitemap, în câte trei limbi.
Ce se schimbă
Traseu pe fiecare pagină statică. staticPage își construiește singur
BreadcrumbList din rootCrumb plus pagina curentă; parametrul extra dispare.
Două trepte și numai două: nici /therapy, nici /legal, nici /support nu are
pagină proprie în router, deci nu există treaptă intermediară de numit. Pagina
principală păstrează Organization în loc de un traseu care ar arăta spre ea
însăși, iar paginile ținute în afara căutării (/account, /comanda,
/order-confirmation) nu mai emit nimic.
Șapte pagini nu aveau nume scurt în fișierele de traducere, deci treapta ar fi
purtat titlul întreg — „Termeni și condiții de comandă și livrare", 41 de
caractere. Au primit unul, în cuvintele cu care meniul și subsolul le numesc
deja. seoLength.test.ts cere acum un nume pentru fiecare pagină statică, nu
doar pentru cele opt care apăreau într-un traseu înainte.
Article pe cele 72 de articole. Titlul întreg (nu cel scurtat pentru
lățimea unui rezultat), rezumatul, fotografia de pe pagină și magazinul ca
editor. Fără author și fără dateModified: corpusul nu înregistrează nici
autor, nici o modificare editorială — updatedAt înregistrează un import — iar
o valoare inventată e o afirmație făcută unui motor de căutare pe care datele nu
o susțin. Traseul articolului primește și treapta lipsă spre pagina principală.
datePublished se citește din data pe care o tipărește pagina, nu din instantul
stocat: dateISO a fost normalizat la UTC la import și pentru 41 din 72 de
articole cade cu o zi după data de sub titlu (toate cele 72 de valori
dateDisplay sunt exact dateISO afișat în America/Los_Angeles — fusul pe care
importul l-a pierdut).
FAQPage pe pagina de întrebări frecvente și pe cele 42 de produse cu
acordeon. Răspunsurile produsului vin din baza de date, în limba cititorului.
Răspunsurile paginii de asistență ajung la randare prin fișierul de șiruri pe
care build-ul frontend îl emite deja, dintr-un fișier nou pe care aplicația nu
îl încarcă niciodată — deci nu îngreunează pachetul de traduceri al niciunui
vizitator. Un test randează pagina și cere ca textul stocat să fie exact textul
afișat: marcaj care răspunde altfel decât pagina de sub el e singurul lucru pe
care ghidul Google îl interzice explicit.
Curățenie adusă de schimbare: adresa logoului era scrisă în două locuri
(structured-data.ts și email-template.ts); acum stă o singură dată, în
lib/company.ts.
Verificare
-
backend:npm run buildcurat,npx vitest run— 600 de teste în 39 de fișiere, trecute (29 dintre ele noi, întests/structured-data.test.ts). -
frontend:npx oxlint— un singur avertisment preexistent însrc/App.tsx;tsc --noEmit -p tsconfig.app.jsonși-p tsconfig.test.jsoncurate;npx vitest run— 433 de teste în 33 de fișiere, trecute;npm run buildcurat. - Randare reală: frontend construit, servit local, backend pornit peste
dev.db. 93 de pagini randate (15 pagini statice + 6 articole + 5 produse + 5 colecții, în trei limbi): fiecare bloc trece prinJSON.parse, 90 de trasee cu poziții 1..n contigue și adrese absolute cu prefixul de limbă corect, 18 blocuriArticle, 18 blocuriFAQPage, nicio pagină cu două blocuri de același tip. Zero probleme. - Baza de dezvoltare a rămas la valorile de referință: 61 de produse, 11 colecții, 72 de articole, o comandă.
Criteriul de acceptare, punctual
-
/blogs/hyperhub/<slug>× 3 limbi:Article+BreadcrumbList, ambele parsabile;datePublished2026-02-01acolo unde pagina scrie „February 1, 2026", deși instantul stocat e2026-02-02T07:58:21Z. -
/about,/therapy/recovery,/support/faq,/legal/terms,/recovery-rooms,/gift-cards× 3 limbi: câte unBreadcrumbList, poziții 1 și 2, adrese cu prefixul corect.
Rămâne în afara acestei lucrări
/gift-cards afișează un acordeon real cu trei întrebări, ale cărui texte stau
în tabelul de conținut editabil din panou, nu în fișierele de traducere. Nu
primește FAQPage aici; primește issue propriu.