Scomporre build ingombranti con Netlify e Next.js

Pubblicato: 2022-03-10
Riepilogo rapido ↬ La generazione statica è ottima per le prestazioni, fino a quando l'app non diventa troppo grande e i tempi di costruzione aumentano alle stelle. Oggi daremo un'occhiata a come i nuovi On-Demand Builder di Netlify possono risolverlo. Inoltre, lo abbiniamo alla rigenerazione statica incrementale di Next.js per la migliore esperienza di utenti e sviluppatori. E, naturalmente, confronta quei risultati!

Uno dei maggiori problemi nel lavorare con i siti Web generati staticamente è la build sempre più lenta man mano che la tua app cresce. Questo è un problema inevitabile che qualsiasi pila deve affrontare a un certo punto e può colpire da punti diversi a seconda del tipo di prodotto con cui stai lavorando.

Ad esempio, se l'app ha più pagine (viste, percorsi) durante la generazione dell'elemento di distribuzione, ciascuno di questi percorsi diventa un file. Quindi, una volta raggiunti i migliaia, inizi a chiederti quando puoi eseguire il deployment senza dover pianificare in anticipo. Questo scenario è comune su piattaforme di e-commerce o blog, che sono già una grande porzione del web ma non tutto. Tuttavia, le rotte non sono l'unico collo di bottiglia possibile.

Anche un'app ricca di risorse alla fine raggiungerà questo punto di svolta. Molti generatori statici eseguono l'ottimizzazione delle risorse per garantire la migliore esperienza utente. Senza le ottimizzazioni delle build (costruzioni incrementali, memorizzazione nella cache, arriveremo presto a quelle) anche questo alla fine diventerà ingestibile: pensa a esaminare tutte le immagini in un sito Web: ridimensionare, eliminare e/o creare nuovi file più e più volte. E una volta fatto tutto: ricorda che Jamstack serve le nostre app dai bordi della Content Delivery Network . Quindi abbiamo ancora bisogno di spostare le cose dal server su cui sono state compilate ai bordi della rete.

Architettura del servizio generale Jamstack
Architettura del servizio generale Jamstack (anteprima grande)

Oltre a tutto ciò, c'è anche un altro fatto: i dati sono spesso dinamici, il che significa che quando creiamo la nostra app e la distribuiamo, potrebbero essere necessari alcuni secondi, alcuni minuti o anche un'ora. Nel frattempo, il mondo continua a girare e se stiamo recuperando dati da altrove, la nostra app è destinata a diventare obsoleta. Inaccettabile! Costruisci di nuovo per aggiornare!

Costruisci una volta, aggiorna quando necessario

Per un po' di tempo, la risoluzione di build voluminose è stata al centro dell'attenzione praticamente per ogni piattaforma, framework o servizio Jamstack. Molte soluzioni ruotano attorno a build incrementali. In pratica, ciò significa che le build saranno ingombranti quanto le differenze che comportano rispetto all'attuale distribuzione.

Tuttavia, definire un algoritmo diff non è un compito facile. Affinché l' utente finale possa effettivamente beneficiare di questo miglioramento, ci sono strategie di invalidamento della cache che devono essere considerate. Per farla breve: non vogliamo invalidare la cache per una pagina o una risorsa che non è cambiata.

Next.js ha creato la rigenerazione statica incrementale ( ISR ). In sostanza, è un modo per dichiarare per ogni percorso quante volte vogliamo che venga ricostruito. Sotto il cofano, semplifica molto il lavoro sul lato server. Perché ogni percorso (dinamico o meno) si ricostruirà da solo in un intervallo di tempo specifico e si adatta perfettamente all'assioma Jamstack di invalidare la cache su ogni build. Pensala come l'intestazione max-age ma per i percorsi nella tua app Next.js.

Per avviare la tua applicazione, ISR è solo una proprietà di configurazione di distanza. Sul tuo componente di percorso (all'interno della directory /pages ) vai al tuo metodo getStaticProps e aggiungi la chiave di revalidate all'oggetto restituito:

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

Il frammento di cui sopra assicurerà che la mia pagina venga ricostruita ogni ora e recuperi più Pokemon da visualizzare.

Ogni tanto riceviamo ancora build in blocco (quando si pubblica una nuova distribuzione). Ma questo ci consente di disaccoppiare il contenuto dal codice, spostando il contenuto in un Content Management System (CMS) possiamo aggiornare le informazioni in pochi secondi, indipendentemente dalle dimensioni della nostra applicazione. Addio ai webhook per l'aggiornamento degli errori di battitura!

Costruttori su richiesta

Netlify ha recentemente lanciato On-Demand Builders, che è il loro approccio al supporto di ISR ​​per Next.js, ma funziona anche su framework inclusi Eleventy e Nuxt. Nella sessione precedente, abbiamo stabilito che l'ISR è stato un grande passo avanti verso tempi di costruzione più brevi e abbiamo affrontato una parte significativa dei casi d'uso. Tuttavia, le avvertenze c'erano:

  1. Full si basa sulla distribuzione continua.
    La fase incrementale avviene solo dopo la distribuzione e per i dati. Non è possibile spedire il codice in modo incrementale
  2. Le build incrementali sono un prodotto del tempo.
    La cache viene invalidata in base al tempo. Pertanto potrebbero verificarsi build non necessarie o gli aggiornamenti necessari potrebbero richiedere più tempo a seconda del periodo di riconvalida impostato nel codice.

La nuova infrastruttura di distribuzione di Netlify consente agli sviluppatori di creare una logica per determinare quali parti della loro app si baseranno sulla distribuzione e quali parti verranno rinviate (e come verranno rinviate).

  • Critico
    Non è necessaria alcuna azione. Tutto ciò che distribuirai sarà basato su push .
  • Differito
    Una parte specifica dell'app non verrà compilata al momento della distribuzione, verrà rinviata per la creazione su richiesta ogni volta che si verifica la prima richiesta, quindi verrà memorizzata nella cache come qualsiasi altra risorsa del suo tipo.

Creazione di un builder On-Demand

Prima di tutto, aggiungi un pacchetto netlify/functions come devDependency al tuo progetto:

 yarn add -D @netlify/functions

Una volta fatto, è come creare una nuova funzione Netlify. Se non hai impostato una directory specifica per loro, vai su netlify/functions/ e crea un file con qualsiasi nome per il tuo builder.

 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)

Come puoi vedere dallo snippet sopra, il builder su richiesta si divide da una normale funzione Netlify perché racchiude il suo gestore all'interno di un metodo builder() . Questo metodo collega la nostra funzione alle attività di compilazione. E questo è tutto ciò di cui hai bisogno per rimandare una parte della tua domanda per la costruzione solo quando necessario. Piccole build incrementali fin dall'inizio!

Next.js su Netlify

Per creare un'app Next.js su Netlify ci sono 2 plugin importanti che uno dovrebbe aggiungere per avere una migliore esperienza in generale: Netlify Plugin Cache Next.js e Essential Next-on-Netlify. Il primo memorizza nella cache NextJS in modo più efficiente e devi aggiungerlo tu stesso, mentre il secondo apporta alcune lievi modifiche al modo in cui l'architettura Next.js è costruita in modo che si adatti meglio a Netlify ed è disponibile per impostazione predefinita per ogni nuovo progetto che Netlify può identificare è utilizzando Next.js.

Costruttori on demand con Next.js

Creazione di prestazioni, distribuzione delle prestazioni, memorizzazione nella cache, esperienza degli sviluppatori. Questi sono tutti argomenti molto importanti, ma è molto e richiede tempo per essere configurati correttamente. Quindi arriviamo a quella vecchia discussione sul concentrarsi sull'esperienza dello sviluppatore anziché sull'esperienza utente. Qual è il momento in cui le cose vanno in un punto nascosto in un arretrato da dimenticare. Non proprio.

Netlify ti copre le spalle. In pochi passaggi, possiamo sfruttare tutta la potenza di Jamstack nella nostra app Next.js. È ora di rimboccarsi le maniche e mettere tutto insieme ora.

Definizione di percorsi pre-renderizzati

Se hai già lavorato con la generazione statica all'interno di Next.js, probabilmente hai sentito parlare del metodo getStaticPaths . Questo metodo è destinato a percorsi dinamici (modelli di pagina che eseguiranno il rendering di un'ampia gamma di pagine). Senza soffermarsi troppo sulla complessità di questo metodo, è importante notare che il tipo restituito è un oggetto con 2 chiavi, come nel nostro Proof-of-Concept questo sarà un file di percorso dinamico [Pokemon]:

 export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
  • paths è un array che esegue tutti i percorsi corrispondenti a questo percorso che verrà pre-renderizzato
  • fallback ha 3 possibili valori: blocking, true o false

Nel nostro caso, il nostro getStaticPaths determina:

  1. Nessun percorso verrà pre-renderizzato;
  2. Ogni volta che viene chiamato questo percorso, non serviremo un modello di fallback, renderemo la pagina su richiesta e manterremo l'utente in attesa, impedendo all'app di fare qualsiasi altra cosa.

Quando utilizzi On-Demand Builders, assicurati che la tua strategia di fallback soddisfi gli obiettivi della tua app, i documenti ufficiali di Next.js: i documenti di fallback sono molto utili.

Prima di On-Demand Builders, il nostro getStaticPaths era leggermente diverso:

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

Stavamo raccogliendo un elenco di tutte le pagine pokemon che intendevamo avere, mappavamo tutti gli oggetti pokemon solo su una string con il nome del pokemon e inoltravamo restituendo l'oggetto { params } che lo trasportava a getStaticProps . Il nostro fallback era impostato su false perché se una route non corrispondeva, volevamo che Next.js generasse una pagina 404: Not Found .

Puoi controllare entrambe le versioni distribuite su Netlify:

  • Con On-Demand Builder: codice, live
  • Generato completamente statico: codice, live

Il codice è anche open source su Github e puoi facilmente distribuirlo da solo per controllare i tempi di compilazione. E con questa coda, passiamo al nostro prossimo argomento.

Costruisci tempi

Come accennato in precedenza, la demo precedente è in realtà una Proof-of-Concept , niente è veramente buono o cattivo se non possiamo misurare. Per il nostro piccolo studio, sono passato alla PokeAPI e ho deciso di catturare tutti i pokemon.

Per motivi di riproducibilità, ho limitato la nostra richiesta (a 1000 ). Questi non sono davvero tutti all'interno dell'API, ma impone che il numero di pagine sarà lo stesso per tutte le build indipendentemente dal fatto che le cose vengano aggiornate in qualsiasi momento.

 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, } }

E quindi ha attivato entrambe le versioni in rami separati su Netlify, grazie alle distribuzioni in anteprima possono coesistere praticamente nello stesso ambiente. Per valutare davvero la differenza tra entrambi i metodi, l'approccio ODB era estremo, nessuna pagina è stata pre-renderizzata per quel percorso dinamico. Sebbene non sia raccomandato per scenari del mondo reale (vorrai eseguire il pre-rendering dei tuoi percorsi ad alto traffico), segna chiaramente la gamma di miglioramenti delle prestazioni in fase di costruzione che possiamo ottenere con questo approccio.

Strategia Numero di pagine Numero di beni Costruisci il tempo Tempo di implementazione totale
Generato completamente statico 1002 1005 2 minuti 32 secondi 4 minuti 15 secondi
Costruttori su richiesta 2 0 52 secondi 52 secondi

Le pagine della nostra piccola app PokeDex sono piuttosto piccole, le risorse dell'immagine sono molto snelle, ma i guadagni in termini di tempo di implementazione sono molto significativi. Se un'app ha un numero di percorsi medio-alto, vale sicuramente la pena considerare la strategia ODB.

Rende le tue distribuzioni più veloci e quindi più affidabili. Il successo delle prestazioni si verifica solo alla prima richiesta, dalla richiesta successiva in poi la pagina sottoposta a rendering verrà memorizzata nella cache direttamente sull'Edge, rendendo le prestazioni esattamente identiche a quelle generate completamente statiche.

Il futuro: rendering persistente distribuito

Lo stesso giorno, gli On-Demand Builders sono stati annunciati e messi in accesso anticipato, Netlify ha anche pubblicato la sua Request for Comments on Distributed Persistent Rendering (DPR).

DPR è il passaggio successivo per i costruttori on-demand. Sfrutta le build più veloci utilizzando tali passaggi di costruzione asincroni e quindi memorizzando nella cache le risorse fino a quando non vengono effettivamente aggiornate. Niente più build complete per un sito Web di 10.000 pagine. DPR consente agli sviluppatori di avere un controllo completo sulla creazione e l'implementazione dei sistemi attraverso una solida memorizzazione nella cache e utilizzando On-Demand Builders.

Immagina questo scenario: un sito Web di e-commerce ha 10.000 pagine di prodotti, ciò significa che ci vorrebbero circa 2 ore per creare l'intera applicazione per la distribuzione. Non abbiamo bisogno di discutere di quanto sia doloroso.

Con DPR, possiamo impostare le prime 500 pagine da costruire su ogni distribuzione. Le nostre pagine con il traffico più intenso sono sempre pronte per i nostri utenti. Ma noi siamo un negozio, cioè ogni secondo conta. Quindi, per le altre 9500 pagine, possiamo impostare un hook post-compilazione per attivare i loro builder, distribuendo il resto delle nostre pagine in modo asincrono e memorizzando immediatamente nella cache. Nessun utente è rimasto ferito, il nostro sito Web è stato aggiornato con la build più veloce possibile e tutto il resto che non esisteva nella cache è stato quindi archiviato.

Conclusione

Sebbene molti dei punti di discussione in questo articolo fossero concettuali e l'implementazione debba essere definita, sono entusiasta del futuro di Jamstack. I progressi che stiamo facendo come community ruotano attorno all'esperienza dell'utente finale.

Qual è la tua opinione sul rendering persistente distribuito? Hai provato On-Demand Builder nella tua applicazione? Fammi sapere di più nei commenti o chiamami su Twitter. sono davvero curioso!

Riferimenti

  • "Una guida completa alla rigenerazione statica incrementale (ISR) con Next.js", Lee Robinson
  • "Costruzioni più veloci per siti di grandi dimensioni su Netlify con costruttori on-demand", Asavari Tayal, Blog Netlify
  • "Rendering persistente distribuito: un nuovo approccio Jamstack per build più veloci", Matt Biilmann, Blog Netlify
  • "Rendering persistente distribuito (DPR)", Cassidy Williams, GitHub