Hreflang × subdomain × subdirectory: რა ავირჩიოთ
აპრილში ერთ-ერთ პროექტზე hreflang-ი დავხვეწე — სამი ლოკალი: en, ka, lt. ტეგები Next.js metadata-ს მეშვეობით head-ში გავწერე, ხელით შევამოწმე ათობით გვერდზე, რამდენიმე URL სხვადასხვა IP-დან ინკოგნიტოში გავხსენი. ყველაფერი სწორად ჩანდა: ქართველი ხედავდა ka-ს, ლიტველი — lt-ს, დანარჩენი მსოფლიო — en-ს. დავხურე დავალება, შემდეგზე გადავერთე.
ორი კვირის შემდეგ ვხსნი Google Search Console-ს. International Targeting-ის ნაწილი წითლად ანათებს: "Return tag missing" 60 URL-ზე. Hreflang-ტეგები en-დან ka-ზე და lt-ზე მიუთითებენ, მაგრამ უკუ-ლინკებს გვერდების ნაწილი არ აძლევს. Google ასეთ კავშირებს ორივე მხრიდან უგულებელყოფს — ანუ ჩემი hreflang-ის ნახევარი მისთვის უბრალოდ არ არსებობს. და მთელი ეს დრო საიტი აშშ-ის მომხმარებლებს, რომლებიც ინგლისურად ეძებდნენ, ka-ვერსიას უჩვენებდა.
მრავალენოვანი SEO ცალკე ქვესისტემაა, საკუთარი ფაილებით, საკუთარი შემოწმებებით, საკუთარი URL-გენერაციის ლოგიკით. და გადაწყვეტილება URL-ის სტრუქტურის შესახებ — სად მოვათავსოთ ლოკალი, დომენში, ქვედომენში თუ ფოლდერში — ერთხელ მიიღება, საწყის ეტაპზე. შემდგომ შეცვლა — ეს 301-რედირექტი, sitemap-ის მიგრაცია, ახალი რეგისტრაცია GSC-ში, მინიმუმ ნახევარი წელი პოზიციების აღსადგენად. უკან თამაშის გადათამაშება თითქმის შეუძლებელია.
ამ პოსტში — სამი URL-სტრატეგია, როგორ ურთიერთქმედებენ ისინი hreflang-თან და რა ავირჩიე hiregora.com-სთვის.
Hreflang — რა არის ეს
Hreflang არის სიგნალი Google-სთვის: "აი ეს გვერდი ამ ენაზე ამ ქვეყნისთვისაა". არაფერი მეტი. არ არის რანჟირების ფაქტორი პირდაპირ, არ არის მაგია — უბრალოდ მეტამონაცემები, რომლებიც Google-ს ეხმარება გაიგოს, თქვენი 3-4 ენოვანი ვერსიიდან რომელი უჩვენოს კონკრეტულ მომხმარებელს.
ფორმატი სტანდარტულია:
<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" />
დასადებად სამი ადგილია: გვერდის <head>-ში, sitemap.xml-ში როგორც <xhtml:link>, ან HTTP-სათაურში Link (არა-HTML ფაილებისთვის, როგორიცაა PDF). მე head-ში ვდებ და sitemap-ში ვადუბლირებ. დუბლირება დაზღვევაა: თუ გვერდზე JS-მა head-ის რენდერინგი დაამტვრია, sitemap მაინც ასწორებს სურათს Google-სთვის.
დაფარვა — ორ დონეზე. ან წმინდა ენა (en, ru, de) — მუშაობს ყველა ქვეყნისთვის, სადაც ამ ენაზე ლაპარაკობენ. ან ენა პლუს რეგიონი (en-US, en-GB) — ზუსტი მიბმა ქვეყანასთან. თუ კონტენტი ერთია ყველა ინგლისურენოვანისთვის — საკმარისია en. თუ რეალურად სხვადასხვა ფასები გაქვს აშშ-სა და ბრიტანეთისთვის — გჭირდება en-US და en-GB ცალკე გვერდებად.
x-default — fallback-ი. როცა მომხმარებელი არცერთ გამოცხადებულ hreflang-კომბინაციას არ ერგება, Google ამ ვერსიას უჩვენებს. ჩვეულებრივ ისმება ძირითად ლოკალზე ან სპეციალურ language picker გვერდზე.
მთავარია: hreflang-ი რანჟირებაზე პირდაპირ არ მოქმედებს. ის გავლენას ახდენს იმაზე, რომელ ვერსიას დაინახავს მომხმარებელი. ხედავს თავის ვერსიას — 3 წამში არ მიდის, ქცევითი სიგნალები კარგია, პოზიციები არაპირდაპირ იზრდება. დამახინჯებული hreflang = ცუდი UX = ცუდი ქცევითი სიგნალები = პოზიციები ქვემოთ.
URL-ის სამი სტრატეგია
URL-ის შესახებ გადაწყვეტილება hreflang-ის კონფიგურაციამდე მიიღება. ლოკალის სამ ადგილზე დადება შეიძლება: თვით დომენში, ქვედომენში, ფოლდერში. თითოეული ვარიანტი hreflang-თან მუშაობს, მაგრამ სხვადასხვაგვარად ურთიერთქმედებს დომენის ავტორიტეტთან, ინდექსაციასთან და ინფრასტრუქტურასთან.
ccTLD — ცალკე დომენი ქვეყანაზე
სტრუქტურა: example.de, example.fr, example.com.br. თითოეულ ქვეყანას აქვს თავისი დომენი ეროვნული დაბოლოებით.
დადებითი მხარეები სერიოზულია. ccTLD ყველაზე ძლიერი გეო-სიგნალია. .de ეს გერმანიაა, ვარიანტების გარეშე, GSC-ში არანაირი კონფიგურაცია არ სჭირდება. დომენის რეპუტაცია გროვდება per-country: ბმულები გერმანული საიტებიდან example.de-ზე გერმანულ ვერსიას აძლიერებენ, დანარჩენებზე არ ნაწილდებიან. და იურიდიულად მოსახერხებელია: ცალკე დომენი შეიძლება ადგილობრივ კომპანიაზე დარეგისტრირდეს.
უარყოფითი მხარეებიც სერიოზულია. თითოეული ccTLD ცალკე დომენია ნულიდან. არანაირი საერთო DR. თუ ძირითად example.com-ს აქვს DR 60, ახალი example.de DR 0-დან იწყებს და მის გადარხევას ბმულებით ნულიდან მოგიწევთ — მინიმუმ 6-12 თვე. პლუს ინფრა: 5 ქვეყანა ეს 5 DNS-ზონაა, 5 სერტიფიკატი, 5 ცალკე GSC-property.
როდის ავირჩიოთ: enterprise დიდი per-country კონტენტით, სხვადასხვა იურიდიული პირებით, ლოკალური გუნდებით. თუ გერმანიაში გყავს თავისი იურიდიული პირი, თავისი ფასები, თავისი გუნდი — example.de გამართლებულია. ერთი პროდუქტი ოთხ ენაზე — ზედმეტობაა.
Subdomain — ქვედომენი ლოკალზე
სტრუქტურა: de.example.com, fr.example.com, en.example.com. ლოკალი ქვედომენშია.
დადებითი მხარეები. ტექნიკური იზოლაცია. თითოეული ქვედომენი თავის სერვერზე, თავის CMS-ზე, თავის სტეკზე შეიძლება დაჯდეს. მოსახერხებელია, როცა de-ს გერმანული გუნდი ეწევა WordPress-ზე, ხოლო en-ს — სხვა გუნდი Next.js-ზე. და შემდეგ ccTLD-ში მიგრაცია იოლია: 301-რედირექტი de.example.com-დან example.de-ზე და ავტორიტეტის ნაწილი გადავა.
უარყოფითი მხარეები. Google-ი default-ად თითოეულ ქვედომენს ცალკე საიტად მიიჩნევს. DR ძირითადი example.com-დან de.example.com-ზე სრულად არ გადადის — გადადის ნაწილობრივ, მაგრამ არა ისე, როგორც ფოლდერიდან. თუ 2 წელი ჩადევი example.com-ის გადარხევაში DR 50-მდე — de.example.com 20-30-ის რაიონში დაიწყებს. პლუს GSC მოითხოვს თითოეული ქვედომენის ცალკე რეგისტრაციას, როგორც property-ის.
როდის ავირჩიოთ: როცა ენობრივ ვერსიებს რეალურად სხვადასხვა ტექნიკური სტეკი, სხვადასხვა გუნდი, სხვადასხვა გამოშვების ციკლი აქვს. ან როცა მომავალში ccTLD-ში მიგრაცია იგეგმება. ან ცალკე პროდუქტისთვის იმავე ბრენდის ქვეშ — ჩემთან ასეა seo.hiregora.com-თან: ეს არ არის ენა, ეს ცალკე ხელსაწყოა, თავისი კოდბაზით.
Subdirectory — ფოლდერი ლოკალზე
სტრუქტურა: example.com/de/, example.com/fr/, example.com/en/. ლოკალი უბრალოდ პათის პირველი სეგმენტია.
დადებითი მხარეები. ერთი დომენი, ერთი დომენის ავტორიტეტი, ყველაფერი ჯამდება. ბმული example.com/de/page-ზე მთელ დომენს აძლიერებს, რუსული და ინგლისური ვერსიების ჩათვლით. ერთი GSC-property, ერთი ანალიტიკა, ერთი SSL, ერთი CDN. ადმინისტრირება ბევრად იოლია. მთელი კონტენტი ერთ დომენ-ერთეულზე მუშაობს.
უარყოფითი მხარეები. საერთო ინფრა — იგივე უპირატესობა და ნაკლი ერთდროულად. ძირითადი საიტი ჩამოვარდა — მთელი მრავალენოვანი საიტი ჩამოვარდა. ლოკალურ გუნდს მისი ფოლდერისთვის ცალკე წვდომის მიცემა შეუძლებელია. მთავარი შეზღუდვა: გეო-ტარგეტინგი GSC-ში მხოლოდ domain-wide კონფიგურირდება. შეუძლებელია გითხრა "მთელი /de/ გერმანიაზე ტარგეტდება, ხოლო /en/ — აშშ-ზე". მხოლოდ რეგიონიანი hreflang-ით — მუშაობს, მაგრამ უფრო სუსტად, ვიდრე ccTLD-სიგნალი.
როდის ავირჩიოთ: ერთიანი პროდუქტი რამდენიმე ენაზე, ერთიანი გუნდი, ერთიანი სტეკი. დომენის DR ფასეულია და მისი გადანაწილება არ გინდა. ინფრასტრუქტურული სიმარტივე ტექნიკურ იზოლაციაზე უფრო მნიშვნელოვანია. 10-დან 9 SaaS / კონტენტ-პროექტში ეს სწორი არჩევანია.
რა ავირჩიე მე
hiregora.com-სთვის — subdirectory: /en, /ka, /lt და ფესვი რუსული ვერსიისთვის. ასე გადავწყვიტე.
ერთი პროდუქტი, ერთი მფლობელი, ერთი სტეკი — Next.js 15. ერთი კონტენტ-გუნდი (ეს მე ვარ). არანაირი ლოკალური იურიდიული პირი, არანაირი სხვადასხვა ფასი. ტიპიური კონფიგურაცია, სადაც subdirectory ყველა ფრონტზე იგებს.
დომენის DR ფასეულია — ბოლო ნახევარი წელია ვარხევ მას და ნებისმიერ ენაზე თითოეული სტატია საერთო ციფრზე მუშაობს. რომ გამეთიშა ენები ქვედომენებში, თითოეული ცალკე უნდა გადამერხია — მინიმუმ წელი იმავე მოთხოვნებზე პოზიციების აღსადგენად.
Cross-locale ბმულები. ხშირად ვაკავშირებ რუსულ პოსტს ინგლისურთან — მომხმარებელი ხედავს "available in English" და გადადის en-ვერსიაზე. როცა ორივე ერთ ფოლდერშია, ეს უბრალოდ <Link href="/en/writings/..."> სხვა დომენზე გასვლის გარეშე. ანალიტიკაც ერთია, სესია ენის გადართვისას არ წყდება.
Subdomain-ს მე მხოლოდ seo.hiregora.com-ისთვის ვიყენებ — მაგრამ ეს უკვე არ არის ენა, ეს ცალკე პროდუქტია. SEO-ჩეკერი თავის კოდბაზაზე ცხოვრობს, სხვა სტეკით. ამიტომ ქვედომენი გამართლებულია: ტექნიკური იზოლაცია საჭიროა, ხოლო DR ნაწილობრივ ჯამდება საერთო მშობელი დომენის ხარჯზე.
რომ ხვალ გადავწყვიტო hiregora.de-ის გაკეთება ლოკალური გერმანული გუნდით — მაშინ ccTLD. მაგრამ ეს ჰიპოთეზაა 2027-ისთვის. ახლა — ფოლდერები.
იმპლემენტაცია Next.js 15-ში
Next.js 15-ში app router-ით ეს ყველაფერი დინამიური სეგმენტით app/[lang]/ იწყობა. ფოლდერების სტრუქტურა URL-ის სტრუქტურას ირეკლავს:
app/
[lang]/
page.tsx → /, /en, /ka, /lt
writings/
page.tsx → /writings, /en/writings, ...
[slug]/
page.tsx → /writings/foo, /en/writings/foo, ...
Middleware იჭერს მოთხოვნებს, ამოწმებს პათის პირველ სეგმენტს და წყვეტს, არის თუ არა იქ ვალიდური ლოკალი. თუ არ არის — ენას Accept-Language-ით ადგენს, არჩეული ლოკალით cookie-ს ბაჯნებს და რედირექტს აკეთებს. ერთი middleware, ერთი ჭეშმარიტების წყარო, ყველა გვერდზე მუშაობს.
generateStaticParams თითოეული გვერდისთვის აბრუნებს ყველა ლოკალის მასივს — [{lang: 'ru'}, {lang: 'en'}, {lang: 'ka'}, {lang: 'lt'}]. ეს იძლევა სტატიკურ გენერაციას: ბილდზე Next.js თითოეული გვერდის 4 ვერსიას აწყობს ცალკე HTML-ფაილებად. არანაირი SSR runtime-ზე, არანაირი დატვირთვა სერვერზე — უბრალოდ სტატიკა, რომელსაც CDN ანაწილებს.
Hreflang-ტეგებს გენერირებს helper buildAlternates() lib/seo.ts-დან. იღებს მიმდინარე პათს ლოკალის გარეშე — აბრუნებს ობიექტს 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 თვითონ ბაჯნებს ამას <head>-ში სწორ <link rel="alternate">-ად. არანაირი ხელით ჩასმა, არანაირი გამოტოვებული გვერდი — რადგან ეს metadata-ს ნაწილია, რომელიც თითოეული რაუტისთვის ავტომატურად გენერირდება.
Sitemap-ს ცალკე route handler-ით ვაგენერირებ app/sitemap.xml/route.ts-ში. თითოეული გვერდისთვის ის ერთ URL-ს კი არ აძლევს, არამედ ბლოკს 4 ლოკალისგან <xhtml:link rel="alternate" hreflang="..."> შიგნით. ეს დაზღვევაა: თუ head-ში რამე ჩამოვარდა, sitemap მაინც ასწორებს Google-სთვის ლოკალებს შორის კავშირების სურათს.
ყველაზე ხშირი შეცდომა — return tags
ეს არის ის წითელი ნივთი, რომლითაც პოსტი დავიწყე. და ეს არის შეცდომა ნომერი ერთი hreflang-ის კონფიგურაციებში, რომელიც ყოველ მეორე პროექტზე მინახავს.
წესი მარტივია და განხილვას არ ექვემდებარება. თუ გვერდი A მიუთითებს გვერდ B-ზე hreflang-ის მეშვეობით — გვერდი B ვალდებულია უკან მიუთითოს გვერდ A-ზე. ამას ჰქვია reciprocal linking, და Google მას მკაცრად ამოწმებს. თუ B არ მიუთითებს A-ზე — Google ორივე ბმულს უგულებელყოფს. არა ერთს, ორივეს.
მაგალითი. ru-გვერდზე /writings/foo დგას hreflang-ტეგები: ru → ru/foo, en → en/foo, ka → ka/foo, lt → lt/foo. ვხსნი en/foo-ს და ვამოწმებ — იქაც ოთხივე უნდა იდგას, ru/foo-ზე ბმულის ჩათვლით. თუ en/foo-ში მითითებულია მხოლოდ en და ka — კავშირი en↔ru გატეხილია, Google მას გადაყრის და რუსულზე ძიებისას ჩემი ru-გვერდი სწორ სიგნალს ვერ მიიღებს.
საიდან ჩნდება პრაქტიკაში ასეთი გატეხილი კავშირები. ყველაზე ხშირი მიზეზი — დინამიური გენერაცია საერთო წყაროს გარეშე. en-გვერდებზე ერთი ფუნქცია გამოიყენება hreflang-ის გენერაციისთვის, ka-ზე — სხვა (მაგალითად, კოპიპასტა, რომელიც ვიღაცამ განახლება დაავიწყდა). ისინი იშლება და ტესტები ამას ვერ იჭერენ, რადგან თითოეული გვერდი ცალკე ვალიდურია.
მკურნალობს ერთი helper-ით, რომელიც hreflang-ბლოკს ნებისმიერი ლოკალისთვის ერთი და იმავე შესასვლელით აგენერირებს — ლოკალის გარეშე პათით. თუ შესასვლელი პარამეტრი ერთია და ფუნქცია ერთია, ყველა ლოკალი ერთსა და იმავე hreflang-ბმულების ბლოკს გასცემს. მაშინ reciprocal linking აღარ არის წესი, რომელიც უნდა გახსოვდეს, არამედ არქიტექტურის შედეგია.
შემოწმება — GSC International Targeting-ის მეშვეობით, კვირაში ერთხელ. იქ ჩანს: რამდენი ბმული იპოვა Google-მა, რამდენი მათგანი არის გატეხილი, რომელ URL-ებზეა პრობლემა. ამ ანგარიშის გარეშე პრობლემებზე მხოლოდ მაშინ შეიტყობ, როცა პოზიციები დაიწევს.
x-default — როდის გჭირდება
x-default — ცალკე hreflang-ტეგი იმ მომხმარებლებისთვის, რომლებიც არცერთ გამოცხადებულ ენობრივ კომბინაციას არ ერგებიან. მაგალითად, გაქვს en, ru, ka, lt. შემოდის მომხმარებელი იაპონიიდან ბრაუზერში იაპონურით. Google უყურებს: en — არის, ru — არ არის, ka — არ არის, lt — არ არის. იაპონური არ გაქვს. და აქ მუშავდება x-default: Google უჩვენებს ვერსიას, რომელიც fallback-ად აქვს მონიშნული.
სად დავდოთ x-default — დამოკიდებულია პროდუქტზე. სამი ვარიანტი:
-
ძირითად ლოკალზე. თუ ტრაფიკის 70% რუსულად ლაპარაკობს —
x-default → /(რუსული ვერსია). იაპონელი დაინახავს რუსულს. იდეალური არ არის, მაგრამ მინიმუმ მუშა გვერდზე მოხვდება და არა 404-ზე ან რედირექტ-მარყუჟზე. -
ინგლისურ ვერსიაზე. თუ პროდუქტი საერთაშორისოა —
x-default → /en/. ინგლისურენოვანი გვერდი მუშაობს როგორც უნივერსალური fallback, რადგან ინგლისურს დაახლოებით მთელი ინტერნეტის მოსახლეობა იგებს. -
language picker-ზე. თუ ყველა ლოკალი თანაბრად ძლიერი გაქვს და აშკარა fallback არ არსებობს —
x-default → /select-language/. გვერდი, სადაც მომხმარებელი თვითონ ირჩევს ენას. ნაკლებად ლამაზია, მაგრამ უფრო პატიოსანი, ვიდრე მისთვის ru-ის თავს მოხვევა, როცა რუსული არ იცის.
ჩემთან — ვარიანტი 1: x-default → / (რუსული ვერსია). ეს ახლა კონტენტის ძირითადი ენაა და სანამ დანარჩენი ლოკალები მოცულობით არ მოეწევიან, fallback რუსულზე ლოგიკურია. როცა en-ვერსია სრულფასოვანი გახდება — x-default-ს მასზე გადავრთავ.
და კიდევ: x-default ყოველთვის ცალკე ტეგია, არა ჩვეულებრივი hreflang-ის ჩანაცვლება. ანუ ru-გვერდს უნდა ჰქონდეს hreflang="ru"-ც (რუსულენოვანებისთვის) და hreflang="x-default"-იც (როგორც fallback). ორი ტეგი ერთ გვერდზე — ეს ნორმალურია.
შეჯამება
URL-სტრუქტურის არჩევანი — ეს გადაწყვეტილებაა, რომელსაც ერთხელ იღებ, საწყის ეტაპზე. ccTLD — enterprise-ისთვის ლოკალური იურიდიული პირებით და დიდი ბიუჯეტით ინფრასტრუქტურაზე. Subdomain — როცა სტეკები ან გუნდები რეალურად სხვადასხვაა, ან როგორც შუალედური ნაბიჯი ccTLD-ისკენ. Subdirectory — დანარჩენი ყველაფრისთვის და ეს სწორი არჩევანია 10 შემთხვევიდან 9-ში.
Hreflang ნებისმიერი ვარიანტის თავზე ერთნაირად მუშაობს, მაგრამ მოითხოვს სამ რამეს: ერთ საერთო გენერატორს ყველა ლოკალისთვის (რათა reciprocal linking არ ტყდებოდეს), sitemap-ში დუბლირებას (head-ში JS-ჩამოვარდნებისგან დაზღვევა) და x-default-ს როგორც fallback. შემოწმება — GSC International Targeting-ის მეშვეობით, კვირაში ერთხელ.
და გახსოვდეს, რომ hreflang არ არის რანჟირების შესახებ პირდაპირ. ეს არის იმაზე, რომ სწორმა ადამიანმა სწორი გვერდი დაინახოს. როცა ეს მუშაობს — ქცევითი სიგნალები კარგია. როცა გატეხილია — Google ლიტველს ru-ვერსიას უჩვენებს, ის 3 წამში მიდის და მთელი შენი SEO-ფუნდამენტი ზემოდან იშლება. დაწვრილებით ფაქტორების ზოგად სიაზე — SEO 2026-ის 30 ფაქტორი. canonical-თან ურთიერთქმედებაზე — როდის გჭირდება canonical URL. ინდექსაციის კონფიგურაციაზე — რა დავბლოკოთ robots.txt-ში.