Scomporre build ingombranti con Netlify e Next.js
Pubblicato: 2022-03-10Questo articolo è stato gentilmente supportato dai nostri cari amici di Netlify che sono un gruppo eterogeneo di incredibili talenti provenienti da tutto il mondo e offre una piattaforma per sviluppatori web che moltiplica la produttività. Grazie!
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.
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:
- 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 - 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
è unarray
che esegue tutti i percorsi corrispondenti a questo percorso che verrà pre-renderizzato -
fallback
ha 3 possibili valori: blocking,true
ofalse
Nel nostro caso, il nostro getStaticPaths
determina:
- Nessun percorso verrà pre-renderizzato;
- 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