Přejít k navigační liště

Zdroják » JavaScript » Jak jsme přestavěli javascriptovou aplikaci, aby jí i roboti rozuměli

Jak jsme přestavěli javascriptovou aplikaci, aby jí i roboti rozuměli

Články JavaScript

Popíšeme vám, jak jsme náš projekt původně postavený na Angularu upravili, aby byl snadno dostupný i pro roboty.

Nálepky:

Text vyšel původně na medium.com.

Je tomu už přes rok, co jsme v Zonky produkčně spouštěli tržiště optimalizované pro vyhledávací roboty. Motivace byla jasná  —  nejsme vidět ve výsledcích vyhledávačů a přitom máme v příbězích obsah, který pomůže projekt zviditelnit. V následujících odstavcích společně projdeme zadání, volbu technologií, a uvedení do produkčního provozu.

Co bylo naším úkolem?

Úkolem bylo vytvořit na tržišti samostatné podstránky, které budou rozdělené podle kategorií. Každá kategorie má mít svůj název, např. “Půjčky na domácnost” a specifickou URL, např. https://app.zonky.cz/pujcky-na-domacnost. S takto zvoleným názvem a URL budeme pro užitele, kteří hledají “Půjčky na domácnost”, vyhledávačem zvýhodněni.

Jednotlivé stránky příběhů budou mít ve svém názvu kromě nadpisu také přezdívku a účel půjčky Spokojené bydlení ve vlastním - zonky242602 - Půjčky na domácnost, a URL https://app.zonky.cz/domacnost/spokojene-bydleni-ve-vlastnim-214608. Stránka s příběhem pak bude zpětně odkazovat na podstránky s kategoriemi.

 
Zpětné odkazy z příběhu na kategorie

Jak na to?

V prvním kroku bylo potřeba rozhodnout, jak tržiště zpřístupnit pro roboty. Stávající aplikace je postavena na technologii AngularJS, a staticky dodávána z webového serveru NGINX. V hledáčku jsme měli tři možnosti:

  1. prohánět tržiště nástrojem PhantomJS (ať už interně nebo s využitím externí služby) a generovat statické stránky,
  2. implementovat separátní tržiště na serveru pouze pro roboty,
  3. přepsat tržiště od základu s podporou pro SSR (“server-side rendering”).

První varianta nebyla vhodná kvůli nutnosti řešit cacheování (TwoHardThings) — technické problémy s (pře)generováním stránek při vystavení nového příběhu, či změně stavu nebo obsahu již existujícího. Druhou variantu jsme zavrhli pro udržování dvou samostatných verzí aplikace a generování shodného výstupu pro roboty i uživatele. Zvolili jsme poslední variantu — krom jiného pro jeden zdrojový kód, shodný HTML výstup a rychlejší vizuální odezvu, a také možnosti použít nový způsob psaní frontendové aplikace běžící i na serveru.

Přepsaná část aplikace je postavena na frameworku Ember.js s rozšířením FastBoot, které přidává podporu pro SSR. FastBoot běží na platformě Node.js jako middleware do Express.js serveru. FastBoot funguje tak, že při spuštění serveru se načtou JavaScriptové soubory do V8 Virtual Machine kontextu a vytvoří se část aplikace, která je odpovědná primárně za její konfiguraci. Jednotlivé HTTP dotazy pak přes Express.js protékají do VM kontextu, kde Ember vytvoří instanci aplikace pro zpracování dotazu, navštíví se požadovaná cesta, poté se počká na vykreslení HTML obsahu, výsledek se odešle do prohlížeče klienta a instance se zahodí.

 
Zpracování HTTP dotazu FastBootem na serveru

Pro vzájemnou komunikaci mezi serverem a prohlížečem slouží:

  1. cookies, které se používají například pro správu přihlášeného uživatele.
  2. Ve FastBootu lze také použít shoebox API, které umožňuje na serveru uložit data ve formátu JSON do meta tagů a v prohlížeči načíst do aplikace. Tento způsob používáme pro předání IP adresy klienta externí službě LaunchDarkly, která nám spravuje feature flags pro fázované nasazení nových funkcí.
getPublicIp() {
  let isFastBoot = this.get('fastboot.isFastBoot');
  let shoebox = this.get('fastboot.shoebox');

  if (isFastBoot) {
    let headers = this.get('fastboot.request.headers');
    let publicIpAddress = headers.get('X-Forwarded-For');
    shoebox.put('public-ip', publicIpAddress);
    return publicIpAddress;
  } else {
    return shoebox.retrieve('public-ip');
  }
}

Zavrhli jsme možnost přepsat celou aplikaci najednou jako “big bang”, proto bylo potřeba vyřešit přepínání mezi stávajícím frameworkem AngularJS a novým frameworkem Ember. Napřed jsme přesunuli Angular část z / (rootu) do podsložky /public. V Angular používáme UI Router, který spravuje stav aplikace v hash části, např. původní dashboard půjčovače byl dostupný na https://app.zonky.cz/#/dashboard/borrower, nová adresa je tedy na https://app.zonky.cz/public/index.html#/dashboard/borrower.

Bylo nutné zajistit přesměrování z původních URL adres na nové. Protože se hash část neposílá na server, je potřeba přesměrovat až v prohlížeči. V Ember části je malý skript, který řeší přechod na nové URL adresy.

Postupně migrujeme stránku po stránce. Když dojde k přepsání stránky, upraví se router v AngularJS tak, aby přesměrovával na novou URL do Emberu. V případě zmiňovaného dashboardu půjčovače by vypadal kód následovně:

onStateChangeStart: (event, toState) =>
  if toState.name is "dashboard.borrower"
    event.preventDefault();
    return window.location = "#{ENV.emberAppUrl}/moje-pujcky"

Mimo jiné bylo potřeba implementovat meta tagy pro navádění robotů při stránkování na tržišti, např. pro druhou stránku takto:

<link rel="canonical" href="https://app.zonky.cz/?page=1">
<link rel="prev" href="https://app.zonky.cz/">
<link rel="next" href="https://app.zonky.cz/?page=2">

A pravidelně přegenerovávat mapu stránek složenou z jednotlivých příběhů (dynamická čast) a kategorií (statická část), k čemuž jsme použili Lambda funkce.

Jdeme na produkci

Z jednoduchého statického serveru jsme se rozhodli přejít do oblak na AWS platformu. Používáme CloudFront jako CDN pro distribuci statického obsahu, který se natahuje z S3 bucketů -jeden bucket /assets pro Ember a druhý /public pro Angular. Dynamická část běží v Elastic Beanstalk s load balancerem a EC2 instancemi s FastBootem.

Při rozjezdu aplikace jsme chtěli mít možnost postupně navyšovat počet uživatelů, a nebo se při chybě vrátit zpět na původní variantu (čehož jsme několikrát využili). Před CloudFront byla umístěna EC2 instance s HAProxy, která se starala o přesměrování požadavků buď na CloudFront nebo NGINX s původní aplikací.

 
AWS infrastruktura

Napřed jsme na nové tržiště vpouštěli pouze uživatele z firemní sítě a až poté jsme začali postupně přidávat uživatele i z internetu. HAProxy nám kromě prvotního přechodu donedávna sloužila i pro nastavení HTTP hlaviček — pravidla pro zabezpečení, kešování, atd. Služba S3, měla v té době pouze omezenou sadu editovatelných hlaviček. Dnes již používáme pro nastavení hlaviček Lambda@Edge funkce, které jsou propojené s CloudFrontem a plně tak nahradily HAProxy.

Na závěr

Použití Ember.js s FastBootem nám umožnilo implementaci SEO požadavků a zároveň mít společný kód jak pro uživatele, tak roboty. Získané zkušenosti s přechodem na jiný framework a s migrací na AWS se nám budou hodit i pro přepis naší back-office aplikace a mohou být inspirací i pro ostatní, kteří jsou postaveni před podobný problém.

Komentáře

Subscribe
Upozornit na
guest
11 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
dejmek

Jaký robot byl konkrétně cílem? Googlebot nemá s aplikacemi problém proto JAM stack tak stoupá v oblibě. Teď už aplikaci budete těžko servovat z cdn. Otázka je jestli by se to vůbec vyplatilo v lokálním měřítku.

YAWD

Pre web typu trziste by bol JAM stack nezmysel, preto je irelevantné, že s inými typmi webov funguje.

Vítězslav Gazda

Typický příklad špatně zvolené technologie na začátku projektu a následné řešení z toho plynoucích problémů způsobem „škrábání se levou rukou v pravém uchu“. Jinak řečeno: 1.chyba byla ve výběru technologie pro veřejný komerční projekt, která má potíže s vyhledávacími roboty (javascript v prohlížeči). 2.chyba je zásadní předělání celého projektu, ale přitom ponechání původní problémové technologie, která je pouze znásilněna k účelu, ke kterému nebyla původně určena (javascript na serveru), takže se domnívám, že nové problémy na sebe nenechají dlouho čekat (výkon, zdroje, poruchovost).

YAWD

Nehnevaj sa Gazda, ale nevieš o čom hovoríš. V dnešnej dobe môže použitie JS na serveri považovať za znásilnenie len úplný ignorant. A aj v bode 1. si mimo, crawlers nemajú problém s JS všeobecne, len s niektorými spôsobmi jeho použitia.

Pitrsonek

Pekny clanek,
mohli byste se vice rozeptat proc jste zrovna zvolili Ember a na zaklade ceho jste se rozhodovali?

Proc ne treba: Angular 6, VueJs nebo React vse SSR podporuje.

Diku

YAWD

Toto je jediná zmysluplná otázka. Premýšľali ste nad tým ako tú apku urobiť SEO friendly bez toho aby ste sa škriabali okolo hlavy, preto ste vypustili riešenie 1. s PhantomJS a vyriešili ste to tak, že … sa škriabete okolo hlavy s EmberJS? Jop, to má logiku… A ešte sa tu s tým aj pochválite, hoci to silne zaváňa nekompetentnosťou? Jediným zmysluplným riešením, keď už je postavená nad Angularom, bolo ostať nad Angularom, akurát ju zmodernizovať do latest verzie a urobiť ju SSR s Angular Universal. Mrzí ma to, ale váš článok je odstrašujúci príklad, nie inšpirácia…

Pitrsonek

Uz chapu zvolenou technologii pro prepis vzhledem k terminu realizace prepisu app je pravda ze Angular 2 a SSR bylo zatim v nedohlednu, takze zvolili pro prepis vhodnou technologii z pohledu moznosti SSR vcetne znalosti vyvojaru. Predpokladam ze nyni by rozhodovani nemeli lehke vzhledem k dostupnosti VueJS, Angular 6, atd. ktere podporuji SSR.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.