gora.
įrašas · 2026 m. gegužės 18 d. · 10 min skaityti

Hreflang × subdomain × subdirectory: ką pasirinkti

Balandį pribaiginėjau hreflang viename iš projektų — trys lokalės: en, ka, lt. Įrašiau tegus į head per Next.js metadata, rankiniu būdu patikrinau dešimtyje puslapių, atidariau kelis URL inkognito iš skirtingų IP. Viskas rodėsi teisingai: gruzinas mato ka, lietuvis — lt, likęs pasaulis — en. Užbaigiau užduotį, persijungiau prie kitos.

Po dviejų savaičių atidarau Google Search Console. Skiltis International Targeting raudonai dega: „Return tag missing" 60 URL. Hreflang tegai nurodo iš en į ka ir lt, bet atgalines nuorodas dalis puslapių neatiduoda. Google tokius ryšius ignoruoja iš abiejų pusių — vadinasi, pusė mano hreflang jam tiesiog neegzistuoja. Ir visą tą laiką svetainė toliau rodė ka versiją vartotojams iš JAV, kurie ieškojo angliškai.

Daugiakalbis SEO — tai atskira posistemė, su savo failais, savo patikromis, savo URL generavimo logika. Ir sprendimas dėl URL struktūros — kur dėti lokalę: į domeną, subdomeną ar aplanką — priimamas VIENĄ kartą, starte. Pakeisti vėliau — tai 301 nukreipimas, sitemap migracija, naujas registravimas GSC, mažiausiai pusė metų pozicijų atstatymui. Atsukti atgal beveik neįmanoma.

Šiame įraše — trys URL strategijos, kaip jos sąveikauja su hreflang ir ką pasirinkau hiregora.com.


Hreflang — kas tai

Hreflang — tai signalas Google: „štai šis puslapis šia kalba šiai šaliai". Ne daugiau. Ne tiesioginis ranguotės faktorius, ne magija — paprasti metaduomenys, padedantys Google suprasti, kurią iš tavo 3-4 kalbinių versijų rodyti konkrečiam vartotojui.

Formatas standartinis:

<link rel="alternate" hreflang="ru" href="https://example.com/ru/page" />
<link rel="alternate" hreflang="en" href="https://example.com/en/page" />
<link rel="alternate" hreflang="x-default" href="https://example.com/page" />

Dėti galima į tris vietas: į puslapio <head>, į sitemap.xml kaip <xhtml:link> arba į HTTP antraštę Link (ne HTML failams, tokiems kaip PDF). Aš statau į head ir dubliuoju sitemap. Dubliavimas — tai draudimas: jei puslapyje JS sulaužys head renderinimą, sitemap vis tiek atiduos Google teisingą vaizdą.

Apimtis — du lygiai. Arba gryna kalba (en, ru, de) — veikia visoms šalims, kuriose kalbama šia kalba. Arba kalba plius regionas (en-US, en-GB) — tikslus susiejimas su šalimi. Jei turinys vienas visiems angliškai kalbantiems — užteks en. Jei tikrai turi skirtingas kainas JAV ir Britanijai — reikia en-US ir en-GB kaip atskirų puslapių.

x-default — fallback. Kai vartotojas neatitinka nė vienos iš deklaruotų hreflang kombinacijų, Google rodo šią versiją. Paprastai statoma ant pagrindinės lokalės arba ant specialaus language picker puslapio.

Svarbiausia: hreflang neveikia ranguotės tiesiogiai. Jis veikia tai, kurią versiją pamatys vartotojas. Mato savąją — neišeina per 3 sekundes, elgsenos signalai geri, pozicijos netiesiogiai auga. Sulaužytas hreflang = blogas UX = elgsena žemyn = pozicijos žemyn.


Trys URL strategijos

Sprendimas dėl URL priimamas iki hreflang konfigūravimo. Lokalę galima padėti į tris vietas: į patį domeną, į subdomeną, į aplanką. Kiekvienas variantas veikia su hreflang, bet skirtingai sąveikauja su domeno autoritetingumu, indeksavimu ir infrastruktūra.

ccTLD — atskiras domenas šaliai

Struktūra: example.de, example.fr, example.com.br. Kiekviena šalis turi savo domeną su nacionaline galūne.

Pliusai rimti. ccTLD — stipriausias geo signalas. .de — tai Vokietija, be variantų, jokių nustatymų GSC nereikia. Domeno reputacija kaupiasi per šalį: nuorodos iš vokiškų svetainių į example.de boostina vokišką versiją, neišsiteplioja ant kitų. Ir juridiškai patogu: atskirą domeną galima registruoti vietinei kompanijai.

Minusai irgi rimti. Kiekvienas ccTLD — atskiras domenas nuo nulio. Jokio bendro DR. Jei pagrindinis example.com turi DR 60, naujas example.de startuoja su DR 0 ir jį teks įsiūbuoti nuorodomis nuo nulio — 6-12 mėnesių mažiausiai. Plius infrastruktūra: 5 šalys — tai 5 DNS zonos, 5 sertifikatai, 5 atskiros GSC property.

Kada rinktis: enterprise su dideliu kiekiu turinio per šalį, skirtingais teisiniais subjektais, vietinėmis komandomis. Jei Vokietijoje turi savo juridinį asmenį, savo kainas, savo komandą — example.de pateisinamas. Vienas produktas keturiomis kalbomis — perdėta.

Subdomain — subdomenas lokalei

Struktūra: de.example.com, fr.example.com, en.example.com. Lokalė subdomene.

Pliusai. Techninė izoliacija. Kiekvieną subdomeną galima pasodinti ant savo serverio, savo CMS, savo steko. Patogu, kai de veda vokiečių komanda ant WordPress, o en — kita komanda ant Next.js. Ir paskui lengva migruoti į ccTLD: 301 nukreipimas iš de.example.com į example.de, ir dalis autoritetingumo persikels.

Minusai. Google pagal nutylėjimą laiko kiekvieną subdomeną atskira svetaine. DR iš pagrindinio example.com neperduodamas visiškai į de.example.com — perduodamas dalinai, bet ne taip, kaip iš aplanko. Jei 2 metus įdėjai į example.com įsiūbavimą iki DR 50 — de.example.com startuos kažkur ties 20-30. Plius GSC reikalauja atskiros kiekvieno subdomeno registracijos kaip property.

Kada rinktis: kai kalbinių versijų techniniai stekai tikrai skirtingi, skirtingos komandos, skirtingi release ciklai. Arba kai planuojama migruoti į ccTLD vėliau. Arba atskiram produktui ant to paties brendo — pas mane taip su seo.hiregora.com: tai ne kalba, tai atskiras įrankis, sava kodų bazė.

Subdirectory — aplankas lokalei

Struktūra: example.com/de/, example.com/fr/, example.com/en/. Lokalė — tai tiesiog pirmas kelio segmentas.

Pliusai. Vienas domenas, vienas domeno autoritetingumas, viskas sumuojasi. Nuoroda į example.com/de/page boostina visą domeną, įskaitant rusišką ir anglišką versijas. Vienas GSC property, viena analitika, vienas SSL, vienas CDN. Administruoti paprasčiau eilę kartų. Visas turinys dirba ant vieno domeno esybės.

Minusai. Bendra infrastruktūra — tas pats privalumas, kuris ir trūkumas. Padėjo pagrindinė svetainė — atsigulė visa daugiakalbė svetainė. Negalima duoti vietinei komandai prieigos tik prie jų aplanko. Pagrindinis apribojimas: geo taikymas GSC nustatomas tik domain-wide. Negalima pasakyti „visa /de/ taikoma Vokietijai, o /en/ — JAV". Tik per hreflang su regionu — veikia, bet silpniau nei ccTLD signalas.

Kada rinktis: vienas produktas keliomis kalbomis, viena komanda, vienas stekas. Domeno DR vertingas ir nenori jo praskiesti. Infrastruktūrinis paprastumas svarbesnis už techninę izoliaciją. 9 iš 10 SaaS / turinio projektų — tai teisingas pasirinkimas.



Ką pasirinkau aš

hiregora.com — subdirectory: /en, /ka, /lt ir šaknis rusiškai versijai. Sprendžiau taip.

Vienas produktas, vienas savininkas, vienas stekas — Next.js 15. Viena turinio komanda (tai aš). Jokių vietinių juridinių asmenų, jokių skirtingų kainų. Tipinė konfigūracija, kurioje subdirectory laimi visuose frontuose.

Domeno DR vertingas — jį siūbuoju paskutinius pusę metų, ir kiekvienas straipsnis bet kuria kalba dirba ant bendro skaičiaus. Jei būčiau išskaidęs kalbas po subdomenus, kiekvieną tektų siūbuoti atskirai — mažiausiai metus pozicijų atstatymui pagal tas pačias užklausas.

Cross-locale nuorodos. Dažnai susieju rusišką įrašą su anglišku — vartotojas mato „available in English" ir pereina į en versiją. Kai abu viename aplanke, tai tiesiog <Link href="/en/writings/..."> be išėjimų į kitą domeną. Analitika irgi viena, sesija neplyšta perjungiant kalbą.

Subdomain naudoju tik seo.hiregora.com — bet tai jau ne kalba, o atskiras produktas. SEO čekeris gyvena ant savo kodų bazės, su kitu steku. Todėl subdomenas pateisinamas: techninė izoliacija reikalinga, o DR sumuojasi dalinai bendro tėvinio domeno sąskaita.

Jei rytoj nuspręsčiau daryti hiregora.de su vietine vokiečių komanda — tada ccTLD. Bet tai hipotezė 2027-iesiems. Dabar — aplankai.


Realizacija Next.js 15

Next.js 15 su app router visa tai surenkama per dinaminį segmentą app/[lang]/. Aplankų struktūra atspindi URL struktūrą:

app/
  [lang]/
    page.tsx              → /, /en, /ka, /lt
    writings/
      page.tsx            → /writings, /en/writings, ...
      [slug]/
        page.tsx          → /writings/foo, /en/writings/foo, ...

Middleware gaudo užklausas, tikrina pirmą kelio segmentą ir sprendžia, ar yra ten validi lokalė. Jei ne — nustato kalbą pagal Accept-Language, padeda cookie su pasirinkta lokale ir nukreipia. Vienas middleware, vienas tiesos šaltinis, veikia visuose puslapiuose.

generateStaticParams kiekvienam puslapiui grąžina visų lokalių masyvą — [{lang: 'ru'}, {lang: 'en'}, {lang: 'ka'}, {lang: 'lt'}]. Tai duoda statinį generavimą: bilde Next.js surenka 4 kiekvieno puslapio versijas kaip atskirus HTML failus. Jokio SSR runtime metu, jokios apkrovos serveriui — paprasta statika, kurią dalina CDN.

Hreflang tegus generuoja helper buildAlternates()lib/seo.ts. Priima dabartinį kelią be lokalės — grąžina objektą Next.js metadata API:

alternates: {
  canonical: 'https://hiregora.com/writings/foo',
  languages: {
    'ru': 'https://hiregora.com/writings/foo',
    'en': 'https://hiregora.com/en/writings/foo',
    'ka': 'https://hiregora.com/ka/writings/foo',
    'lt': 'https://hiregora.com/lt/writings/foo',
    'x-default': 'https://hiregora.com/writings/foo',
  },
}

Next.js pats stato tai į <head> kaip korektiškus <link rel="alternate">. Jokių rankinių įterpimų, jokių praleistų puslapių — nes tai metadata dalis, kuri generuojama automatiškai kiekvienam route.

Sitemap generuoju atskiru route handler app/sitemap.xml/route.ts. Kiekvienam puslapiui jis atiduoda ne vieną URL, o 4 lokalių bloką su <xhtml:link rel="alternate" hreflang="..."> viduje. Tai draudimas: jei head kažkas nukris, sitemap vis tiek atiduos Google teisingą lokalių ryšių vaizdą.


Dažniausia klaida — return tags

Tai ta pati raudona istorija, su kuria pradėjau įrašą. Ir tai klaida numeris vienas hreflang konfigūracijose, kurią mačiau kas antrame projekte.

Taisyklė paprasta ir nediskutuojama. Jei puslapis A nurodo į puslapį B per hreflang — puslapis B PRIVALO nurodyti atgal į puslapį A. Tai vadinasi reciprocal linking, ir Google jį tikrina griežtai. Jei B nenurodo į A — Google ignoruoja abi nuorodas. Ne vieną, abi.

Pavyzdys. ru puslapyje /writings/foo stovi hreflang tegai: ru → ru/foo, en → en/foo, ka → ka/foo, lt → lt/foo. Atidarau en/foo ir tikrinu — ten irgi turi stovėti visi keturi, įskaitant nuorodą į ru/foo. Jei en/foo nurodyta tik en ir ka — ryšys en↔ru sulaužytas, Google jį atmes, ir ieškant rusiškai mano ru puslapis negaus teisingo signalo.

Iš kur praktikoje atsiranda tokie sulaužyti ryšiai. Dažniausia priežastis — dinaminis generavimas be bendro šaltinio. en puslapiuose naudojama viena funkcija hreflang generavimui, ka — kita (pavyzdžiui, copy-paste, kurį kažkas pamiršo atnaujinti). Jos išsiskiria, ir testai to nepagauna, nes kiekvienas puslapis izoliuotai validus.

Gydoma vienu helper, kuris generuoja hreflang bloką bet kuriai lokalei pagal tą patį įvedimą — kelią be lokalės. Jei įeinantis parametras vienas ir funkcija viena, visos lokalės atiduoda vienodą hreflang nuorodų bloką. Tada reciprocal linking — tai ne taisyklė, kurią reikia atsiminti, o architektūros pasekmė.

Tikrinti — per GSC International Targeting, kartą per savaitę. Ten matosi: kiek nuorodų Google rado, kiek iš jų sulaužytos, kuriuose URL problema. Be šios ataskaitos apie problemas sužinosi tik tada, kai pozicijos nukris.


x-default — kada reikia

x-default — atskiras hreflang tegas vartotojams, kurie neatitinka nė vienos iš deklaruotų kalbinių kombinacijų. Pavyzdžiui, turi en, ru, ka, lt. Užeina vartotojas iš Japonijos su japonų kalba naršyklėje. Google žiūri: en — yra, ru — nėra, ka — nėra, lt — nėra. Japonų pas tave nėra. Ir čia suveikia x-default: Google rodo versiją, pažymėtą kaip fallback.

Kur statyti x-default — priklauso nuo produkto. Trys variantai:

  1. Ant pagrindinės lokalės. Jei 70% srauto kalba rusiškai — x-default → / (rusiška versija). Japonas pamatys rusišką. Ne idealu, bet jis bent jau pateks ant veikiančio puslapio, o ne ant 404 ar nukreipimų ciklo.

  2. Ant angliškos versijos. Jei produktas tarptautinis — x-default → /en/. Angliškas puslapis dirba kaip universalus fallback, nes anglų kalbą supranta maždaug visa interneto populiacija.

  3. Ant language picker. Jei pas tave visos lokalės vienodai stiprios ir nėra akivaizdaus fallback — x-default → /select-language/. Puslapis, kuriame vartotojas pats pasirenka kalbą. Mažiau gražu, bet sąžiningiau nei brukti jam ru, kai jis nemoka rusiškai.

Pas mane — pirmas variantas: x-default → / (rusiška versija). Tai dabar pagrindinė turinio kalba, ir kol kitos lokalės nepasivijo pagal apimtį, fallback ant rusų logiškas. Kai en versija taps pilnavertė — perjungsiu x-default ant jos.

Ir dar: x-default — tai visada atskiras tegas, ne pakeitimas įprastiems hreflang. Tai yra ru puslapis turi turėti ir hreflang="ru" (rusakalbiams), ir hreflang="x-default" (kaip fallback). Du tegai viename puslapyje — tai normalu.


Apibendrinimas

URL struktūros pasirinkimas — tai sprendimas, kurį priimi vieną kartą, starte. ccTLD — enterprise su vietiniais juridiniais asmenimis ir dideliu biudžetu infrastruktūrai. Subdomain — kai stekai ar komandos tikrai skirtingi, arba kaip tarpinis žingsnis į ccTLD. Subdirectory — visam likusiam, ir tai teisingas pasirinkimas 9 iš 10 atvejų.

Hreflang ant bet kurio varianto veikia vienodai, bet reikalauja trijų dalykų: vieno bendro generatoriaus visoms lokalėms (kad reciprocal linking nesulūžtų), dubliavimo sitemap (draudimas nuo JS sutrikimų head) ir x-default kaip fallback. Tikrinti — per GSC International Targeting, kartą per savaitę.

Ir atmink, kad hreflang — tai ne apie ranguotę tiesiogiai. Tai apie tai, kad teisingas žmogus matytų teisingą puslapį. Kai tai veikia — elgsenos signalai geri. Kai sulaužyta — Google rodo ru versiją lietuviui, tas išeina per 3 sekundes, ir visas tavo SEO pamatas trupa iš viršaus. Plačiau apie bendrą faktorių sąrašą — 30 SEO faktorių 2026. Apie sąveiką su canonical — kada reikia canonical URL. Apie indeksavimo derinimą — ką blokuoti robots.txt.

Hreflang × subdomain × subdirectory: ką pasirinkti · hiregora.com