- A #/szolgaltatasok típusú cím nem önálló URL, mert a fragmentet a böngésző nem küldi el a szervernek, és a Google sem kezeli külön oldalként.
- React, Vue és Angular alatt a hash módot History API alapú routerre kell cserélni, a régi hash címeket pedig kliensoldali szkripttel lehet átirányítani.
- A szervernek minden létező útvonalra 200-at, minden nem létezőre 404-et kell adnia, a mindenre index.html-t visszaadó beállítás soft 404-hez vezet.
- A Google csak a valós URL-re mutató href-fel rendelkező a elemeket követi megbízhatóan, az onclick alapú navigációt nem.
- Az AI-crawlerek egy része a független mérések szerint nem futtat JavaScriptet, ezért az SSR vagy az előre generált HTML a biztonságosabb választás.
Egy egyoldalas alkalmazásként (SPA) épített weboldal a böngészőben gyors és gördülékeny, a keresők és az AI-válaszmotorok szemében viszont könnyen egyetlen oldallá zsugorodik. A leggyakoribb ok egy apró karakter, a kettőskereszt. Ha a szolgáltatásaid a pelda.hu/#/szolgaltatasok, az ajánlatkérő űrlapod a pelda.hu/#/kapcsolat címen él, akkor a Google jó eséllyel mindkettőt ugyanannak a pelda.hu/ oldalnak látja. Ebben a cikkben végigmegyünk azon, miért van ez így, hogyan javítható React, Vue vagy más keretrendszer alatt, és mit kérj a fejlesztődtől egy átadható ellenőrzőlistában.

A cikkben háromféle jelölést használunk. Hivatalosan igazolt az, ami a Google Search Central dokumentációjában vagy egy szabványban (IETF, WHATWG) szerepel. Saját tapasztalat az, amit weboldal-auditok során rendszeresen látunk. Szakmai feltételezés az, amire jó okunk van, de hivatalos megerősítés nincs rá.
Miért nem indexelhető külön oldalként a #/szolgaltatasok cím?
A kettőskereszt utáni rész (fragment) a böngészőé, a szerver meg sem kapja, és a Google az ilyen címeket alapesetben nem kezeli külön URL-ként. Ezért a /#/szolgaltatasok és a /#/rolunk egyetlen indexelhető oldalnak számít, a főoldalnak.
Hivatalosan igazolt. Az URL-ek általános szintaxisát leíró RFC 3986 szerint a fragment a dokumentumon belüli hivatkozásra szolgál, és a böngésző nem küldi el a szervernek. Ha valaki megnyitja a pelda.hu/#/szolgaltatasok címet, a szerver csak annyit lát, hogy a / útvonalat kérték. A Google JavaScript SEO alapjairól szóló útmutatója kifejezetten leírja, hogy a fragmentekre épülő útvonalakat a Googlebot nem tudja megbízhatóan feloldani, és a History API használatát javasolja helyettük.
Volt egy korábbi kerülőút is, a #! („hashbang”) jelölés és a hozzá tartozó AJAX-feltérképezési séma. A Google ezt 2015-ben elavulttá nyilvánította, 2018 óta pedig nem támogatja. Ha egy régi oldalon még #! címeket látsz, azokra ma már nem érdemes építeni.
Saját tapasztalat. Hash alapú SPA-knál a Search Console jellemzően csak a főoldalt mutatja indexeltként, a belső „oldalak” sehol nem jelennek meg a teljesítményjelentésben. A tartalom tehát létezik, csak nincs saját címe, amelyre egy keresési találat vagy egy AI-válasz hivatkozhatna.
Mi a különbség a hash és a History API alapú útvonalkezelés között?
A hash alapú routing a # után tárolja az útvonalat, a History API alapú pedig valódi, a szerver felé is látható útvonalat (/szolgaltatasok) ír a címsorba, oldalújratöltés nélkül. Keresőoptimalizálás szempontjából csak a második ad minden nézetnek saját, indexelhető URL-t.
A History API a böngésző beépített felülete, amelyet a WHATWG HTML-szabvány ír le. A history.pushState() hívással a kód új bejegyzést tesz az előzményekbe és megváltoztatja a címsort, a popstate esemény pedig jelzi, ha a látogató a vissza gombot használja. A felhasználó ugyanazt a gyors, újratöltés nélküli élményt kapja, a címsorban viszont olyan cím áll, amelyet a szerver is értelmez.
A különbség akkor jön elő, amikor valaki ezt a címet közvetlenül nyitja meg egy Google-találatból, egy megosztott linkből vagy egy AI-válasz forráslistájából. Hash esetén a szerver mindig a főoldalt kapja kérésként. History API esetén a /szolgaltatasok kérés eljut a szerverhez, és ott el kell dönteni, mit válaszol rá. Erről szól a következő két szakasz.
Hogyan állítsd át a routert React, Vue vagy Angular alatt?
A legtöbb keretrendszerben ez egyetlen beállítás, a hash mód helyett a „history” vagy „browser” módot kell választani. A nehezebb rész a szerver és a meglévő linkek rendbetétele.
- Vue Router 4 esetén a
createWebHashHistory()helyett acreateWebHistory()függvénnyel hozd létre a routert. - React Router esetén a
HashRouterhelyettBrowserRouterkell, az újabb, adat-routeres változatokban acreateHashRouterhelyettcreateBrowserRouter. - Angular alatt a path alapú stratégia az alapértelmezett, tehát elég kivenni a
useHash: truebeállítást vagy awithHashLocation()hívást a router konfigurációjából. - Next.js, Nuxt, SvelteKit és Astro alapból valódi útvonalakat használnak, és szerveroldali renderelést vagy előre generált HTML-t is tudnak adni. Új projektnél ezek jó eséllyel kevesebb utólagos munkát jelentenek.
Az átállásnál a régi hash címeket nem lehet szerveroldali 301-es átirányítással kezelni, mert a szerver nem látja a fragmentet. Erre egy kis kliensoldali szkript kell a főoldalon, amely kiolvassa a location.hash értékét, és a location.replace('/szolgaltatasok') hívással átküldi a látogatót az új címre. Szakmai feltételezés. A Google a JavaScriptes átirányítást is feldolgozza, de mivel a régi hash címek eleve nem voltak külön indexelve, ennek a lépésnek főleg a régi könyvjelzők és a korábban megosztott linkek miatt van értelme.
Milyen válaszkódot kell adnia a szervernek minden útvonalra?
Minden létező útvonalnak 200-as kóddal és lehetőleg kész tartalommal kell válaszolnia, minden nem létezőnek 404-gyel vagy 410-zel, az áthelyezett oldalaknak 301-gyel. A tipikus SPA-beállítás ezzel szemben minden kérésre 200-at és ugyanazt az üres vázat adja vissza.
Saját tapasztalat. A leggyakoribb SPA-szerverbeállítás egyetlen sor, Nginx alatt például try_files $uri $uri/ /index.html;. Ez minden ismeretlen címre az index.html fájlt adja, 200-as kóddal. A /szolgaltatasok így működik, de a /nincs-ilyen-oldal vagy egy elgépelt link is „sikeres” oldalként jön vissza. A Search Console ezeket jellemzően soft 404-ként jelzi, rosszabb esetben a Google üres vagy egymást duplikáló oldalakat próbál feldolgozni.
Hivatalosan igazolt. A Google JavaScript SEO útmutatója két megoldást ír le a kliensoldali útvonalkezelés hibaoldalaira. Az egyik, hogy JavaScripttel átirányítasz egy olyan URL-re, amelyre a szerver valódi 404-et ad. A másik, hogy a hibanézetbe JavaScripttel <meta name="robots" content="noindex"> címkét teszel.
Ennél tisztább, ha a szerver eleve ismeri az útvonalakat. Erre három út van.
- Szerveroldali renderelés (SSR). A szerver minden kérésre lefuttatja az alkalmazást, és kész HTML-t küld a megfelelő státuszkóddal. Ha az útvonal nem létezik, vagy a kért terméket törölték, a válasz 404.
- Előre generált oldalak (statikus generálás, prerender). A build során minden útvonalhoz külön HTML-fájl készül, például
/szolgaltatasok/index.html. Nginx alatt ilyenkor atry_files $uri $uri/index.html =404;és egyerror_page 404 /404.html;sor gondoskodik arról, hogy csak a ténylegesen legenerált oldalak kapjanak 200-at. - Útvonal-lista a szerveren. Ha az előző kettő nem fér bele, a szerver legalább egy engedélyezett útvonal-lista alapján döntsön. Ami a listán van, arra mehet az
index.html200-zal, minden másra 404-es státuszkód, akár ugyanazzal az alkalmazásvázzal.
Az áthelyezett vagy megszüntetett oldalaknál ugyanez a logika érvényes. A 301-es átirányítást a szervernek kell kiadnia, a router belső átirányítása ezt nem pótolja teljesen.
Miért kell a belső linkeket valódi href-ként megírni?
A Google csak azokat a linkeket követi megbízhatóan, amelyek <a> elemek, és valós URL-re mutató href attribútumuk van. Egy kattintásra reagáló div vagy egy href nélküli gomb a látogatónak működik, a feltérképező robotnak viszont zsákutca.
Hivatalosan igazolt. A Google feltérképezhető linkekről szóló útmutatója szerint a követhető link egy href attribútummal rendelkező <a> elem, amely feloldható URL-re mutat. A dokumentáció a nem követhető minták között említi a href nélküli, csak onclick eseménnyel működő elemet és a javascript: kezdetű értéket.
<a href="/szolgaltatasok">Szolgáltatások</a>követhető, ez a helyes forma.<span onclick="router.push('/szolgaltatasok')">nem követhető, mert nincs benne URL.<a href="#" onclick="...">nem követhető, mert a cél nem ahrefértékében van.<a href="/#/szolgaltatasok">technikailag követhető, de a főoldalra mutat, ezért nem ad saját URL-t a szolgáltatásoknak.
A keretrendszerek link-komponensei, a React Router Link és a Vue RouterLink eleme valódi <a href> elemet renderelnek, és közben elkapják a kattintást, hogy ne töltődjön újra az oldal. A gond ott kezdődik, amikor egy gomb onClick eseményéből hívja a kód a navigate() vagy a router.push() függvényt. Saját tapasztalat. Auditok során a főmenü általában rendben van, a kártyák, a „Tovább” gombok és a lapozók viszont gyakran így készülnek, ezért a mélyebb aloldalak (egyes szolgáltatások, blogcikkek) belső link nélkül maradnak.
A # jel a saját helyén továbbra is hasznos, oldalon belüli ugráshoz (/szolgaltatasok#gyik) nyugodtan használhatod. Külön oldalak szétválasztására viszont ne használd.
Elég, ha a Google rendereli a JavaScriptet?
A Google képes JavaScriptet futtatni, de nem minden robot az, és a renderelés nála is késleltetett lehet. Ha a fontos tartalom és a linkek már a szerver által küldött HTML-ben benne vannak, sokkal több rendszer látja őket.
Hivatalosan igazolt. A Googlebot a Chromium aktuális verziójával renderel, a feldolgozás három lépésben (feltérképezés, renderelés, indexelés) zajlik, és a renderelésre váró oldalak sorba kerülnek. A dinamikus renderelést, vagyis a robotoknak külön kiszolgált változatot a Google ma ideiglenes kerülőútnak tekinti, és SSR-t, statikus generálást vagy hidratálást javasol helyette.
Szakmai feltételezés. Az AI-válaszmotorok robotjairól (GPTBot, ClaudeBot, PerplexityBot és társaik) kevés hivatalos információ szól a JavaScript-futtatásról. Független mérések szerint ezek jellemzően a nyers HTML-t dolgozzák fel, és nem futtatják a kliensoldali kódot. Ha ez így van, egy tisztán kliensoldali SPA-ból egy AI-rendszer csak az üres vázat látja. GEO és AEO szempontból ezért az SSR vagy az előre generált HTML a biztonságosabb választás, akkor is, ha a Google-ben a kliensoldali változat elfogadhatóan teljesít.
Minden útvonalnak saját title, meta description és canonical címke kell, ideális esetben már a szerver által küldött HTML-ben. A canonical mindig a valódi, path alapú URL-re mutasson, soha ne hash-es címre.
Hogyan ellenőrizheted, hogy jól működik?
Néhány perces teszttel kiderül, hogy az oldalaid valódi URL-ek-e. Ehhez nem kell fejlesztői tudás, elég egy böngésző és a Search Console.
- Kattints a menü egyik pontjára, és nézd meg a címsort. Ha
#áll az útvonal előtt, hash alapú a routing. - Másold ki a címet, nyisd meg egy privát ablakban, és frissítsd az oldalt. Ha a főoldal jön be vagy hibát kapsz, a szerver nem ismeri az útvonalat.
- Nyiss meg egy biztosan nem létező címet, például
/teszt-404-oldal. A fejlesztői eszközök Hálózat (Network) fülén nézd meg a státuszkódot. 404-nek kell lennie, nem 200-nak. - Nézd meg az oldal forrását (Windowson Ctrl+U, Macen Cmd+Option+U). Ha a szöveg és a linkek nincsenek benne, csak egy üres
<div id="root">elem, a tartalom kizárólag JavaScripttel áll elő. - A Search Console URL-ellenőrzés eszközével kérd le az aloldalt, és a feltérképezett oldal megtekintésénél nézd meg a renderelt HTML-t és a képernyőképet.
- Futtass egy feltérképező programot (például Screaming Frog) először JavaScript-renderelés nélkül, majd azzal. Ha a talált URL-ek száma között nagy az eltérés, a linkek egy része csak JavaScripttel jön létre.
Mit adj át a fejlesztőnek? Ellenőrzőlista
Az alábbi lista szó szerint továbbítható. Minden pont egy ellenőrizhető állítás, amelyre a fejlesztő igennel vagy nemmel tud felelni.
- A router History API módban fut (Vue
createWebHistory, ReactBrowserRoutervagycreateBrowserRouter), a címekben nincs#/. - Minden indexelendő nézetnek saját, beszédes, kisbetűs, ékezet nélküli útvonala van, például
/szolgaltatasok/weboldal-keszites. - Bármely útvonal közvetlen megnyitása és frissítése a helyes tartalmat adja, 200-as kóddal.
- Nem létező útvonalra a szerver 404-es vagy 410-es HTTP-kódot ad.
- Az áthelyezett oldalakra a szerver 301-es átirányítást ad.
- A régi hash címeket egy kliensoldali szkript az új útvonalakra irányítja.
- Minden belső link
<a href="/valodi-utvonal">elem, a kártyákon, gombokon és a lapozóban is,hrefnélküli navigáció nincs. - Minden útvonal saját
title, meta description és önhivatkozócanonicalcímkét kap, lehetőleg már a szerver által küldött HTML-ben. - A fő tartalom és a navigáció SSR-rel vagy előre generált HTML-lel érkezik.
- Az XML oldaltérkép csak path alapú, 200-as kódot adó, kanonikus URL-eket tartalmaz.
- A
robots.txtnem tiltja a rendereléshez szükséges JavaScript-, CSS- és API-fájlokat.
Szakmai feltételezés. A javítás után az aloldalak indexelése fokozatosan rendeződik, ennek ütemét a Google határozza meg, és az indexelés önmagában semmilyen helyezést nem garantál. Amíg viszont egy szolgáltatásnak nincs saját URL-je, addig nincs mit rangsorolni, és egy AI-válasz sem tud rá forrásként hivatkozni.
Források és további olvasnivalók
- Google Search Central, Understand the JavaScript SEO basics
- Google Search Central, Make your links crawlable
- Google Search Central, Dynamic rendering as a workaround
- Google Search Central, How HTTP status codes affect Google Search
- Google Search Central Blog, Deprecating our AJAX crawling scheme (2015)
- IETF RFC 3986, Uniform Resource Identifier (URI) Generic Syntax
- WHATWG HTML Living Standard, Session history and the History interface
- Vue Router dokumentáció, Different History modes
- React Router dokumentáció, Routers
Gyakori kérdések
Indexeli a Google a #/ kezdetű címeket külön oldalként?
Alapesetben nem. A kettőskereszt utáni részt a böngésző nem küldi el a szervernek, és a Google az ilyen címeket ugyanannak az oldalnak tekinti, ezért a hash alapú SPA nézetei jó eséllyel a főoldalba olvadnak.
Mit kell átállítani a routerben, hogy indexelhetők legyenek az aloldalak?
Vue Router 4 alatt a createWebHashHistory helyett createWebHistory, React Routerben a HashRouter helyett BrowserRouter vagy createBrowserRouter kell. Ezután a szervert is be kell állítani, hogy minden útvonalra helyes státuszkódot adjon.
Miért baj, ha a szerver minden címre az index.html-t adja vissza?
Mert így a nem létező címek is 200-as kóddal jönnek vissza, amit a Google soft 404-ként kezelhet. A létező útvonalakra 200, a nem létezőkre 404 vagy 410 a helyes válasz.
Átirányíthatók a régi hash címek 301-gyel?
Szerveroldalon nem, mert a szerver nem látja a kettőskereszt utáni részt. A régi hash címeket egy kis kliensoldali szkript tudja az új, path alapú útvonalakra irányítani.
Elég, ha a gombok onclick eseménnyel navigálnak?
A látogatónak működik, a Google viszont csak a valós URL-re mutató href attribútummal rendelkező a elemeket követi megbízhatóan. A navigációs elemeket a keretrendszer link-komponensével vagy valódi a href elemként érdemes megírni.
Látják az AI-válaszmotorok egy kliensoldali SPA tartalmát?
Erről kevés a hivatalos információ, de független mérések szerint több AI-crawler nem futtat JavaScriptet. Ezért a szerveroldali renderelés vagy az előre generált HTML a biztonságosabb megoldás.
Források és további olvasnivalók
Ezen az oldalon a hivatalosan igazolt információt elsődleges forrásokból építjük fel; a saját tapasztalatot és a szakmai becslést mindig külön jelöljük a szövegben.