Blog/Webfejlesztés

Többnyelvű SEO Nuxt i18n-nel: URL-stratégia, hreflang, canonical és sitemap

Így állítsd be a Nuxt i18n modult, hogy a keresők minden nyelvet indexeljenek: útvonal-stratégia, useLocaleHead, hreflang, részleges fordítás, sitemap.

··8 perc olvasás

  • Nuxt
  • i18n
  • SEO
  • hreflang
  • Multilingual
Egy oldal három nyelvi változata, angol, német és magyar, hreflang nyilakkal összekötve és egy x-default tartalékkal.

A lényeg röviden

  • Minden nyelvnek legyen saját URL-je; a Google ezt ajánlja a sütik vagy a böngészőbeállítások helyett, a Googlebot pedig nem küld Accept-Language fejlécet.
  • A @nuxtjs/i18n-ben minden locale-nál add meg a language értéket és az éles baseUrl-t, a useLocaleHead pedig lang, hreflang, og:locale és canonical tageket ad.
  • A hreflang-készlet minden változaton azonos legyen, tartalmazza magát az oldalt, teljes URL-eket és érvényes ISO-nyelvkódokat használjon.
  • A kevesebb nyelven létező oldalaknál csak a valódi fordításokat sorold fel, a lefordítatlan másolatok canonicalja pedig az eredetire mutasson.
  • Ne irányíts át a böngésző nyelve alapján; adj valódi linkekből álló nyelvválasztót, és a hreflang vigye a keresőket a megfelelő változatra.

Cikk meghallgatása

0:000:00

Egy többnyelvű Nuxt-oldal a látogatóknak tökéletesnek tűnhet, a keresőmotorok számára mégis félig láthatatlan lehet: a német oldalak nincsenek indexelve, a Google a magyar keresőknek az angol oldalt mutatja, vagy két nyelvi változat versenyez ugyanarra a keresésre. Az ok szinte mindig az oldal headjében van: hiányzó vagy ellentmondásos hreflang linkek, rossz nyelvre mutató canonical, vagy olyan URL-stratégia, amely sütik mögé rejti a nyelveket.

A cikk egy olyan @nuxtjs/i18n beállítást mutat be, amelyet a keresők értenek, ezt az oldalt használva példaként: statikusan generálódik angolul, németül és magyarul. Szó lesz az útvonal-stratégiáról, a useLocaleHead-ről, a Google dokumentációjának hreflang-szabályairól, a csak egyes nyelveken létező oldalakról, a sitemapekről, és arról, miért árt az automatikus nyelvi átirányítás.

Milyen URL-stratégiát használjon egy többnyelvű Nuxt-oldal?

A Google többnyelvű oldalakra vonatkozó ajánlása egyértelmű: minden nyelvi változatnak legyen saját URL-je, sütik vagy böngészőbeállítások helyett. Ha egy URL-en a nyelv szerint változik a tartalom, a Google nem biztos, hogy minden változatot megtalál, mert a Googlebot általában az USA-ból, Accept-Language fejléc nélkül kér le oldalakat. A Nuxt i18n négy útvonal-stratégiát kínál:

StratégiaURL-ekSEO-szempontból
prefix_except_default/blog, /de/blog, /hu/blogJó alapértelmezés; a fő nyelv tiszta URL-eket kap
prefix/en/blog, /de/blog, /hu/blogJó; szimmetrikus, a gyökérhez átirányítás vagy nyelvválasztó kell
prefix_and_default/blog and /en/blog, /de/blogMűködik, de duplikátumokat hoz létre; a canonical az előtag nélküli URL-re mutat
no_prefix/blog for allNyilvános tartalomnál kerüld; egy URL csak egy nyelven indexelhető

Az egy domainen belüli alkönyvtárak üzemeltetése is a legolcsóbb. Külön országos domain akkor indokolt, ha az üzlet országonként valóban különálló; egy második nyelv kiszolgálásához nem kell.

Hogyan konfiguráld a @nuxtjs/i18n modult SEO-hoz?

Két beállítás kell ahhoz, hogy az SEO-funkciók helyes kimenetet adjanak: minden locale-nak kell egy language címke, ebből lesz a hreflang értéke és a <html lang>, a baseUrl pedig az éles origin legyen, mert a hreflang és a canonical URL-eknek teljesnek kell lenniük. Ennek az oldalnak a konfigurációja:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    baseUrl: 'https://balazscsorba.com',
    strategy: 'prefix_except_default',
    defaultLocale: 'en',
    locales: [
      { code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
      { code: 'de', language: 'de-AT', name: 'Deutsch', file: 'de.json' },
      { code: 'hu', language: 'hu-HU', name: 'Magyar', file: 'hu.json' },
    ],
    // No automatic redirect by browser language; the switcher links to every version.
    detectBrowserLanguage: false,
  },
})

Mit tesz a useLocaleHead az oldal headjébe?

A useLocaleHead() olyan head-metaadatot ad vissza, amelyet a useHead-nek adsz át: a <html> lang attribútumát, minden beállított locale-hoz egy hreflang-os alternate linket, og:locale és og:locale:alternate metatageket, valamint egy canonical linket. Hívd meg egyszer az app.vue-ban, így minden oldal megkapja.

<!-- app.vue -->
<script setup lang="ts">
const i18nHead = useLocaleHead({ dir: true, seo: true })

useHead(() => ({
  htmlAttrs: { lang: i18nHead.value.htmlAttrs?.lang },
  link: [...(i18nHead.value.link ?? [])],
  meta: [...(i18nHead.value.meta ?? [])],
}))
</script>

Egy /de/blog/post oldalon a renderelt head ekkor tartalmazza a <html lang="de-AT"> elemet, egy canonical linket a német URL-re, és alternate linkeket az en-US, de-AT és hu-HU értékekre. A modul nyelvcsoportonként egy gyűjtő linket is hozzáad, például hreflang="en", valamint egy x-default-ot. Oldaltípusonként egyszer nézd meg a generált HTML-t; könnyű azt hinni, hogy a head rendben van, csak mert a modul telepítve van.

Mely hreflang-szabályokat rontják el gyakran?

Egy hreflang-csoportEgy oldal három nyelvi változata kölcsönösen hivatkozik egymásra, mindegyik önmagát is felsorolja, és egy x-default link a nem illeszkedő nyelvekhez az angol változatra mutat./blog/posthreflang="en"/de/blog/posthreflang="de"/hu/blog/posthreflang="hu"x-defaultMinden változat mind a hármat és az x-defaultot felsorolja, önmagát is.
A hreflang csak zárt készletként működik: ha a német oldal nem hivatkozik vissza, a Google figyelmen kívül hagyhatja a párt.

A Google hreflang-dokumentációja rövid, és az auditokban talált hibák többsége ezek közül sért meg egyet:

  • Az oldal önmagát is sorolja fel. Minden változat változatonként egy linket tartalmaz, önmagát is, és a készlet minden változaton azonos.
  • Teljes URL-eket használj, protokollal és hoszttal. A relatív href többnyire hiányzó baseUrl következménye.
  • Érvényes kódokat használj: ISO 639-1 szerinti nyelvet, opcionálisan ISO 3166-1 szerinti régióval (de-AT). A régió önmagában nem működik, az olyan kódok pedig, mint az es-419, nem támogatottak.
  • Adj hozzá x-default-ot azoknak a felhasználóknak, akiknek a nyelve egyik változathoz sem illik; ez általában az alapértelmezett nyelvű oldal.
  • A canonical minden nyelvi változaton önmagára mutasson. Egy német oldal, amelynek canonicalja az angolra mutat, azt üzeni a Google-nek, hogy hagyja el a németet.

Még egy fontos tény: a Google szerint sem a hreflangot, sem a lang attribútumot nem használja az oldal nyelvének felismerésére, hanem a tartalomból ismeri fel. Egy lefordított sablon lefordítatlan szövegtörzzsel továbbra is angol oldal.

Mi a helyzet a csak egyes nyelveken létező oldalakkal?

A prefix_except_default stratégiával a Nuxt minden locale-hoz útvonalat generál, így egy csak angol cikk is megjelenik a /de/blog/post címen, német navigációval és angol szöveggel. Ha nem nyúlsz hozzá, a useLocaleHead német fordításként hirdeti, a német másolat canonicalja pedig önmagára mutat. Ez az oldal mindkettőt az app.vue-ban javítja: a kevesebb nyelven létező oldal bejelenti a nyelveit, a hreflang ekkor csak ezeket sorolja fel, a lefordítatlan másolatok canonicalja pedig az angol eredetire mutat.

// app.vue (excerpt): pageLocales is set by pages that exist in fewer languages
const headLinks = computed(() => {
  const links = i18nHead.value.link ?? []
  const only = pageLocales.value
  if (!only || only.path !== route.path) return links
  return links
    .filter((l) => l.rel !== 'alternate' || !l.hreflang || l.hreflang === 'x-default'
      || only.locales.some((c) => l.hreflang.startsWith(c)))
    .map((l) => (l.rel === 'canonical' && !only.translated
      ? { ...l, href: `${siteUrl}${switchLocalePath('en')}` }
      : l))
})

A másik lehetőség, hogy a lefordítatlan útvonalakat egyáltalán nem generálod, hanem 404-et vagy az eredetire mutató átirányítást adsz. Ez a keresőknek tisztább, de rosszabb annak a látogatónak, aki németre állította a felületet, és úgy is akarja hagyni. Mindkettő működik, amíg a head igazat mond arról, mely fordítások léteznek.

A hreflang a sitemapbe is kerüljön?

A Google három helyen fogadja el a hreflangot – HTML-tagekben, HTTP-fejlécekben és a sitemapben –, egyenértékűnek tekinti őket, és szerinte több módszer együtt nem ad előnyt a keresésben, csak a karbantartást nehezíti. A HTTP-fejléc főleg nem HTML-fájloknál, például PDF-eknél hasznos. Ez az oldal head-tageket és sitemap-bejegyzéseket is kiad, mert mindkettő buildkor ugyanabból a bejegyzésadatból készül, így nem tudnak elcsúszni egymástól. Ha nálad különböző kódutakból jönnek, válassz egyet.

<url>
  <loc>https://balazscsorba.com/de/blog/agent-loop-explained</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://balazscsorba.com/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="de" href="https://balazscsorba.com/de/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="hu" href="https://balazscsorba.com/hu/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="x-default" href="https://balazscsorba.com/blog/agent-loop-explained"/>
</url>

Miért ne irányítsd át a látogatókat a böngésző nyelve szerint?

A Google nem javasolja, hogy a feltételezett nyelvük alapján irányítsd át a felhasználókat egy nyelvi változatra, mert ez megakadályozhatja, hogy a felhasználók és a keresők minden változatot elérjenek. A Nuxt i18n detectBrowserLanguage beállítása az első látogatáskor átirányíthat; nyilvános oldalon kikapcsolom, és helyette két dologra építek: a hreflangra, amely a találati oldalról közvetlenül a megfelelő változatra viszi a keresőket, és egy valódi linkekből álló nyelvválasztóra, amelyet a crawlerek is követhetnek.

Mit kell még lefordítani a többnyelvű SEO-hoz?

  • Címek és meta-leírások locale-onként. Ezeket olvassák a keresők a találatok között; egy angol című német oldal kattintásokat veszít.
  • Strukturált adat inLanguage értékkel, amely illik az oldalhoz, hogy az AI-keresés és a bővített találatok a megfelelő változatot idézzék. Erről az oldal GEO-auditjában írtam.
  • Képek alt szövege és az Open Graph-képek lokalizált szöveggel, ha a kép szavakat tartalmaz.
  • A belső linkek maradjanak a nyelven belül. Mindenhol localePath()-ot használj, hogy egy német cikk német oldalakra hivatkozzon, és ne terelje vissza a látogatókat az angolra.

A teljesítmény minden nyelven ugyanannyira számít; ugyanazok a szabályok érvényesek, mint a Core Web Vitals Nuxthoz cikkben, a locale-onként lustán betöltött üzenetfájlok pedig kicsiben tartják a bundle-t.

Hogyan ellenőrizd a beállítást?

Kérd le oldaltípusonként és nyelvenként egy oldal generált HTML-jét, és hasonlítsd össze a linkkészleteket; egy rövid szkript elkapja a szokásos hibákat, mielőtt a Search Console hetekkel később tenné.

// check-hreflang.mjs: every version must list the same set, including itself
const pages = ['/blog/agent-loop-explained', '/de/blog/agent-loop-explained', '/hu/blog/agent-loop-explained']
const base = 'https://balazscsorba.com'
const sets = await Promise.all(pages.map(async (p) => {
  const html = await (await fetch(base + p)).text()
  const attr = (tag, name) => tag.match(new RegExp(` ${name}="([^"]+)"`))?.[1]
  const tags = html.match(/<link[^>]*>/g) ?? []
  const links = tags.filter((t) => attr(t, 'rel') === 'alternate' && attr(t, 'hreflang'))
    .map((t) => [attr(t, 'hreflang'), attr(t, 'href')])
  const canonical = attr(tags.find((t) => attr(t, 'rel') === 'canonical') ?? '', 'href')
  if (canonical !== base + p) console.warn(`${p}: canonical is ${canonical}`)
  if (!links.some(([, href]) => href === base + p)) console.warn(`${p}: does not list itself`)
  return links.map(([lang, href]) => `${lang} ${href}`).sort().join('\n')
}))
console.log(new Set(sets).size === 1 ? 'hreflang sets match' : 'hreflang sets differ')

Források

  1. Nuxt i18n docs: SEO (useLocaleHead)
  2. Nuxt i18n docs: Routing strategies
  3. Google Search Central: Tell Google about localized versions of your page
  4. Google Search Central: Managing multi-regional and multilingual sites
  5. Google Search Central: How to specify a canonical URL

Gyakori kérdések

Melyik Nuxt i18n útvonal-stratégia a legjobb SEO-hoz?

A prefix_except_default és a prefix is saját URL-t ad minden nyelvnek, ahogy a Google ajánlja. A prefix_except_default az alapértelmezett nyelvet előtag nélkül hagyja. Nyilvános tartalomnál kerüld a no_prefix stratégiát, mert minden nyelv ugyanazon az URL-en osztozik, és a keresők csak az egyiket tudják indexelni.

A useLocaleHead automatikusan előállítja a hreflangot?

Igen. Visszaadja a html elem lang attribútumát, minden beállított locale-hoz egy hreflang-os alternate linket, az og:locale tageket és egy canonical linket. Ehhez minden locale-nak kell egy language érték, a modulnak pedig baseUrl, hogy az URL-ek teljesek legyenek.

Kell x-default?

Ajánlott. A Google az x-default változatot azoknak a felhasználóknak mutatja, akiknek a nyelve egyik változathoz sem illik. A legtöbb oldalon az alapértelmezett nyelvű változatra vagy egy nyelvválasztó oldalra mutat.

A hreflang a HTML headbe vagy a sitemapbe kerüljön?

Bármelyikbe. A Google egyenértékűnek tekinti a HTML-tageket, a HTTP-fejléceket és a sitemap-bejegyzéseket, és szerinte több módszer együtt nem ad pluszt. Azt válaszd, amelyiket helyesen tudod tartani; egy adatforrásból generált statikus oldalnál mindkettő jöhet ugyanabból a kódból.

Használja a Google a hreflangot az oldal nyelvének felismerésére?

Nem. A Google szerint a nyelvet saját algoritmusaival a tartalomból ismeri fel; a hreflang csak azt mondja meg, mely URL-ek ugyanannak az oldalnak a lokalizált változatai. Egy angol szöveggel teli „német” oldal a Google számára attól még angol.

Pont erre van szükséged?

Írj a projektedről vagy a pozícióról – szívesen hallok felőled.