Bij het schrijven van single page applicaties is het makkelijk en natuurlijk om verstrikt te raken in het proberen de ideale ervaring te creëren voor het meest voorkomende type gebruikers – andere mensen zoals wijzelf. Deze agressieve focus op één soort bezoeker van onze site kan vaak een andere belangrijke groep in de kou laten staan – de crawlers en bots die worden gebruikt door zoekmachines zoals Google. Deze gids laat zien hoe een aantal eenvoudig te implementeren best practices en een beweging naar server-side rendering uw applicatie het beste van twee werelden kan geven als het gaat om SPA gebruikerservaring en SEO.
Voorvereisten
Een actieve kennis van Angular 5+ wordt verondersteld. Sommige delen van de gids gaan over Angular 6, maar kennis daarvan is niet strikt vereist.
Veel van de onbedoelde SEO-fouten die we maken, komen voort uit de mentaliteit dat we webapplicaties bouwen en geen websites. Wat is het verschil? Het is een subjectief onderscheid, maar ik zou zeggen vanuit het oogpunt van de focus van de inspanning:
- Web applicaties richten zich op natuurlijke en intuïtieve interacties voor gebruikers
- Web sites richten zich op het algemeen beschikbaar stellen van informatie
Maar deze twee concepten hoeven elkaar niet uit te sluiten! Door gewoon terug te keren naar de wortels van de regels voor website-ontwikkeling, kunnen we de gelikte look en feel van SPA’s behouden en informatie op alle juiste plaatsen zetten om een ideale website voor crawlers te maken.
Verstop inhoud niet achter interacties
Een principe om over na te denken bij het ontwerpen van componenten is dat crawlers een soort van dom zijn. Ze zullen op je ankers klikken, maar ze zullen niet willekeurig over elementen vegen of op een div klikken alleen omdat de inhoud “Lees meer” zegt. Dit komt op gespannen voet te staan met Angular, waar een gangbare praktijk om informatie te verbergen is om “*ngif it out”. En vaak is dit zinvol! We gebruiken dit om de prestaties van applicaties te verbeteren door potentieel zware componenten niet in een niet zichtbaar deel van de pagina te laten zitten.
Dit betekent echter dat als je content op je pagina verbergt door slimme interacties, de kans groot is dat een crawler die content nooit te zien krijgt. Je kunt dit tegengaan door CSS te gebruiken in plaats van *ngif om dit soort inhoud te verbergen. Natuurlijk, slimme crawlers zullen merken dat de tekst verborgen is en het zal waarschijnlijk worden gewogen als minder belangrijk dan zichtbare tekst. Maar dit is een beter resultaat dan dat de tekst helemaal niet toegankelijk is in het DOM. Een voorbeeld van deze aanpak ziet er als volgt uit:
Maak geen “virtuele ankers”
Het onderstaande component toont een antipatroon dat ik veel zie in Angular-toepassingen en dat ik een “virtueel anker” noem:
Basically what’s happening is that a click handler is attached to something like a <button> or <div> tag and that handler will perform some logic, then use the imported Angular Router to navigate to another page. Dit is problematisch om twee redenen:
- Crawlers zullen waarschijnlijk niet op dit soort elementen klikken, en zelfs als ze dat doen, zullen ze geen link leggen tussen de bron- en bestemmingspagina.
- Dit voorkomt de zeer handige ‘Open in nieuw tabblad’-functie die browsers van nature bieden aan echte anker-tags.
In plaats van virtuele ankers te gebruiken, gebruik dan een echte <a>-tag met de routerlink-richtlijn. Als u extra logica moet uitvoeren voordat u navigeert, kunt u nog steeds een click handler aan de anchor tag toevoegen.
Vergeet de koppen niet
Een van de principes van goede SEO is het vaststellen van het relatieve belang van verschillende tekst op een pagina. Een belangrijk hulpmiddel hierbij voor webontwikkelaars zijn de koppen. Het is gebruikelijk om de koppen helemaal te vergeten bij het ontwerpen van de component hiërarchie van een Angular applicatie; of ze wel of niet worden opgenomen maakt geen visueel verschil in het eindproduct. Maar dit is wel iets waar je rekening mee moet houden om ervoor te zorgen dat crawlers zich richten op de juiste delen van je informatie. Overweeg dus om heading tags te gebruiken waar dat zinvol is. Zorg er echter voor dat onderdelen die heading-tags bevatten niet zo kunnen worden gerangschikt dat een <h1> binnen een <h2> verschijnt.
Maak “Zoekresultaat-pagina’s” linkbaar
Terugkomend op het principe van hoe crawlers dom zijn – denk aan een zoekpagina voor een widget-bedrijf. Een crawler ziet geen tekst op een formulier en typt iets in als “Toronto widgets”. Conceptueel, om zoekresultaten beschikbaar te maken voor crawlers moet het volgende gebeuren:
- Er moet een zoekpagina worden opgezet die zoekparameters accepteert via het pad en/of de query.
- Links naar specifieke zoekopdrachten waarvan u denkt dat de crawler ze interessant zou kunnen vinden, moeten worden toegevoegd aan de sitemap of als ankerlinks op andere pagina’s van de site.
De strategie rond punt #2 valt buiten het bestek van dit artikel (Enkele nuttige bronnen zijn https://yoast.com/internal-linking-for-seo-why-and-how/ en https://moz.com/learn/seo/internal-link). Wat belangrijk is, is dat zoekcomponenten en -pagina’s worden ontworpen met punt #1 in gedachten, zodat je de flexibiliteit hebt om een link te maken naar elk soort zoekactie die mogelijk is, zodat deze kan worden geïnjecteerd waar je maar wilt. Dit betekent dat je de ActivatedRoute importeert en reageert op de veranderingen in pad en query parameters om de zoekresultaten op je pagina aan te sturen, in plaats van alleen te vertrouwen op je on-page query en filtering componenten.
Maak paginering linkable
Wanneer we het toch over zoekpagina’s hebben, is het belangrijk om er voor te zorgen dat paginering correct wordt afgehandeld, zodat crawlers elke pagina van je zoekresultaten kunnen benaderen als ze dat willen. Er zijn een paar best practices die je kunt volgen om dit te verzekeren.
Om eerdere punten te herhalen: gebruik geen “Virtuele Ankers” voor je “volgende”, “vorige” en “paginanummer” links. Als een crawler deze niet als ankers kan zien, zal hij misschien nooit verder kijken dan de eerste pagina. Gebruik hiervoor echte <a> tags met RouterLink. Neem paginering ook op als optioneel onderdeel van uw doorklikbare zoek-URL’s – dit gebeurt vaak in de vorm van een page= queryparameter.
U kunt crawlers extra hints geven over de paginering van uw site door relatieve “prev”/”next” <link>-tags toe te voegen. Een uitleg over waarom deze nuttig kunnen zijn, is te vinden op: https://webmasters.googleblog.com/2011/09/pagination-with-relnext-and-relprev.html. Hier volgt een voorbeeld van een service die deze <link>-tags automatisch kan beheren op een Angular-vriendelijke manier:
Dynamische metadata opnemen
Eén van de eerste dingen die we doen bij een nieuwe Angular-applicatie is aanpassingen aanbrengen in het index.html bestand – het instellen van de favicon, het toevoegen van responsive meta tags en waarschijnlijk het instellen van de inhoud van de <title> en <meta name=”description”> tags op een aantal verstandige standaardwaarden voor uw applicatie. Maar als u er om geeft hoe uw pagina’s in de zoekresultaten verschijnen, kunt u het daar niet bij laten. Op elke route voor je applicatie moet je dynamisch de titel en beschrijving tags instellen zodat ze overeenkomen met de pagina inhoud. Dit helpt niet alleen crawlers, het helpt ook gebruikers, omdat zij informatieve browsertabbladtitels, bladwijzers en voorbeeldinformatie te zien krijgen wanneer ze een link delen op sociale media. Onderstaand knipsel laat zien hoe u deze op een Angular-vriendelijke manier kunt bijwerken met de klassen Meta en Title:
Test of crawlers uw code kraken
Sommige bibliotheken of SDK’s van derden worden afgesloten of kunnen niet worden geladen van hun hostingprovider wanneer user agents worden gedetecteerd die behoren tot crawlers van zoekmachines. Als een deel van uw functionaliteit afhankelijk is van deze afhankelijkheden, moet u een fallback bieden voor afhankelijkheden die crawlers niet toestaan. Op zijn minst zou je applicatie in deze gevallen gracieus moeten degraderen, in plaats van het renderproces van de client te laten crashen. Een goed hulpmiddel voor het testen van de interactie van je code met crawlers is de Google Mobile Friendly testpagina: https://search.google.com/test/mobile-friendly. Zoek naar uitvoer zoals deze, die aangeeft dat de toegang tot een SDK voor de crawler wordt geblokkeerd:
Bundelgrootte verkleinen met Angular 6
Bundelgrootte in Angular-applicaties is een bekend probleem, maar er zijn veel optimalisaties die een ontwikkelaar kan doorvoeren om dit te beperken, waaronder het gebruik van AOT builds en conservatief zijn met het opnemen van bibliotheken van derden. Echter, om vandaag de dag de kleinst mogelijke Angular bundels te krijgen, is het nodig om te upgraden naar Angular 6. De reden hiervoor is de vereiste parallelle upgrade naar RXJS 6, die aanzienlijke verbeteringen biedt in de mogelijkheid om met bomen te schudden. Om deze verbetering daadwerkelijk te krijgen, zijn er enkele harde eisen voor uw applicatie:
- Verwijder de rxjs-compat library (die standaard wordt toegevoegd in het Angular 6 upgrade proces) – deze library maakt uw code backwards compatible met RXJS 5 maar verslaat de tree-shaking verbeteringen.
- Zorg ervoor dat alle afhankelijkheden naar Angular 6 verwijzen en de rxjs-compat-bibliotheek niet gebruiken.
- Importeer RXJS-operatoren een voor een in plaats van in het groot om ervoor te zorgen dat tree shaking zijn werk kan doen. Zie https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md voor een volledige gids over migreren.
Server Rendering
Zelfs na het volgen van alle voorgaande best practices kunt u merken dat uw Angular-website niet zo hoog wordt gerangschikt als u zou willen. Een mogelijke reden hiervoor is een van de fundamentele gebreken met SPA frameworks in de context van SEO – ze vertrouwen op Javascript om de pagina te renderen. Dit probleem kan zich op twee manieren manifesteren:
- Terwijl Googlebot Javascript kan uitvoeren, doet niet elke crawler dat. Voor degenen die dat niet doen, zullen al uw pagina’s er in wezen leeg uitzien.
- Om een pagina bruikbare inhoud te laten zien, moet de crawler wachten tot Javascript-bundels zijn gedownload, de engine ze heeft geparseerd, de code is uitgevoerd, en eventuele externe XHR’s zijn teruggekeerd – dan zal er inhoud in het DOM zijn. Vergeleken met meer traditionele server rendered talen waar informatie beschikbaar is in het DOM zodra het document de browser raakt, zal een SPA hier waarschijnlijk enigszins worden gestraft.
Gelukkig heeft Angular een oplossing voor dit probleem dat het mogelijk maakt een applicatie in een server rendered vorm te serveren: Angular Universal (https://github.com/angular/universal). Een typische implementatie die gebruik maakt van deze oplossing ziet er als volgt uit: