Como construímos uma plataforma de publishers com Next.js, GraphQL e Vercel

Uma solução de CMS + API + Site escalável utilizando uma combinação de tecnologias modernas em todas as pontas.

Este post foi originalmente escrito (em inglês) no blog da On2, empresa parceira da BrazilJS 😍:
https://blog.on2.dev/how-we-built-a-publisher-platform-with-next-js-graphql-and-vercel-16ef50c9a00d

Quer saber mais sobre a On2? Acompanhe a BrazilJS que em breve teremos mais conteúdo por aqui.
Ah, a On2 está com vagas abertas!
https://www.linkedin.com/company/on2


A história de fundo

Em 2020 recebemos uma missão da Alright, um de nossos principais parceiros: criar um CMS alternativo e um site otimizado voltado para pequenos e médios publishers.
A Alright é uma AdTech que conecta publishers e marcas através de tecnologias proprietárias.
Eles têm mais de 300 publishes em todo o Brasil, o que significa que a solução deveria ser construída pensando na escalabilidade - e não só nisso - deveríamos ter uma abordagem mais genérica para a stack tecnológica.

Você pode estar se perguntando, mas e o WordPress?
Bom, alguns publishers já usam WordPress e houveram alguns gargalos na integração da tecnologia Alright com esses sites, especialmente porque as instâncias WP não estão sendo executadas na infraestrutura da Alright.

Atingimos o objetivo. Nossa equipe sugeriu uma API GraphQL genérica para lidar não apenas com os dados do CMS, mas também com todos os dados genéricos que a Alright tem em outras aplicações e que eventualmente seriam necessários.
Amamos o PostgreSQL, então aqui a decisão foi fácil. Se precisarmos de um banco de dados relacional com dados estruturados, por que não usar o "Banco de dados relacional de código aberto mais avançado do mundo"?
No início queríamos uma solução rápida para GraphQL, então ao invés de construir todos os schemes, resolvers, mutations, etc, à mão, escolhemos o PostGraphile.
Há muitos benefícios com uma solução que pega um banco de dados e gera uma API, mas como você pode imaginar, há alguns problemas também (vamos cobrir isso em um post futuro).

Feito? Não. E quanto à interface? Aqui entra o Next.js.
O CMS em si não foi construído com Next.js, pois é uma aplicação que não precisa de desempenho, SEO e não há necessidade de uma estratégia para lidar com vários usuários simultâneos, então construímos SPA React “simples”.
Como nosso principal objetivo era construir um site que priorizasse o desempenho para publishers, apostamos no Next.js e em suas promessas. Naquela época, o Next.js ainda não estava no nível de maturidade que alcançou hoje, que é realmente impressionante.
Nós o adotamos para o projeto que chamamos de Alright Publisher Viewer, que era a parte de visualização de um ecossistema crescente de pequenos e grandes pedaços de software.

Depois do deploy

Tecnicamente falando e do ponto de vista dev, o Next.js foi ótimo.
Além da aplicação Alright Publisher Viewer, agora tínhamos o Alright Publisher Editor (React SPA simples), Alright GraphQL API, Alright Sqitch (relacionado ao PostgreSQL) e algumas outras soluções auxiliares.
A tecnologia por trás do produto não era trivial, mas no final funcionou muito bem.
Havia uma condição adicional e complicada aqui nesta fase: Estávamos criando e gerenciando toda a infraestrutura necessária. Isso significa que nossa equipe estava lidando com a Amazon diariamente. EC2, Code Deploy, S3, Cloud Front, RDS.
Funcionava bem, mas poderia ser muito melhor, especialmente quando olhamos para a experiência dev, deploys para produção, testes, homologação, etc.
Logo após nosso primeiro lançamento, percebemos algo estranho. O Alright Publisher Viewer, desenvolvido com Next.js, não chegou nem perto do resultado que esperávamos.
Depois de toda a luta para adaptar o app Next.js para rodar bem na Amazon, todos os dias gastos na arquitetura e agora nosso app de produção estava apenas OK.
Definitivamente havia algo errado, nossa equipe pensou. E de fato havia.

Mudando a estratégia

O principal problema estava relacionado ao fato de como o Next.js funciona e como implementamos a lógica da parte da visualização.
Se você tiver um app que precisa buscar dados, o que você faz?
Essa foi fácil. Como precisamos de desempenho e SEO, a abordagem do Next.js de renderização do lado do servidor parece ser a solução.
Vimos a documentação e boom! A renderização do lado do servidor estava pronta. Nossa camada de visão estava se comunicando muito bem com a API GraphQL.

export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}

O problema é que isso não é muito rápido. Implementamos a aplicação de forma que cada vez que alguém pedisse uma página, haveria um pedido de API e a página seria renderizada no servidor.
Claro, uma camada de cache está faltando, você provavelmente está pensando.
Nós sabíamos disso, mas sabíamos que havia algo ainda melhor e aqui entra a abordagem Next.js de páginas estáticas.

export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}

Quando você usa getStaticProps, o Next.js pré-renderizará a página no momento da construção usando os props retornados por getStaticProps.
Fantástico! Estamos quase lá.
Para fazer tudo funcionar, temos que combinar getStaticProps e getStaticPaths.

export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: true or false // See the "fallback" section below
};
}

A aplicação possui rotas dinâmicas. Lembre-se de que a Alright oferece uma ferramenta para publishers, então é basicamente um site de notícias com muitas páginas e artigos.
No Next.js precisamos definir uma lista de caminhos que devem ser renderizados para HTML no momento do build.
Se você exportar uma função assíncrona chamada getStaticPaths de uma página que usa rotas dinâmicas, o Next.js irá pré-renderizar estaticamente todos os caminhos especificados por getStaticPaths.

Precisamos apontar um detalhe aqui. Publishers podem ter toneladas de artigos/páginas, e foi o caso de nosso primeiro publisher rodando na plataforma.
Não podemos gerar todas as páginas no momento do build. Imagine milhões de páginas sendo geradas para cada build, isso não é viável.
O Next.js tem uma boa solução para isso. Com getStaticPaths e getStaticProps, podemos limitar quantas páginas queremos gerar no momento do build.
Mas ainda tem mais. O que acontece com as páginas que ainda não foram geradas?
Com o fallback true, o comportamento do getStaticProps muda.
Os paths que não foram gerados no momento do build não resultarão em uma página 404. Em vez disso, o Next.js servirá uma versão de "fallback" da página na primeira solicitação para esse path.
Em segundo plano, o Next.js irá gerar estaticamente o HTML e o JSON solicitado. Isso inclui a execução de getStaticProps.

Resumindo, com esta abordagem podemos ter quantas páginas quisermos pré-geradas e em cache e quando uma nova página for solicitada, o Next.js irá carregar todos os dados necessários e armazená-los em cache.

Indo para a Vercel

Agora que temos a combinação perfeita de recursos no lado do Next.js, só falta uma peça: Vercel.
A Vercel combina a melhor experiência dev com um foco obsessivo no desempenho do usuário final.
Na verdade, já tínhamos a app antiga (antes da refatoração estática) rodando na Vercel.
Começamos com pequenos passos e uma coisa que queríamos era toda a experiência dev na implantação e construção de apps que a Vercel oferece.
Adoramos as implementações automatizadas via Git (incluindo diferentes branches) e a poderosa interface no navegador e a CLI.
Como já tínhamos a nova app em execução na Vercel com várias branches, mover tudo para a produção não foi difícil.
Com algumas mudanças no DNS, algumas variáveis de ambiente e algumas pequenas otimizações, alcançamos nosso objetivo.

Outro recurso incrível da Vercel é a Edge Network.
A Edge fica entre a Internet e as implantações de nossas aplicações. Esta é basicamente a nossa CDN.
Esta camada é responsável por rotear solicitações para as funções serverless corretas ou arquivos estáticos.
A Vercel armazena em cache o conteúdo na borda para fornecer dados o mais rápido possível.

Conclusão e próximas etapas

Claro que houveram algumas etapas extras e muita pesquisa, mas no final agora temos uma aplicação complexa instalada e funcionando. E mais importante, executando de forma escalonável.
Toda a infraestrutura é automatizada, a experiência dev é ótima e novos recursos podem ser feitos sem problemas.
Vercel e Next.js são ótimos para esse tipo de solução, mas com certeza, primeiro você precisa saber o que deseja e quais são as diferentes abordagens que a plataforma e o framework oferecem.
Ainda precisamos migrar alguns subprojetos, mas o gargalo já foi removido com sucesso.
Nosso plano é ter todas apps em execução na Vercel e deixar apenas o banco de dados no Amazon RDS.
Esse trabalho já foi feito e está trabalhando na fase de teste.