Defalcarea versiunilor voluminoase cu Netlify și Next.js

Publicat: 2022-03-10
Rezumat rapid ↬ Generarea statică este excelentă pentru performanță – până când aplicația devine prea mare și timpii de construcție trec pe deasupra. Astăzi, vom arunca o privire asupra modului în care proaspeții generatori la cerere de la Netlify pot rezolva asta. În plus, îl asociem cu regenerarea statică incrementală Next.js pentru cea mai bună experiență de utilizator și dezvoltator. Și, desigur, comparați aceste rezultate!

Una dintre cele mai mari probleme ale lucrului cu site-uri web generate static este construirea treptat mai lente pe măsură ce aplicația dvs. crește. Aceasta este o problemă inevitabilă cu care se confruntă orice stivă la un moment dat și poate lovi din diferite puncte, în funcție de tipul de produs cu care lucrați.

De exemplu, dacă aplicația dvs. are mai multe pagini (vizualizări, rute) la generarea artefactului de implementare, fiecare dintre acele rute devine un fișier. Apoi, odată ce ați ajuns la mii, începeți să vă întrebați când puteți implementa fără a fi nevoie să planificați din timp. Acest scenariu este obișnuit pe platformele de comerț electronic sau blogurile, care sunt deja o mare parte a webului, dar nu tot. Rutele nu sunt însă singurul blocaj posibil.

O aplicație bogată în resurse va ajunge în cele din urmă la acest punct de cotitură. Mulți generatori statici efectuează optimizarea activelor pentru a asigura cea mai bună experiență de utilizare. Fără optimizări ale build-urilor (build-uri incrementale, cache, vom ajunge la acestea în curând), acest lucru va deveni în cele din urmă imposibil de gestionat - gândiți-vă să parcurgeți toate imaginile dintr-un site web: redimensionarea, ștergerea și/sau crearea de noi fișiere din nou și din nou. Și odată ce toate acestea s-au terminat: amintiți-vă că Jamstack deservește aplicațiile noastre de la marginile rețelei de difuzare a conținutului . Deci mai trebuie să mutăm lucrurile de pe serverul pe care au fost compilate la marginile rețelei.

Arhitectura de servicii generale Jamstack
Arhitectura serviciului general Jamstack (previzualizare mare)

Pe lângă toate acestea, există și un alt fapt: datele sunt adesea dinamice, ceea ce înseamnă că atunci când ne construim aplicația și o implementăm, poate dura câteva secunde, câteva minute sau chiar o oră. Între timp, lumea continuă să se învârtească și, dacă preluăm date din altă parte, aplicația noastră va deveni învechită. Inacceptabil! Construiește din nou pentru a actualiza!

Construiți o dată, actualizați când este necesar

Rezolvarea Build-urilor voluminoase a fost de o vreme în primul rând pentru fiecare platformă, cadru sau serviciu Jamstack. Multe soluții gravitează în jurul versiunilor incrementale. În practică, aceasta înseamnă că versiunile vor fi la fel de voluminoase ca și diferențele pe care le prezintă față de implementarea actuală.

Definirea unui algoritm de diferență nu este însă o sarcină ușoară. Pentru ca utilizatorul final să beneficieze efectiv de această îmbunătățire, există strategii de invalidare a memoriei cache care trebuie luate în considerare. Pe scurt: nu dorim să invalidăm memoria cache pentru o pagină sau un material care nu s-a schimbat.

Next.js a venit cu regenerarea statică incrementală ( ISR ). În esență, este o modalitate de a declara pentru fiecare rută cât de des dorim să fie reconstruită. Sub capotă, simplifică o mulțime de lucru pe partea serverului. Deoarece fiecare rută (dinamică sau nu) se va reconstrui singură, având în vedere un interval de timp specific și se încadrează perfect în axioma Jamstack de a invalida memoria cache pentru fiecare construcție. Gândiți-vă la el ca la antetul max-age dar pentru rute în aplicația dvs. Next.js.

Pentru a începe aplicația dvs., ISR doar o proprietate de configurare distanță. Pe componenta de rută (în directorul /pages ) accesați metoda getStaticProps și adăugați cheia de revalidate la obiectul returnat:

 export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }

Fragmentul de mai sus se va asigura că pagina mea se reconstruiește la fiecare oră și va aduce mai mulți Pokemon de afișat.

Încă primim build-urile în bloc din când în când (când emitem o nouă implementare). Dar acest lucru ne permite să decuplăm conținutul de cod, prin mutarea conținutului într-un Sistem de management al conținutului (CMS) putem actualiza informațiile în câteva secunde, indiferent de cât de mare este aplicația noastră. La revedere webhook-urilor pentru actualizarea greșelilor de scriere!

Constructori la cerere

Netlify a lansat recent On-Demand Builders, care este abordarea lor pentru a susține ISR pentru Next.js, dar funcționează și în cadre, inclusiv Eleventy și Nuxt. În sesiunea anterioară, am stabilit că ISR a fost un pas grozav către timpi de construcție mai scurti și am abordat o parte semnificativă a cazurilor de utilizare. Cu toate acestea, avertismentele au existat:

  1. Se construiește complet pe o implementare continuă.
    Etapa incrementală are loc numai după implementare și pentru date. Nu este posibil să expediați codul în mod incremental
  2. Construcțiile incrementale sunt un produs al timpului.
    Cache-ul este invalidat pe o bază de timp. Deci, pot apărea versiuni inutile sau actualizările necesare pot dura mai mult, în funcție de perioada de revalidare stabilită în cod.

Noua infrastructură de implementare a Netlify permite dezvoltatorilor să creeze o logică pentru a determina ce părți ale aplicației lor se vor construi pe baza implementării și ce părți vor fi amânate (și cum vor fi amânate).

  • Critic
    Nu este necesară nicio acțiune. Tot ceea ce implementați va fi construit prin push .
  • Amânat
    O anumită bucată a aplicației nu va fi construită la implementare, va fi amânată pentru a fi construită la cerere ori de câte ori apare prima solicitare, apoi va fi stocată în cache ca orice altă resursă de acest tip.

Crearea unui constructor la cerere

Mai întâi de toate, adăugați un pachet netlify/functions ca devDependency la proiectul dvs.:

 yarn add -D @netlify/functions

Odată ce este făcut, este la fel cu crearea unei noi funcții Netlify. Dacă nu ați setat un director specific pentru ele, mergeți la netlify/functions/ și creați un fișier cu orice nume pentru constructorul dvs.

 import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)

După cum puteți vedea din fragmentul de mai sus, generatorul la cerere se împarte de o funcție Netlify obișnuită, deoarece își înfășoară handlerul într-o metodă builder() . Această metodă conectează funcția noastră la sarcinile de construire. Și asta este tot ceea ce aveți nevoie pentru a avea o parte din aplicație amânată pentru construcție numai atunci când este necesar. Mici versiuni incrementale de la început!

Next.js pe Netlify

Pentru a construi o aplicație Next.js pe Netlify, există 2 pluginuri importante pe care ar trebui să le adăugați pentru a avea o experiență mai bună în general: Netlify Plugin Cache Next.js și Essential Next-on-Netlify. Primul memorează în cache NextJS-ul dvs. mai eficient și trebuie să îl adăugați singur, în timp ce cel de-al doilea face câteva ajustări ușoare la modul în care este construită arhitectura Next.js, astfel încât să se potrivească mai bine cu Netlify și să fie disponibil implicit pentru fiecare proiect nou pe care Netlify îl poate identifica. folosind Next.js.

Constructori la cerere cu Next.js

Construire performanță, implementare performanță, cache, experiență de dezvoltator. Acestea sunt toate subiecte foarte importante, dar sunt multe - și necesită timp pentru a configura corect. Apoi ajungem la acea discuție veche despre concentrarea pe experiența dezvoltatorului în loc de experiența utilizatorului. Acesta este momentul în care lucrurile merg într-un loc ascuns într-un întârziere pentru a fi uitate. Nu chiar.

Netlify ți-a luat spatele. În doar câțiva pași, putem valorifica întreaga putere a Jamstack-ului în aplicația noastră Next.js. Este timpul să ne suflecăm mânecile și să punem totul cap la cap acum.

Definirea căilor pre-rendate

Dacă ați mai lucrat cu generarea statică în cadrul Next.js, probabil ați auzit de metoda getStaticPaths . Această metodă este destinată rutelor dinamice (șabloane de pagină care vor reda o gamă largă de pagini). Fără a insista prea mult asupra complexității acestei metode, este important să rețineți că tipul de returnare este un obiect cu 2 chei, ca în Proof-of-Concept, acesta va fi fișierul de rută dinamică [Pokemon]:

 export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
  • paths este o array care realizează toate căile care se potrivesc cu această rută, care vor fi pre-rendate
  • fallback are 3 valori posibile: blocking, true sau false

În cazul nostru, getStaticPaths determină:

  1. Nicio cale nu va fi pre-rendată;
  2. Ori de câte ori se apelează această rută, nu vom servi un șablon alternativ, vom reda pagina la cerere și vom face utilizatorul să aștepte, blocând aplicația să facă orice altceva.

Când utilizați On-Demand Builders, asigurați-vă că strategia de rezervă îndeplinește obiectivele aplicației dvs., documentele oficiale Next.js: documentele de rezervă sunt foarte utile.

Înainte de On-Demand Builders, getStaticPaths era ușor diferit:

 export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }

Strângeam o listă cu toate paginile pokemon pe care intenționam să le avem, mapam toate obiectele pokemon doar într-un string cu numele pokemonului și trimiteam înapoi obiectul { params } care îl transporta la getStaticProps . Soluția noastră a fost setată la false , deoarece dacă o rută nu se potrivea, am vrut ca fallback să afișeze o pagină 404: Not Found .

Puteți verifica ambele versiuni implementate pe Netlify:

  • Cu On-Demand Builder: cod, live
  • Complet static generat: cod, live

Codul este, de asemenea, open source pe Github și îl puteți implementa cu ușurință singur pentru a verifica timpii de construire. Și cu această coadă, trecem la următorul nostru subiect.

Build Times

După cum am menționat mai sus, demonstrația anterioară este de fapt o dovadă de concept , nimic nu este cu adevărat bun sau rău dacă nu putem măsura. Pentru micul nostru studiu, m-am dus la PokeAPI și am decis să prind toți pokemonii.

Din motive de reproductibilitate, am limitat cererea noastră (la 1000 ). Acestea nu sunt într-adevăr toate în cadrul API-ului, dar impune că numărul de pagini va fi același pentru toate versiunile, indiferent dacă lucrurile sunt actualizate în orice moment.

 export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }

Și apoi a lansat ambele versiuni în ramuri separate către Netlify, datorită implementărilor de previzualizare, acestea pot coexista practic în același mediu. Pentru a evalua cu adevărat diferența dintre ambele metode, abordarea ODB a fost extremă, nu au fost pre-rendate pagini pentru acea rută dinamică. Deși nu este recomandat pentru scenariile din lumea reală (veți dori să vă redați în prealabil rutele cu trafic intens), acesta marchează în mod clar gama de îmbunătățire a performanței la momentul construirii pe care o putem obține cu această abordare.

Strategie Număr de pagini Numărul de active Construiește timp Timp total de implementare
Complet Static Generat 1002 1005 2 minute și 32 de secunde 4 minute 15 secunde
Constructori la cerere 2 0 52 de secunde 52 de secunde

Paginile din mica noastră aplicație PokeDex sunt destul de mici, activele de imagine sunt foarte slabe, dar câștigurile în timpul de implementare sunt foarte semnificative. Dacă o aplicație are o cantitate medie până la mare de rute, cu siguranță merită să luați în considerare strategia ODB.

Vă face implementările mai rapide și, prin urmare, mai fiabile. Lovitura de performanță are loc doar la prima solicitare, de la cererea ulterioară și mai departe, pagina redată va fi stocată în cache chiar pe Edge, făcând performanța exact aceeași cu cea generată complet static.

Viitorul: randare persistentă distribuită

Chiar în aceeași zi, On-Demand Builders au fost anunțați și puși cu acces anticipat, Netlify și-a publicat și Solicitarea de comentarii privind redarea persistentă distribuită (DPR).

DPR este următorul pas pentru constructorii la cerere. Valorifică versiunile mai rapide utilizând astfel de pași de construcție asincroni și apoi memorând în cache activele până când sunt actualizate efectiv. Gata cu versiunile complete pentru site-ul web al unei pagini de 10.000. DPR le permite dezvoltatorilor să aibă un control deplin asupra construirii și implementării sistemelor prin memorarea în cache solidă și utilizarea generatorilor la cerere.

Imaginează-ți acest scenariu: un site de comerț electronic are 10.000 pagini de produse, ceea ce înseamnă că ar dura aproximativ 2 ore pentru a construi întreaga aplicație pentru implementare. Nu trebuie să argumentăm cât de dureros este acest lucru.

Cu DPR, putem seta primele 500 de pagini pentru a se construi pe fiecare implementare. Paginile noastre cu cel mai intens trafic sunt întotdeauna pregătite pentru utilizatorii noștri. Dar, suntem un magazin, adică fiecare secundă contează. Așadar, pentru celelalte 9500 de pagini, putem seta un cârlig post-build pentru a-și declanșa constructorii - implementând restul paginilor noastre în mod asincron și stocarea imediată în cache. Niciun utilizator nu a fost rănit, site-ul nostru a fost actualizat cu cea mai rapidă versiune posibilă și tot ce nu exista în cache a fost apoi stocat.

Concluzie

Deși multe dintre punctele de discuție din acest articol au fost conceptuale și implementarea urmează să fie definită, sunt încântat de viitorul Jamstack-ului. Progresele pe care le facem ca comunitate se învârt în jurul experienței utilizatorului final.

Ce părere aveți despre randarea persistentă distribuită? Ați încercat On-Demand Builders în aplicația dvs.? Spune-mi mai multe în comentarii sau sună-mă pe Twitter. Sunt chiar curioasa!

Referințe

  • „Un ghid complet pentru regenerarea statică incrementală (ISR) cu Next.js”, Lee Robinson
  • „Constructii mai rapide pentru site-uri mari de pe Netlify cu constructori la cerere”, Asavari Tayal, blogul Netlify
  • „Randarea persistentă distribuită: o nouă abordare Jamstack pentru versiuni mai rapide”, Matt Biilmann, blogul Netlify
  • „Răzare persistentă distribuită (DPR)”, Cassidy Williams, GitHub