Het geval van gedeeltelijke hydratatie (met Next en Preact)

Bij Spring zijn we verantwoordelijk voor het onderhouden van verschillende nieuwsmedia-websites voor ons moederbedrijf, Axel Springer. Een van onze nieuwste releases, welt.de, is de snelste nieuwsmedia-website in Duitsland; een van onze meest gewilde doelen is om continu de best mogelijke prestaties te bereiken en de reden hiervoor is eenvoudig: betere prestaties betekent meestal een betere gebruikerservaring en dus een hogere gebruikersretentie.

tl; Dr.

Scrol naar beneden naar "Samenvatting" voor een korte samenvatting met een infographic. De belangrijkste punten hiervoor zijn in een notendop:
  • Prestaties zijn cruciaal voor het web
  • om hoge prestaties te behalen willen we zo min mogelijk naar de klant sturen
  • we kunnen dit doen door de componenten te kiezen die we willen verzenden en hydrateren naar de klant
  • we laten de rest van de pagina statisch achter en hebben meerdere renderwortels
  • Dit alles werkt met een enkele codebasis
  • Hieronder staat een uitgebreid artikel over hoe we de hierboven genoemde stappen hebben geïmplementeerd. U vindt hier ook een link naar een WIP-repo van deze implementatie:
  • https://github.com/spring-media/next-super-performance

Prestaties op het web

Als je Addy Osmani volgt, ken je de oefening al, hij schrijft veel over de oorzaak en de gevolgen van prestaties op het web. Om je op weg te helpen, kan ik Addy's artikel over 'De kosten van JavaScript in 2018' aanbevelen. Twee zeer belangrijke dingen die u uit dit artikel kunt leren, zijn:

  • De kosten van JavaScript zijn niet alleen de tijd die nodig is om uw bundel te laden
  • De tijd om uw JavaScript te parseren en uit te voeren is net zo cruciaal

Natuurlijk is er veel meer aan prestaties dan dit, inclusief laadstrategieën, een kritisch weergavepad, prestatiebudgetten enzovoort. Al deze dingen draaien om het optimaliseren van alles wat u naar uw klant stuurt. Waar we ons met gedeeltelijke hydratatie op willen concentreren, is niet hoe we kunnen optimaliseren wat u verzendt, maar hoeveel u helemaal verzendt.

Een belangrijk aspect hiervan is server side rendering (SSR) omdat we op de server veel kunnen doen dat niet op de client hoeft te gebeuren. In feite is dit de kern van dit artikel; Wat op de server kan worden gedaan, moet op de server worden gedaan, maar de client mag alleen worden verzonden wat aan de client moet worden uitgevoerd. Bovendien kunt u nog steeds alles toepassen wat u weet over webprestaties, maar u zult veel minder factoren moeten beheren. Dit wordt verderop in het artikel uitgebreid uitgelegd.

SSR en hydratatie

Om ons doel van het bouwen van een performante website te realiseren, zullen we een aangepaste versie van Next gebruiken. Next wordt geleverd met tal van ingebouwde prestatieverbeterende functies, het belangrijkste is dat Next server side rendering (SSR) uit de doos haalt. Dit betekent dat Next uw app gebruikt, geschreven in React en in deze volgorde:

  1. Render het als een HTML-string op de server
  2. Stuur de gerenderde HTML-reeks naar uw gebruikers als broncode
  3. Stuur uw React-code als JavaScript naar uw gebruikers
  4. En dan 'hydrateer' je HTML vervolgens met je React-code

'Hydrateren' betekent in dit geval dat Next uw React-code over uw HTML rolt en vervolgens React zoiets als dit vertelt:

Hey React, hier is wat HTML die exact overeenkomt met wat je zou weergeven als ik je zou vertellen om in een leeg DOM-knooppunt te renderen, alsjeblieft niet alles opnieuw renderen, gebruik in plaats daarvan gewoon de HTML alsof je het hebt weergegeven en ga door met jouw dag

Reageren zal antwoorden

Ok, ik heb net naar je HTML gekeken en het lijkt exact overeen te komen met wat ik zou hebben weergegeven. Het is cool. Ik voeg gewoon enkele event-handlers toe aan je DOM en je pagina fungeert nu als een applicatie met één pagina, alsof ik het allemaal zelf heb gedaan.

De voordelen van het laden van een website op deze manier zijn eenvoudig: uw gebruikers zien al een volledig gerenderde pagina wanneer ze uw website laden (in plaats van een lege pagina) en dan wordt het interactief. Uw website zal ook profiteren van een belangrijke prestatieverbetering omdat de browser geen repaints hoeft te doen (uw pagina opnieuw weergeven met React).

Te veel overhead

Deze aanpak is fantastisch wanneer u webtoepassingen wilt maken of met andere woorden websites die volledig door JavaScript moeten worden beheerd en ook interactief zijn waar u ook klikt. Voorbeelden van deze benadering bij de productie zijn websites zoals Facebook, Twitter en webgebaseerde e-mailclients.

Maar de meeste websites zijn niet zo, de meeste websites zijn nogal statisch en bevatten ook enkele interactieve elementen.

Nu verzendt u uiteindelijk uw volledige applicatiecode naar uw gebruikers, inclusief React-componenten voor elke kop of tekstalinea overal op uw pagina. Het resultaat is een onnodig grote bundel die moet worden geladen, ontleed en uitgevoerd. Dit resulteert in suboptimale prestaties, uw pagina zal traag (re) zijn, vooral voor mobiele gebruikers en zonder goede reden!

En dat is klote.

Dus, wat kunnen we doen? Nou, er zijn veel strategieën en producten die er zijn. Een van de meest prominente is Gatsby, een statische sitegenerator (ik weet zeker dat je er nu al van hebt gehoord) die zich sterk richt op prestatieoptimalisatie. Het probleem met Gatsby is dat het al je pagina's en subpagina's tijdens het compileren moet genereren, wat niet echt werkt wanneer je sites hebt die zijn gekoppeld aan een CMS die dagelijks wordt bijgewerkt en dat miljoenen artikelen host - en dat is precies wat we nodig hebben voor onze nieuws mediasites. Dit is de reden waarom we Next gebruiken en het aanpassen aan onze behoeften.

Voer gedeeltelijke hydratatie in

Om de bovengenoemde problemen op te lossen, hebben we iets bedacht dat we gedeeltelijke hydratatie noemen.

Als je dit onderwerp hebt bekeken, zul je waarschijnlijk termen als progressieve hydratatie of luie hydratatie zijn tegengekomen. Ze bedoelen niet allemaal hetzelfde, maar ze zijn allemaal met elkaar verbonden en horen in dezelfde sfeer.

Het basisidee achter onze versie van gedeeltelijke hydratatie is: in plaats van SSR te doen en vervolgens uw volledige toepassing naar uw klant te verzenden, worden alleen delen van het JavaScript van uw toepassing naar de klant verzonden om de delen van uw website te hydrateren waarvoor specifiek JavaScript vereist is om te werken . Als u een website zou maken met behulp van een dergelijke methode, zou u meerdere kleine React ‘apps’ hebben met meerdere renderwortels op uw anders statische website.

Dingen op deze manier doen, zou uw website een enorme prestatieverbetering moeten geven, omdat u uiteindelijk alleen HTML, CSS en de minste hoeveelheid JavaScript nodig voor uw pagina ontvangt. Een ding om op te merken, bij het meten van prestaties moet u niet alleen factor in laadtijd, maar ook parsing en uitvoeringstijd.

Je kunt er ook voor kiezen om de juiste prestatiestrategieën te implementeren, zoals het splitsen van codes, zorgen voor de trage start van TCP en je kritieke weergavepad enz.

Onze implementatie

Onze implementatie bestaat uit 2 pakketten:

  • De Preact-bibliotheek voor gedeeltelijke hydratatie wordt pool-attendant-preact genoemd
  • De plug-in Next.js wordt next-super-performance genoemd

De laatste bibliotheek is slechts een plug-in voor Next.js die gebruik maakt van pool-attendant dus laten we ons concentreren op pool-attendant-preact. Ik kan in een later stadium een ​​vervolgpost over next-super-performance schrijven.

pool-attendant-preact

Lay-out met een koptekst, body, zijbalk en 2 reactieve elementen

Stel je deze lay-out voor en laten we doen alsof de grijze vakken elementen zijn die volledig statisch kunnen zijn en je wilt dat degenen in paars interactief zijn. De grotere kan bijvoorbeeld een Twitter-feed zijn en de kleinere een kleine stemhulpmiddel. We moeten dus JavaScript op deze elementen toepassen om ze interactief te maken en we willen de rest als statische elementen laten.

Een voorbeeldimplementatie hiervoor met pool-attendant-preact kan er zo uitzien:

Regels 3-8 zijn alle componenten die we willen weergeven, deze componenten worden op de server weergegeven en vervolgens als HTML en CSS naar de client verzonden zonder enige JavaScript.

In regel 10 en 11 markeren we de componenten TwitterFeed en Poll voor hydratatie en krijgen we een nieuw component terug. Lijnen 18 en 19 zijn waar we ze gebruiken.

Lijn 22 is van het grootste belang. Dit is een component die hydratatiegegevens (rekwisieten en componentnamen) in de pagina injecteert.

Maar laat het me uitleggen. Wanneer we een normale hydratatie uitvoeren met reageren, ziet uw code er ongeveer zo uit:

Er zijn 2 problemen die we hier moeten oplossen als we gedeeltelijke hydratatie uitvoeren

  1. ReactDOM.hydrate werkt op een wortelknooppunt in het DOM, het knooppunt dat het gebruikt als uitgangspunt voor de hydratatie. Dat rootknooppunt moet een door de server gerenderde DOM-structuur bevatten die overeenkomt met de componenten en status van uw app. De vangst: u moet een DOM-knooppunt expliciet een naam geven om als rootknooppunt te fungeren. In dit voorbeeld is dit eenvoudig, u kunt die knoop een ID geven, document.getElementbyId gebruiken en die knoop vervolgens in ReactDOM.hydrate gooien en u bent klaar!
    Gedeeltelijke hydratatie betekent daarentegen dat je meerdere DOM-elementen op je statische pagina hebt die je moet hydrateren. Je zou ze niet expliciet allemaal willen noemen, wat vervelend werk voor de ontwikkelaar zou zijn.
  2. Wat als HydrateTwitterFeed of HydratedPoll rekwisieten nodig hebben die aan hen moeten worden doorgegeven? Zeg bijvoorbeeld iets als . Als we ReactDOM.hydrate willen uitvoeren (, rootElementOnThePage), waar halen we de luke_schmuke dan van? Hoe zou een statische pagina hiervan op de hoogte zijn? We moeten ze op de een of andere manier opslaan en naar de klant sturen.

Oplossing

De manier waarop we dit probleem hebben aangepakt, kan worden begrepen door de implementatie van withHydration:

Laten we dit eens nader bekijken: met Hydratatie werkt met de componenttechniek van een hogere orde, de component van hogere orde retourneert de originele component samen met zijn oorspronkelijke ongewijzigde rekwisieten, maar het wordt ook voorafgegaan door een