export function buildCanonicalUrl(baseUrl: string, pathname: string, rawParams: Record<string, string | string[] | undefined>): string {
const url = new URL(pathname, baseUrl);
// Păstrăm DOAR parametrii relevanți pentru indexare (ex: paginație)
if (rawParams.page) {
const pageNum = parseInt(String(rawParams.page), 10);
if (!isNaN(pageNum) && pageNum > 1) {
url.searchParams.set('page', pageNum.toString());
}
}
// Eliminăm automat UTM-uri, sortări, token-uri de sesiune
return url.toString();
}Am văzut atâtea site-uri cu traficul organic în picaj din cauza tag-urilor canonical setate aiurea, încât am zis că e timpul să scriu ghidul ăsta scurt. Când construiești un e-commerce sau un SaaS cu mii de pagini, un <link rel="canonical"> pus greșit îți poate îngropa tot SEO-ul în câteva săptămâni. Hai să trecem prin cazurile reale pe care le-am lovit în ultimii ani și prin soluțiile care chiar funcționează.
Filtre și URL parameters: capcana clasică din E-commerce
La un proiect din 2022 cu vreo 40.000 de produse, am observat o scădere de 25% a traficului organic pe categoriile principale. Când am săpat în Google Search Console, am descoperit că Google Bot indexa sute de variații ale aceleiași pagini: ?sort=price_asc, ?color=blue&size=m, plus parametri de tracking de prin ad-uri (?utm_source=facebook).
Soluția obligatorie aici este ca toate variantele parametrizate ale unei pagini să aibă canonical-ul către varianta curată, fără query string-uri de sortare sau tracking.
O excepție importantă: dacă un parametru schimbă dramatic conținutul (de exemplu, un filtru de brand într-o categorie mare care generează o pagină dedicată, stil /laptopuri?brand=apple), acolo s-ar putea să vrei ca pagina să fie indexabilă separat. Însă pentru sortări, paginații și culori secundare, URL-ul curat trebuie să fie singura autoritate.
Paginated content: greșeala de începător pe care o văd zilnic
Înainte, Google folosea atributele rel="next" și rel="prev". De câțiva ani le-au scos din ecuație. Ce fac mulți devi din păcate? Pe pagina /blog?page=2 pun canonical către /blog. Asta e o greșeală uriașă.
Dacă pui canonical către prima pagină, îi spui motorului de căutare că pagina 2 este un duplicat al paginii 1. Ce se întâmplă? Google nu mai crawling-ează paginile 2, 3, 4 și pierzi indexarea tuturor articolelor sau produselor mai vechi care apar doar pe paginile respective.
Regula corectă: fiecare pagină dintr-o paginare trebuie să aibă canonical auto-referențial. Adică /produse?page=2 are canonical exact către /produse?page=2.
Cross-Domain Canonical: când republici conținut
Am avut cazul unui grup de magazine online care vindeau în România, Ungaria și Bulgaria. Preluau articole de blog identice pe toate domeniile, dar traducerea dura câteva săptămâni. Până apărea versiunea tradusă, lăsau textul în română.
Ca să nu fim penalizați pentru duplicate content între domenii diferite, am folosit cross-domain canonical. Pagina de pe domeniul .hu avea <link rel="canonical" href="https://site.ro/blog/articol">.
Trade-off sincer: Canonical-ul este o sugestie pentru Google, nu o directivă strictă (cum e un redirect 301). Dacă algoritmii consideră că cele două pagini sunt totuși suficient de diferite, s-ar putea să ignore recomandarea ta. Dar în 90% din cazuri, dacă conținutul e identic, funcționează perfect.
Cum implementăm asta curat în cod
Dacă lucrezi cu SSR, trebuie să sanitizezi URL-ul înainte de a-l injecta în <head>. Iată un helper simplu în TypeScript pe care îl folosesc ca să scot parametrii inutili de tracking menținând doar ruta curată și paginarea rezonabilă.
Voi cum gestionați canonical-ul pe produsele care apar în mai multe categorii diferite (ex: /electronice/telefoane/iphone vs /promotii/iphone)? Lăsați URL-ul fără categorie în path sau folosiți master category?