Quebrando compilações volumosas com Netlify e Next.js
Publicados: 2022-03-10Este artigo foi gentilmente apoiado por nossos queridos amigos da Netlify, que são um grupo diversificado de talentos incríveis de todo o mundo e oferece uma plataforma para desenvolvedores web que multiplica a produtividade. Obrigado!
Uma das maiores dores de trabalhar com sites gerados estaticamente são as compilações cada vez mais lentas à medida que seu aplicativo cresce. Este é um problema inevitável que qualquer pilha enfrenta em algum momento e pode ocorrer de diferentes pontos, dependendo do tipo de produto com o qual você está trabalhando.
Por exemplo, se seu aplicativo tiver várias páginas (visualizações, rotas) ao gerar o artefato de implantação, cada uma dessas rotas se tornará um arquivo. Então, depois de atingir milhares, você começa a se perguntar quando pode implantar sem precisar planejar com antecedência. Esse cenário é comum em plataformas de e-commerce ou blogs, que já são uma grande parte da web, mas não toda. As rotas não são o único gargalo possível, no entanto.
Um aplicativo com muitos recursos também chegará a esse ponto de virada. Muitos geradores estáticos realizam a otimização de ativos para garantir a melhor experiência do usuário. Sem otimizações de compilação (compilações incrementais, armazenamento em cache, chegaremos a elas em breve), isso também se tornará incontrolável – pense em passar por todas as imagens em um site: redimensionar, excluir e/ou criar novos arquivos repetidamente. E uma vez feito tudo isso: lembre-se de que o Jamstack atende nossos aplicativos a partir das bordas da Content Delivery Network . Portanto, ainda precisamos mover as coisas do servidor em que foram compiladas para as bordas da rede.
Além de tudo isso, há também outro fato: os dados geralmente são dinâmicos, o que significa que, quando construímos nosso aplicativo e o implantamos, pode levar alguns segundos, alguns minutos ou até uma hora. Enquanto isso, o mundo continua girando e, se estivermos buscando dados de outro lugar, nosso aplicativo ficará desatualizado. Inaceitável! Construa novamente para atualizar!
Construa uma vez, atualize quando necessário
Resolver compilações volumosas tem sido uma prioridade para basicamente todas as plataformas, estruturas ou serviços do Jamstack por um tempo. Muitas soluções giram em torno de compilações incrementais. Na prática, isso significa que as compilações serão tão volumosas quanto as diferenças que carregam em relação à implantação atual.
Definir um algoritmo diff não é tarefa fácil. Para que o usuário final realmente se beneficie dessa melhoria, existem estratégias de invalidação de cache que devem ser consideradas. Para encurtar a história: não queremos invalidar o cache de uma página ou um ativo que não foi alterado.
Next.js surgiu com Regeneração Estática Incremental ( ISR ). Em essência, é uma maneira de declarar para cada rota com que frequência queremos que ela seja reconstruída. Sob o capô, simplifica muito o trabalho no lado do servidor. Porque cada rota (dinâmica ou não) se reconstruirá em um período de tempo específico e se encaixa perfeitamente no axioma Jamstack de invalidar o cache em cada compilação. Pense nisso como o cabeçalho max-age
, mas para rotas em seu aplicativo Next.js.
Para iniciar seu aplicativo, o ISR está a apenas uma propriedade de configuração de distância. No seu componente de rota (dentro do diretório /pages
) vá para o seu método getStaticProps
e adicione a chave revalidate
ao objeto de retorno:
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
O trecho acima garantirá que minha página seja reconstruída a cada hora e busque mais Pokémon para exibir.
Ainda recebemos as compilações em massa de vez em quando (ao emitir uma nova implantação). Mas isso nos permite dissociar o conteúdo do código, movendo o conteúdo para um Sistema de Gerenciamento de Conteúdo (CMS) podemos atualizar as informações em poucos segundos, independentemente do tamanho do nosso aplicativo. Adeus aos webhooks para atualizar erros de digitação!
Construtores sob demanda
A Netlify lançou recentemente o On-Demand Builders, que é sua abordagem para oferecer suporte ao ISR para Next.js, mas também funciona em estruturas como Eleventy e Nuxt. Na sessão anterior, estabelecemos que o ISR foi um grande passo em direção a tempos de construção mais curtos e abordamos uma parte significativa dos casos de uso. No entanto, as ressalvas estavam lá:
- Compilações completas em implantação contínua.
O estágio incremental acontece somente após a implantação e para os dados. Não é possível enviar código incrementalmente - Builds incrementais são um produto do tempo.
O cache é invalidado com base no tempo. Portanto, compilações desnecessárias podem ocorrer ou atualizações necessárias podem demorar mais, dependendo do período de revalidação definido no código.
A nova infraestrutura de implantação da Netlify permite que os desenvolvedores criem lógica para determinar quais partes de seu aplicativo serão criadas na implantação e quais partes serão adiadas (e como elas serão adiadas).
- Crítico
Nenhuma ação é necessária. Tudo o que você implantar será construído por push . - Diferido
Uma parte específica do aplicativo não será compilada na implantação, será adiada para ser compilada sob demanda sempre que ocorrer a primeira solicitação e, em seguida, será armazenada em cache como qualquer outro recurso de seu tipo.
Criando um construtor sob demanda
Antes de tudo, adicione um pacote netlify/functions como um devDependency
ao seu projeto:
yarn add -D @netlify/functions
Feito isso, é o mesmo que criar uma nova Função Netlify. Se você não definiu um diretório específico para eles, vá para netlify/functions/
e crie um arquivo de qualquer nome para o seu construtor.
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)
Como você pode ver no trecho acima, o construtor sob demanda se separa de uma função Netlify regular porque envolve seu manipulador dentro de um método builder()
. Este método conecta nossa função às tarefas de construção. E isso é tudo que você precisa para ter uma parte do seu aplicativo adiada para construção apenas quando necessário. Pequenas compilações incrementais desde o início!
Next.js no Netlify
Para construir um aplicativo Next.js no Netlify existem 2 plugins importantes que se deve adicionar para ter uma melhor experiência em geral: Netlify Plugin Cache Next.js e Essential Next-on-Netlify. O primeiro armazena em cache seu NextJS com mais eficiência e você precisa adicioná-lo você mesmo, enquanto o último faz alguns pequenos ajustes na forma como a arquitetura Next.js é construída para que se ajuste melhor à do Netlify e esteja disponível por padrão para cada novo projeto que o Netlify possa identificar. usando Next.js.
Construtores sob demanda com Next.js
Desempenho de construção, desempenho de implantação, armazenamento em cache, experiência do desenvolvedor. Todos esses são tópicos muito importantes, mas são muito – e leva tempo para serem configurados corretamente. Então chegamos àquela velha discussão sobre focar na experiência do desenvolvedor em vez da experiência do usuário. Que é o momento em que as coisas vão para um ponto escondido em uma lista de pendências para serem esquecidas. Na verdade.
Netlify tem suas costas. Em apenas algumas etapas, podemos aproveitar todo o poder do Jamstack em nosso aplicativo Next.js. É hora de arregaçar as mangas e juntar tudo agora.
Definindo caminhos pré-renderizados
Se você já trabalhou com geração estática dentro do Next.js, provavelmente já ouviu falar do método getStaticPaths
. Este método destina-se a rotas dinâmicas (modelos de página que renderizarão uma ampla variedade de páginas). Sem me deter muito nos meandros deste método, é importante notar que o tipo de retorno é um objeto com 2 chaves, como em nossa Prova de Conceito este será [Pokemon] arquivo de rota dinâmica:
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
array
que executa todos os caminhos correspondentes a esta rota que serão pré-renderizados -
fallback
tem 3 valores possíveis: blocking,true
oufalse
No nosso caso, nosso getStaticPaths
está determinando:
- Nenhum caminho será pré-renderizado;
- Sempre que essa rota for chamada, não serviremos um modelo de fallback, renderizaremos a página sob demanda e deixaremos o usuário esperando, bloqueando o aplicativo de fazer qualquer outra coisa.
Ao usar o On-Demand Builders, certifique-se de que sua estratégia de fallback atenda aos objetivos do seu aplicativo, os documentos oficiais Next.js: fallback docs são muito úteis.
Antes dos construtores sob demanda, nosso getStaticPaths
era um pouco diferente:
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
Estávamos reunindo uma lista de todas as páginas de pokemon que pretendíamos ter, mapeando todos os objetos pokemon
para apenas uma string
com o nome do pokemon e encaminhando o objeto { params }
que o carregava para getStaticProps
. Nosso fallback
foi definido como false
porque, se uma rota não fosse uma correspondência, queríamos que o Next.js lançasse uma página 404: Not Found
.
Você pode verificar as duas versões implantadas no Netlify:
- Com o On-Demand Builder: código, ao vivo
- Totalmente gerado estático: código, ao vivo
O código também é de código aberto no Github e você pode implantá-lo facilmente para verificar os tempos de compilação. E com essa fila, deslizamos para o nosso próximo tópico.
Tempos de construção
Como mencionado acima, a demonstração anterior é na verdade uma Prova de Conceito , nada é realmente bom ou ruim se não pudermos medir. Para nosso pequeno estudo, fui até a PokeAPI e decidi pegar todos os pokemons.
Para fins de reprodutibilidade, limitei nossa solicitação (para 1000
). Eles não estão realmente todos dentro da API, mas impõem que o número de páginas seja o mesmo para todas as compilações, independentemente de as coisas serem atualizadas a qualquer 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, em seguida, disparou ambas as versões em branches separados para o Netlify, graças às implantações de visualização, elas podem coexistir basicamente no mesmo ambiente. Para realmente avaliar a diferença entre os dois métodos, a abordagem ODB foi extrema, nenhuma página foi pré-renderizada para essa rota dinâmica. Embora não seja recomendado para cenários do mundo real (você vai querer pré-renderizar suas rotas de tráfego intenso), ele marca claramente a faixa de melhoria de desempenho em tempo de construção que podemos alcançar com essa abordagem.
Estratégia | Número de páginas | Número de ativos | Tempo de construção | Tempo total de implantação |
---|---|---|---|---|
Gerado totalmente estático | 1002 | 1005 | 2 minutos 32 segundos | 4 minutos 15 segundos |
Construtores sob demanda | 2 | 0 | 52 segundos | 52 segundos |
As páginas em nosso pequeno aplicativo PokeDex são bem pequenas, os recursos de imagem são muito enxutos, mas os ganhos no tempo de implantação são muito significativos. Se um aplicativo tiver uma quantidade média a grande de rotas, definitivamente vale a pena considerar a estratégia ODB.
Isso torna suas implantações mais rápidas e, portanto, mais confiáveis. O impacto no desempenho só acontece na primeira solicitação, a partir da solicitação subsequente e em diante a página renderizada será armazenada em cache diretamente no Edge, tornando o desempenho exatamente igual ao Gerado Totalmente Estático.
O futuro: renderização persistente distribuída
No mesmo dia, os On-Demand Builders foram anunciados e colocados em acesso antecipado, a Netlify também publicou seu Request for Comments on Distributed Persistent Rendering (DPR).
DPR é o próximo passo para construtores sob demanda. Ele capitaliza em compilações mais rápidas usando essas etapas de compilação assíncrona e, em seguida, armazenando os ativos em cache até que eles sejam realmente atualizados. Não há mais compilações completas para o site de uma página de 10k. O DPR capacita os desenvolvedores a um controle total sobre os sistemas de compilação e implantação por meio de um cache sólido e do uso de construtores sob demanda.
Imagine este cenário: um site de comércio eletrônico tem 10 mil páginas de produtos, isso significa que levaria algo em torno de 2 horas para construir todo o aplicativo para implantação. Não precisamos discutir o quão doloroso isso é.
Com o DPR, podemos definir as 500 principais páginas para criar em cada implantação. Nossas páginas de tráfego mais pesado estão sempre prontas para nossos usuários. Mas, somos uma loja, ou seja, cada segundo conta. Assim, para as outras 9.500 páginas, podemos definir um gancho pós-compilação para acionar seus construtores — implantando o restante de nossas páginas de forma assíncrona e com armazenamento imediato em cache. Nenhum usuário foi ferido, nosso site foi atualizado com a compilação mais rápida possível e tudo o mais que não existia no cache foi armazenado.
Conclusão
Embora muitos dos pontos de discussão neste artigo tenham sido conceituais e a implementação deva ser definida, estou empolgado com o futuro do Jamstack. Os avanços que estamos fazendo como comunidade giram em torno da experiência do usuário final.
Qual é a sua opinião sobre a renderização persistente distribuída? Você já experimentou Construtores sob demanda em seu aplicativo? Deixe-me saber mais nos comentários ou me chame no Twitter. Estou realmente curioso!
Referências
- “Um guia completo para regeneração estática incremental (ISR) com Next.js”, Lee Robinson
- “Construções mais rápidas para sites grandes na Netlify com construtores sob demanda”, Asavari Tayal, blog da Netlify
- “Renderização persistente distribuída: uma nova abordagem Jamstack para compilações mais rápidas”, Matt Biilmann, Netlify Blog
- “Renderização persistente distribuída (DPR)”, Cassidy Williams, GitHub