Tilfellet med delvis hydrering (med Next og Preact)

På våren er vi ansvarlige for å opprettholde forskjellige forskjellige medie-nettsteder for morselskapet Axel Springer. En av våre siste utgivelser, welt.de, er det raskeste nettstedet for nyhetsmedier i Tyskland; et av våre mest etterspurte mål er å kontinuerlig oppnå best mulig ytelse, og grunnen til dette er enkel: bedre ytelse betyr vanligvis en bedre brukeropplevelse og dermed en høyere brukeroppbevaring.

tl; dr

Bla ned til “Oppsummering” for en kort oppsummering med en infographic. De viktigste punktene for dette i et nøtteskall:
  • Ytelse er avgjørende for nettet
  • For å oppnå høy ytelse ønsker vi å sende så lite som mulig til klienten
  • Vi kan gjøre dette ved å velge komponentene vi vil sende og hydrere til klienten
  • Vi lar resten av siden være statiske og har flere røtter
  • Alt dette fungerer med en enkelt kodebase
  • Nedenfor er en lang artikkel om hvordan vi implementerte trinnene nevnt ovenfor. Du finner også en lenke til en WIP-repo av denne implementeringen her:
  • https://github.com/spring-media/next-super-performance

Ytelse på nettet

Følger du Addy Osmani kjenner du allerede borna, han skriver mye om årsaken og effektene av ytelsen på nettet. For å komme i gang kan jeg anbefale Addys artikkel om “Kostnadene for JavaScript i 2018”. To veldig viktige ting du kan lære av denne artikkelen er:

  • Kostnaden for JavaScript er ikke bare tiden det tar å laste bunten
  • Tiden for å analysere og utføre JavaScript er like avgjørende

Selvfølgelig er det mye mer å prestere enn dette, inkludert lastestrategier, en kritisk gjengivelsesbane, ytelsesbudsjetter og så videre. Alle disse tingene dreier seg om hvordan du kan optimalisere det du ender opp med å sende til klienten. Det vi ønsker å fokusere på med delvis hydrering er ikke hvordan du optimaliserer det du sender, men hvor mye du i det hele tatt sender.

Et sentralt aspekt av dette vil være serversides rendering (SSR) fordi det er mye vi kan gjøre på serveren som ikke trenger å gjøres på klienten. Faktisk er dette kjernen i denne artikkelen; Uansett hva som kan gjøres på serveren, bør gjøres på serveren, men klienten skal bare sendes hva som må utføres på klientsiden. I tillegg kan du fremdeles bruke alt du vet om ytelse på nettet, men du vil ha langt færre faktorer å administrere. Dette vil bli forklart inngående lenger ned i artikkelen.

SSR og hydrering

For å utføre vårt mål om å bygge et utøvende nettsted, bruker vi en modifisert versjon av Next. Neste kommer med mange innebygde ytelsesforbedrende funksjoner, viktigst av alt: Next gjør serversides rendering (SSR) utenfor boksen. Dette betyr at Next tar appen din, skrevet i React og i denne rekkefølgen:

  1. Gjeng den som en HTML-streng på serveren
  2. Send den gjengitte HTML-strengen til brukerne dine som kildekode
  3. Send React-koden din som JavaScript til brukerne dine
  4. Og så til slutt "hydrat" HTML-koden din med React-koden

Å "hydrere" i dette tilfellet betyr at Next vil rulle ut React-koden over HTML-koden din og deretter fortelle React noe som dette:

Hei reagerer, her er noen HTML som samsvarer nøyaktig med hva du ville gjengitt hvis jeg ba deg gjengi i en tom DOM-knute, vær så snill og ikke gjengi alt på nytt, i stedet, bruk bare HTML-koden som om du hadde gjengitt den og fortsett med din dag

React vil svare

OK, jeg så bare på deg HTML, og det ser ut til å stemme nøyaktig med det jeg ville ha gitt. Det er kult. Jeg vil bare knytte noen hendelsesbehandlere til DOM-en din, og siden din fungerer nå som en enkeltsidesapplikasjon som om jeg gjorde alt selv i utgangspunktet.

Fordelene med å laste et nettsted på denne måten er enkle: Brukerne dine vil allerede se en fullt gjengitt side når de laster inn nettstedet ditt (i stedet for en tom side), og da blir det interaktivt. Nettstedet ditt vil også dra nytte av en større ytelsesforbedring fordi nettleseren ikke trenger å gjøre noen gjenvinninger (gjengi siden din med React).

For mye overhead

Denne tilnærmingen er fantastisk når du vil lage webapplikasjoner eller med andre ord nettsteder som må kontrolleres fullstendig av JavaScript og også er interaktive uansett hvor du klikker. Eksempler på denne tilnærmingen i produksjonen inkluderer nettsteder som Facebook, Twitter og nettbaserte e-postklienter.

Men de fleste nettsteder er ikke som dette, de fleste nettsteder er statiske og inneholder også noen interaktive elementer.

Nå ender du opp med å sende hele programkoden til brukerne dine, inkludert React-komponenter for hver overskrift eller tekstparagraf hvor som helst på siden din. Resultatet er et unødvendig stort pakke som må lastes, analyseres og utføres. Dette resulterer i suboptimal ytelse, siden din vil være treg (er) spesielt for mobile brukere og uten god grunn!

Og det suger.

Så hva kan vi gjøre? Vel, det er mange strategier og produkter der ute. En av de mest fremtredende er Gatsby, en statisk nettstedgenerator (jeg er sikker på at du har hørt om det nå) som fokuserer sterkt på ytelsesoptimalisering. Problemet med Gatsby er at det er nødt til å generere alle sidene og undersidene dine på kompileringstidspunktet som egentlig ikke fungerer når du har nettsteder som er koblet til et CMS som oppdateres hver dag, og som inneholder millioner av artikler - som er akkurat det vi trenger for våre nyhetsmediesider. Dette er grunnen til at vi bruker Next i tillegg til å endre det slik at det passer våre behov.

Legg inn delvis hydrering

For å løse de ovennevnte problemene kom vi frem til noe vi liker å kalle delvis hydrering.

Hvis du har sett nærmere på dette emnet, vil du sannsynligvis ha kommet på begreper som progressiv hydrering eller lat hydrering. De betyr ikke alle det samme spesifikt, men de henger sammen og hører hjemme i samme sfære.

Den grunnleggende ideen bak vår versjon av delvis hydrering er: I stedet for å gjøre SSR og deretter sende hele applikasjonen til klienten din, vil bare deler av applikasjonens JavaScript bli sendt til klienten for å hydrere delene av nettstedet ditt som spesifikt krever at JavaScript skal fungere . Hvis du skulle opprette et nettsted ved hjelp av en slik metode, ville du ha flere bittesmå Reager ‘apper’ med flere gjengirote på det ellers statiske nettstedet.

Å gjøre ting på denne måten bør gi nettstedet ditt et enormt ytelsesøkning fordi det du endelig sender er vanlig HTML, CSS og minst mulig JavaScript som kreves for siden din. Én ting å merke seg, når du måler ytelse, må du ikke bare ta hensyn til lastetid, men også analysering og utførelsestid.

Du kan også velge å implementere riktige ytelsesstrategier på toppen av alt dette, for eksempel kodedeling, omsorg for langsom TCP-start og din kritiske gjengivelsesbane osv.

Vår implementering

Vår implementering består av to pakker:

  • Preact-biblioteket for delvis hydrering kalt pool-attendent-preact
  • Next.js-plugin-en kalt neste-super-performance

Det siste biblioteket er bare et programtillegg for Next.js som bruker pool-attendent-preact, så la oss konsentrere oss om pool-attendent-preact. Det kan hende at jeg skriver et oppfølgingsinnlegg om neste superopptreden på et tidspunkt i fremtiden.

pool-attendant-preact

Oppsett med en overskrift, kropp, sidefelt og 2 reaktive elementer

Se på dette oppsettet, og la oss late som de grå boksene er elementer som kan være helt statiske, og du vil at de i lilla skal være interaktive. For eksempel kan den større være en Twitter-feed, og den mindre en liten stemmeverktøy. Så vi må bruke JavaScript på disse elementene for å gjøre dem interaktive, og vi vil la resten være statiske elementer.

Et eksempel på en implementering av dette ved bruk av pool-attendent-preact kan se slik ut:

Linje 3–8 er alle komponentene vi ønsker å vise, disse komponentene blir gjengitt på serveren og deretter sendt som HTML og CSS til klienten uten noe som helst JavaScript.

Linje 10 og 11 er der vi markerer komponentene TwitterFeed og Poll for hydrering og får en ny komponent i retur. Linje 18 og 19 er der vi bruker dem.

Linje 22 er av største betydning. Dette er en komponent som injiserer hydratiseringsdata (rekvisitter og komponentnavn) på siden.

Men la meg forklare. Når vi gjør en normal fuktighet med react, ser koden din slik ut:

Det er to problemer vi må løse her når vi gjør delvis hydrering

  1. ReactDOM.hydrate opererer på en rotnode i DOM, den noden den bruker som utgangspunkt for hydratiseringen. Den rotnoden må inneholde et server-gjengitt DOM-tre som samsvarer med appens komponenter og tilstand. Fangsten: Du må eksplisitt navngi en DOM-nod for å fungere som en rotknute. I dette eksemplet er dette enkelt, du kan gi den noden en ID, bruke document.getElementbyId og deretter kaste den noden i ReactDOM.hydrate og du er ferdig!
    Delvis hydrering betyr derimot at du vil ha flere DOM-elementer på den statiske siden din som du trenger å hydrere. Du ønsker ikke eksplisitt å navngi dem alt som vil være slitsomt arbeid for utvikleren.
  2. Hva om HydrateTwitterFeed eller HydratedPoll trenger rekvisitter som må sendes videre til dem? Si noe som . Hvis vi vil kjøre ReactDOM.hydrate (, rootElementOnThePage) hvor vil vi få luke_schmuke fra? Hvordan ville en statisk side vite om dette? Vi må på en eller annen måte lagre og sende dem til klienten.

Løsning

Måten vi taklet dette problemet på, kan forstås fra implementeringen av withHydration:

La oss se nærmere på dette: withHydration fungerer ved hjelp av komponentteknikken med høyere orden, komponenten med høyere ordre returnerer den originale komponenten sammen med de originale, uendrede rekvisittene, men avhenger den også med en