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

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

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.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 11

Přehled komentářů

dejmek Cil?
Jan Bobisud Re: Cil?
YAWD Re: Cil?
Vítězslav Gazda Typický příklad
Jan Bobisud Re: Typický příklad
YAWD Re: Typický příklad
Pitrsonek
Jan Bobisud Re:
YAWD Re:
Jan Bobisud Re:
Pitrsonek Re:
Zdroj: https://www.zdrojak.cz/?p=21230