<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Artigos - Roberto Marçal</title><link>https://cmarcal-roberto.dev/pt-br/articles/</link><description>Frontend Engineer na construção de produtos digitais em escala, com profundidade em arquitetura, performance e qualidade, e liderando a adoção de IA no desenvolvimento de software.</description><generator>Hugo</generator><language>pt-BR</language><lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://cmarcal-roberto.dev/pt-br/articles/index.xml" rel="self" type="application/rss+xml"/><item><title>Next.js vs TanStack Start direto na AWS</title><link>https://cmarcal-roberto.dev/pt-br/articles/nextjs-vs-tanstack-start-on-aws/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate><guid>https://cmarcal-roberto.dev/pt-br/articles/nextjs-vs-tanstack-start-on-aws/</guid><description>O que muda de verdade quando o mesmo app SSR roda na mesma infraestrutura da AWS.</description><content:encoded>&lt;p&gt;Esta é a versão em texto de um artigo interativo. Leia no site: &lt;a href="https://cmarcal-roberto.dev/pt-br/articles/nextjs-vs-tanstack-start-on-aws/"&gt;https://cmarcal-roberto.dev/pt-br/articles/nextjs-vs-tanstack-start-on-aws/&lt;/a&gt;&lt;/p&gt;&lt;div class="ax"&gt;
&lt;p class="meta"&gt;S3, CloudFront, Route 53 e Lambda, sem Vercel&lt;/p&gt;
&lt;p class="sub"&gt;O que muda de verdade quando o mesmo app SSR roda na mesma infraestrutura da AWS.&lt;/p&gt;
&lt;p class="lead"&gt;Coloque qualquer um dos dois atrás do CloudFront e, de fora, tudo parece igual. A diferença está nos detalhes: onde o seu código roda, como os dados são alterados, quanto tempo um conteúdo novo leva para ir ao ar e quanto trabalho de manutenção sobra para você quando algo quebra.&lt;/p&gt;
&lt;div class="tldr"&gt;
&lt;div class="card next"&gt;&lt;h3&gt;Next.js via OpenNext&lt;/h3&gt;&lt;p&gt;Já traz mais coisa pronta (ISR, otimização de imagens, middleware), mas, para isso, você precisa de mais peças na AWS e de um adaptador da comunidade.&lt;/p&gt;&lt;/div&gt;
&lt;div class="card tan"&gt;&lt;h3&gt;TanStack Start via Nitro&lt;/h3&gt;&lt;p&gt;Menos peças na AWS e uma separação mais clara entre servidor e cliente. Em compensação, traz menos recursos prontos, não tem um alvo oficial na AWS e ainda era um release candidate em novembro de 2025.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p class="note"&gt;Tudo aqui tem link para a fonte. As caixas marcadas como Opinião ou Inferência são leitura minha.&lt;/p&gt;
&lt;ol class="toc" style="list-style:none"&gt;&lt;li&gt;&lt;a href="#flow"&gt;1. Do merge do PR até a página no navegador&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#adds"&gt;2. O que cada framework adiciona&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#code"&gt;3. Onde o seu código roda&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#fresh"&gt;4. Como o conteúdo fica atualizado&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#routes"&gt;5. Estratégia de renderização por rota&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#cwv"&gt;6. Core Web Vitals&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#ops"&gt;7. O que fica com você quando algo quebra&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#lambda"&gt;8. Indo mais fundo: Lambda e mutations&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id="flow"&gt;1. Do merge do PR até a página no navegador&lt;/h2&gt;&lt;p&gt;Antes de comparar os frameworks, vamos acompanhar qualquer app SSR desde o momento em que um pull request é mergeado. Clique pelos passos ou aperte o Play.&lt;/p&gt;
&lt;div class="stepper" data-stepper data-play="▶ Play" data-pause="❚❚ Pausar"&gt;
&lt;p&gt;&lt;em&gt;Fluxo de deploy e de requisição: repositório, CI, S3, CloudFront, Route 53, servidor&lt;/em&gt;&lt;/p&gt;
&lt;ol class="steps"&gt;
&lt;h4&gt;Construa o app SSR&lt;/h4&gt;&lt;p&gt;Você tem um projeto Next.js ou TanStack Start no git. As páginas são renderizadas primeiro no servidor e depois hidratadas no navegador, para ficarem interativas.&lt;/p&gt;
&lt;h4&gt;Faça o merge do PR na main&lt;/h4&gt;&lt;p&gt;O merge na main dispara o pipeline (o GitHub Actions, por exemplo). Ainda não há nada no ar. O papel do pipeline é transformar o seu código-fonte em algo que a AWS consiga servir.&lt;/p&gt;
&lt;h4&gt;Faça o build do projeto&lt;/h4&gt;&lt;p&gt;O build gera dois tipos de saída: os &lt;b&gt;arquivos estáticos&lt;/b&gt; (JS, CSS e imagens com hash no nome) e o &lt;b&gt;código de servidor&lt;/b&gt;, que renderiza as páginas.&lt;/p&gt;&lt;p&gt;A saída do Next.js precisa ser adaptada para rodar na AWS, e quem faz isso é o &lt;a href="https://opennext.js.org/aws"&gt;OpenNext&lt;/a&gt;. O TanStack Start gera a saída do servidor pelo &lt;a href="https://nitro.build/deploy/providers/aws"&gt;Nitro&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Salve os arquivos estáticos no S3&lt;/h4&gt;&lt;p&gt;Os arquivos estáticos vão para um bucket S3 privado. O nome de cada arquivo inclui um hash do conteúdo, então eles podem ficar em cache por muito tempo. O Next.js, por exemplo, marca esse tipo de asset como &lt;code&gt;immutable&lt;/code&gt;, com max-age de um ano (&lt;a href="https://nextjs.org/docs/app/guides/self-hosting"&gt;docs&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;O CloudFront entrega esses arquivos&lt;/h4&gt;&lt;p&gt;O S3 só armazena arquivos. O CloudFront fica na frente dele e mantém cópias em edge locations perto dos usuários. Você também configura o bucket para que só o CloudFront consiga lê-lo.&lt;/p&gt;
&lt;h4&gt;O Route 53 aponta o seu domínio para ele&lt;/h4&gt;&lt;p&gt;Um registro alias no Route 53 aponta &lt;code&gt;seuapp.com&lt;/code&gt; para a distribuição do CloudFront. Para ter HTTPS, você também precisa de um certificado do ACM, que tem que ser criado em &lt;code&gt;us-east-1&lt;/code&gt;, porque é lá que o CloudFront o procura (&lt;a href="https://sst.dev/docs/component/aws/nextjs"&gt;docs do SST&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;Mas quem renderiza o HTML?&lt;/h4&gt;&lt;p&gt;O S3 não executa código, e o SSR precisa de código rodando a cada requisição. Por isso, o pipeline também faz o deploy do código de servidor em algum lugar que consiga executá-lo.&lt;/p&gt;&lt;p&gt;Neste artigo, esse lugar é o &lt;b&gt;AWS Lambda&lt;/b&gt;, a opção serverless. Você também poderia usar containers no ECS ou um servidor Node/Docker comum (&lt;a href="https://opennext.js.org/aws"&gt;OpenNext&lt;/a&gt;, &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/hosting"&gt;hospedagem do TanStack&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;Como o servidor chega aos usuários&lt;/h4&gt;&lt;p&gt;O CloudFront responde às requisições de arquivos direto do cache. O resto vai para o servidor, que devolve o HTML, e o CloudFront repassa para o navegador. Os usuários nunca falam direto com o servidor.&lt;/p&gt;&lt;p&gt;Os detalhes do Lambda estão na &lt;a href="#lambda"&gt;última seção&lt;/a&gt;, mas você não precisa deles para acompanhar o resto.&lt;/p&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;h2 id="adds"&gt;2. O que cada framework adiciona&lt;/h2&gt;
&lt;p&gt;A configuração acima é a mesma para os dois. Escolha um para ver o que ele acrescenta do lado da AWS.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Caminho animado da requisição, do navegador passando pelo Route 53 e pelo CloudFront até o S3 ou o Lambda, com os serviços extras de cada framework&lt;/em&gt;&lt;/p&gt;
&lt;p class="note"&gt;Aqui, o servidor é o Lambda. Falo mais sobre ele no final.&lt;/p&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;p class="meta"&gt;Next.js · via &lt;a href="https://opennext.js.org/aws"&gt;OpenNext&lt;/a&gt; · recursos segundo o &lt;a href="https://sst.dev/docs/component/aws/nextjs"&gt;SST&lt;/a&gt;&lt;/p&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;SSG · SSR · ISR&lt;/li&gt;&lt;li&gt;Otimização de imagens&lt;/li&gt;&lt;li&gt;Middleware&lt;/li&gt;&lt;li&gt;Self-hosting documentado&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Adaptador da comunidade&lt;/li&gt;&lt;li&gt;6+ tipos de recurso na AWS&lt;/li&gt;&lt;li&gt;Máximo de 25 behaviors no CloudFront&lt;/li&gt;&lt;li&gt;Sem build nativo no Windows&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;p class="meta"&gt;TanStack Start · via &lt;a href="https://nitro.build/deploy/providers/aws"&gt;Nitro&lt;/a&gt; · &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/hosting"&gt;guia de hospedagem&lt;/a&gt;&lt;/p&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;Pouca coisa para manter na AWS&lt;/li&gt;&lt;li&gt;Servidor que roda em qualquer host&lt;/li&gt;&lt;li&gt;Streaming opcional no Lambda&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;A AWS não é um alvo listado&lt;/li&gt;&lt;li&gt;Só receitas da comunidade&lt;/li&gt;&lt;li&gt;Nenhum ISR nem ferramenta de imagens à vista&lt;/li&gt;&lt;li&gt;Release candidate (nov/2025)&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="code"&gt;3. Onde o seu código roda&lt;/h2&gt;
&lt;p&gt;Esta é a maior diferença entre os dois, e quase tudo o que vem a seguir é consequência dela.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Quais componentes são enviados ao navegador em cada framework&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;Server Components por padrão (&lt;a href="https://nextjs.org/docs/app/getting-started/server-and-client-components"&gt;docs&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Os segredos ficam no servidor&lt;/li&gt;&lt;li&gt;Menos JS no navegador&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;&lt;code&gt;'use client'&lt;/code&gt; arrasta todo o grafo de imports&lt;/li&gt;&lt;li&gt;Sem React context em Server Components&lt;/li&gt;&lt;li&gt;As props precisam ser serializáveis&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Atenção:&lt;/b&gt; &lt;code&gt;'use client'&lt;/code&gt; marca uma fronteira no grafo de módulos, não em um componente isolado. Se você o colocar em um arquivo grande, tudo o que esse arquivo importa vai para o navegador. Use-o em componentes pequenos, lá nas folhas da árvore.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;Um só modelo mental: é React + router&lt;/li&gt;&lt;li&gt;Código de servidor explícito: &lt;code&gt;createServerFn&lt;/code&gt;&lt;/li&gt;&lt;li&gt;O handler do servidor nunca vai para o bundle do cliente&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Isomórfico por padrão → &lt;b&gt;os loaders rodam também no cliente&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;process.env&lt;/code&gt; no nível do módulo pode vazar&lt;/li&gt;&lt;li&gt;Server Components: experimental, opt-in&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Atenção:&lt;/b&gt; a própria documentação é clara: &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/execution-model"&gt;"Route loaders are isomorphic"&lt;/a&gt;. Eles rodam no cliente também, então um segredo ou uma chamada ao banco dentro de um loader é um bug esperando para acontecer. Tudo o que for sensível deve ficar atrás de &lt;code&gt;createServerFn&lt;/code&gt; ou &lt;code&gt;createServerOnlyFn&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="fresh"&gt;4. Como o conteúdo fica atualizado&lt;/h2&gt;
&lt;p&gt;Esta parte define quanta infraestrutura você roda e quanto trabalho de manutenção ela dá.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pipeline de atualização de conteúdo em cada framework&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;Por tempo: stale-while-revalidate&lt;/li&gt;&lt;li&gt;Sob demanda: tags, &lt;code&gt;revalidatePath&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Atualiza o conteúdo sem redeploy&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;O cache e o estado das tags precisam ser compartilhados entre as instâncias&lt;/li&gt;&lt;li&gt;HTML e RSC precisam ser cacheados juntos&lt;/li&gt;&lt;li&gt;Mais peças da AWS para monitorar&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Há mais detalhes na documentação sobre &lt;a href="https://nextjs.org/docs/app/guides/how-revalidation-works"&gt;como a revalidação funciona&lt;/a&gt; e na &lt;a href="https://sst.dev/docs/component/aws/nextjs"&gt;lista de recursos do SST&lt;/a&gt;. Um ponto de atenção: se uma CDN cacheia o HTML e o payload RSC com TTLs diferentes, o usuário pode ver conteúdo inconsistente ao navegar no cliente.&lt;/p&gt;
&lt;p class="opinion inf"&gt;Desenhei o pipeline acima numa ordem simplificada.&lt;/p&gt;
&lt;p class="opinion"&gt;Dos pontos acima, o descompasso entre HTML e RSC é o que eu esperaria que desse problema primeiro.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;O prerender gera arquivos estáticos comuns&lt;/li&gt;&lt;li&gt;O cache mais simples possível: S3 + CloudFront&lt;/li&gt;&lt;li&gt;Os dados de navegação vêm do cache de loaders do router&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Sem ISR na &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/static-prerendering"&gt;documentação de prerender&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Rotas dinâmicas só são pré-renderizadas se houver link para elas + &lt;code&gt;crawlLinks&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Conteúdo novo = rebuild ou busca em runtime&lt;/li&gt;&lt;/ul&gt;
&lt;p class="opinion"&gt;Se o seu catálogo muda de hora em hora, no Start isso significa rebuilds agendados ou renderização ao vivo. Para muitos apps, tudo bem. Mas se a equipe de conteúdo espera publicar e ver no ar em segundos, compensa pagar pela estrutura do Next/OpenNext.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="routes"&gt;5. Estratégia de renderização por rota&lt;/h2&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;Estática&lt;/span&gt;&lt;span class="cell s"&gt;HTML pré-renderizado&lt;/span&gt;&lt;span class="cell"&gt;O CloudFront serve&lt;/span&gt;&lt;span class="cell"&gt;Cache-Control: public&lt;/span&gt;&lt;/div&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;Dinâmica&lt;/span&gt;&lt;span class="cell s"&gt;O Lambda renderiza a cada requisição&lt;/span&gt;&lt;span class="cell"&gt;usa cookies / headers&lt;/span&gt;&lt;span class="cell"&gt;private, no-store&lt;/span&gt;&lt;/div&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;Streaming&lt;/span&gt;&lt;span class="cell s"&gt;Primeiro o shell&lt;/span&gt;&lt;span class="cell"&gt;Chunks do Suspense à medida que ficam prontos&lt;/span&gt;&lt;span class="cell x"&gt;precisa de um caminho sem buffer&lt;/span&gt;&lt;/div&gt;
&lt;p class="note"&gt;Uma rota ser estática ou dinâmica depende das APIs que ela usa (&lt;a href="https://nextjs.org/docs/app/guides/self-hosting"&gt;docs&lt;/a&gt;). Você não declara isso: o Next descobre sozinho a partir do código.&lt;/p&gt;
&lt;p class="opinion"&gt;O Next deduz isso pelo que o seu código toca. Dá menos trabalho, mas também mais surpresa: uma chamada aparentemente inofensiva a &lt;code&gt;cookies()&lt;/code&gt; pode tornar a página dinâmica sem avisar.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;&lt;code&gt;ssr: true&lt;/code&gt;&lt;/span&gt;&lt;span class="cell s"&gt;loader no servidor&lt;/span&gt;&lt;span class="cell s"&gt;componente → HTML&lt;/span&gt;&lt;span class="cell c"&gt;hidrata no cliente&lt;/span&gt;&lt;/div&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;&lt;code&gt;'data-only'&lt;/code&gt;&lt;/span&gt;&lt;span class="cell s"&gt;loader no servidor&lt;/span&gt;&lt;span class="cell c"&gt;componente só no cliente&lt;/span&gt;&lt;/div&gt;
&lt;div class="lane2"&gt;&lt;span class="lab"&gt;&lt;code&gt;ssr: false&lt;/code&gt;&lt;/span&gt;&lt;span class="cell c"&gt;loader no cliente&lt;/span&gt;&lt;span class="cell c"&gt;componente no cliente&lt;/span&gt;&lt;/div&gt;
&lt;p class="note"&gt;Você declara isso rota por rota, com o &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/selective-ssr"&gt;SSR seletivo&lt;/a&gt;. Uma rota filha só pode ser mais restrita que a pai, nunca mais permissiva.&lt;/p&gt;
&lt;p class="opinion"&gt;O Start pede que você declare isso em cada rota. Dá mais trabalho, mas nada muda escondido por baixo dos panos.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="cwv"&gt;6. Core Web Vitals&lt;/h2&gt;
&lt;p&gt;O Google avalia a experiência da página com três números. Uma página é considerada "boa" quando atinge a meta em 75% das visitas (&lt;a href="https://web.dev/articles/vitals"&gt;web.dev&lt;/a&gt;).&lt;/p&gt;
&lt;div class="tiles fadein"&gt;
&lt;div class="tile" style="border-color:var(--next)"&gt;LCP&lt;b&gt;≤ 2,5 s&lt;/b&gt;conteúdo principal visível&lt;/div&gt;
&lt;div class="tile" style="border-color:var(--tan)"&gt;INP&lt;b&gt;≤ 200 ms&lt;/b&gt;reage a toques e cliques&lt;/div&gt;
&lt;div class="tile"&gt;CLS&lt;b&gt;≤ 0,1&lt;/b&gt;nada pula de lugar&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;O que o framework não decide&lt;/h3&gt;
&lt;p&gt;Um primeiro byte lento (TTFB) prejudica o LCP, mas o TTFB não é um dos três vitals e, em grande parte, tem pouco a ver com o framework. Ele depende da rapidez da sua API e do banco de dados, da distância entre o servidor e o usuário e de o CloudFront conseguir ou não cachear a resposta (&lt;a href="https://web.dev/articles/ttfb"&gt;web.dev&lt;/a&gt;). Uma página renderizada no servidor pode até ter um TTFB maior que uma renderizada no cliente e mesmo assim ganhar no LCP. Então não escolha um framework por causa do TTFB. Comece corrigindo a chamada de API mais lenta.&lt;/p&gt;
&lt;h3&gt;Onde os frameworks de fato diferem&lt;/h3&gt;
&lt;div class="scroll fadein"&gt;&lt;table&gt;
&lt;tr&gt;&lt;th&gt;Item&lt;/th&gt;&lt;th class="next"&gt;Next.js&lt;/th&gt;&lt;th class="tan"&gt;TanStack Start&lt;/th&gt;&lt;th&gt;Quem leva&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LCP · imagens&lt;/td&gt;&lt;td&gt;Otimização de imagens embutida (com um Lambda próprio na AWS)&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; comum, seguindo o checklist abaixo, servido do S3 pelo CloudFront&lt;/td&gt;&lt;td&gt;&lt;span class="flag"&gt;Empate&lt;/span&gt; se você seguir o checklist; o Next automatiza o dimensionamento&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LCP · HTML em cache&lt;/td&gt;&lt;td&gt;Páginas totalmente estáticas são &lt;code&gt;public&lt;/code&gt;, então o CloudFront consegue cacheá-las, e o ISR as mantém atualizadas. Páginas dinâmicas são &lt;code&gt;private, no-store&lt;/code&gt; e sempre chegam ao servidor (&lt;a href="https://nextjs.org/docs/app/guides/self-hosting"&gt;docs&lt;/a&gt;)&lt;/td&gt;&lt;td&gt;Páginas pré-renderizadas são arquivos comuns, também cacheáveis (&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/static-prerendering"&gt;docs&lt;/a&gt;). Sem ISR, conteúdo novo exige rebuild&lt;/td&gt;&lt;td&gt;&lt;span class="flag"&gt;Empate nas estáticas&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LCP · streaming&lt;/td&gt;&lt;td&gt;Faz streaming com Suspense&lt;/td&gt;&lt;td&gt;SSR com streaming (&lt;a href="https://www.infoq.com/news/2025/11/tanstack-start-v1"&gt;InfoQ&lt;/a&gt;)&lt;/td&gt;&lt;td&gt;&lt;span class="flag"&gt;Empate&lt;/span&gt;, desde que nada no caminho faça buffer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;INP · JS no navegador&lt;/td&gt;&lt;td&gt;Server Components não enviam JS próprio; só as partes com &lt;code&gt;'use client'&lt;/code&gt; são hidratadas (&lt;a href="https://nextjs.org/docs/app/getting-started/server-and-client-components"&gt;docs&lt;/a&gt;)&lt;/td&gt;&lt;td&gt;Os componentes são renderizados no servidor e hidratados por padrão (&lt;a href="https://tanstack.com/start/latest/docs/framework/react/start-vs-nextjs"&gt;docs&lt;/a&gt;)&lt;/td&gt;&lt;td&gt;&lt;span class="flag"&gt;Next, em páginas com muito conteúdo&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CLS&lt;/td&gt;&lt;td&gt;Depende sobretudo do seu markup: tamanho das imagens, fontes, banners que carregam tarde&lt;/td&gt;&lt;td&gt;Igual&lt;/td&gt;&lt;td&gt;&lt;span class="flag"&gt;Empate&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;Uma boa imagem de LCP no TanStack Start&lt;/h3&gt;
&lt;p&gt;Estas práticas vêm do &lt;a href="https://web.dev/articles/optimize-lcp"&gt;guia de LCP do web.dev&lt;/a&gt; e valem para qualquer framework:&lt;/p&gt;
&lt;ul class="chips pro"&gt;
&lt;li&gt;Coloque a imagem no HTML renderizado no servidor&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchpriority="high"&lt;/code&gt; só na imagem principal (hero)&lt;/li&gt;
&lt;li&gt;Nunca use &lt;code&gt;loading="lazy"&lt;/code&gt; nela&lt;/li&gt;
&lt;li&gt;&lt;code&gt;srcset&lt;/code&gt; para o tamanho certo&lt;/li&gt;
&lt;li&gt;AVIF ou WebP&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;// routes/index.tsx
export const Route = createFileRoute('/')({
head: () =&amp;gt; ({
links: [
{ rel: 'preload', as: 'image', href: '/img/hero-1200.avif', fetchPriority: 'high' },
],
}),
component: Home,
})
const Home = () =&amp;gt; (
&amp;lt;img
src="https://cmarcal-roberto.dev/img/hero-1200.avif"
srcSet="/img/hero-600.avif 600w, /img/hero-1200.avif 1200w"
sizes="100vw"
width={1200}
height={630}
fetchPriority="high"
alt="Football pitch at sunset"
/&amp;gt;
)&lt;/pre&gt;
&lt;p class="note"&gt;No Start, você adiciona tags ao head da página pela opção &lt;code&gt;head&lt;/code&gt; da rota (&lt;a href="https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management"&gt;docs&lt;/a&gt;). A documentação mostra um preload de fonte, então a versão com imagem acima é uma adaptação minha. Os arquivos ficam no S3 e o CloudFront os cacheia como qualquer outro asset. Como o &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; já está no HTML renderizado no servidor, o preload é opcional; ele faz mais diferença quando a imagem é referenciada a partir de CSS ou JavaScript.&lt;/p&gt;
&lt;p&gt;Um teste público mediu 116 KB de JavaScript no cliente para o TanStack Start contra 193 KB para o Next.js, no mesmo app de dashboard (&lt;a href="https://blog.logrocket.com/tanstack-start-rsc-vs-next-js-rsc-performance-dx-production-readiness/"&gt;LogRocket&lt;/a&gt;). É um único app, então leia como "o runtime do framework pode pesar", e não como "um sempre ganha". A própria página de comparação do TanStack &lt;a href="https://tanstack.com/start/latest/docs/framework/react/start-vs-nextjs"&gt;se recusa a apontar um vencedor em tamanho de runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Não encontrei &lt;b&gt;nenhuma comparação medida de Core Web Vitals&lt;/b&gt; entre os dois. Rode o Lighthouse e olhe os dados de campo (CrUX) no seu próprio app.&lt;/p&gt;
&lt;p class="opinion"&gt;Um site de conteúdo, com poucas partes interativas, aproveita mais os Server Components e o pipeline de imagens do Next. Um app muito interativo envia a maior parte do JS em qualquer um dos dois, então a diferença diminui.&lt;/p&gt;
&lt;h2 id="ops"&gt;7. O que fica com você quando algo quebra&lt;/h2&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;O adaptador precisa acompanhar os lançamentos do Next&lt;/li&gt;&lt;li&gt;Os mantenedores têm capacidade limitada (&lt;a href="https://opennext.js.org/aws"&gt;OpenNext&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;25 behaviors por distribuição do CloudFront&lt;/li&gt;&lt;li&gt;Memória e cold starts do Lambda de imagens&lt;/li&gt;&lt;li&gt;Bugs de consistência de cache e de tags&lt;/li&gt;&lt;/ul&gt;
&lt;p class="opinion"&gt;A superfície de falha é grande, mas muita gente roda essa combinação, então a maioria dos erros dá para achar numa busca. Fixe as versões do Next e do OpenNext juntas e atualize as duas de uma vez.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Release candidate em nov/2025 (&lt;a href="https://www.infoq.com/news/2025/11/tanstack-start-v1"&gt;InfoQ&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Plugin do Nitro para o Vite "under active development"&lt;/li&gt;&lt;li&gt;Sem runbook oficial para a AWS&lt;/li&gt;&lt;li&gt;Você mesmo constrói as camadas de imagem e de cache&lt;/li&gt;&lt;/ul&gt;
&lt;p class="opinion"&gt;A superfície de falha é menor, mas menos gente já passou por ali. Quando algo quebrar, você vai ler mais o código do Nitro e do Start do que respostas no Stack Overflow. Conte com isso e confira o status atual do release antes de decidir.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="lambda"&gt;8. Indo mais fundo: Lambda e mutations&lt;/h2&gt;&lt;p&gt;Esta parte é opcional. Você pode pulá-la e ainda assim tomar a decisão.&lt;/p&gt;&lt;h3&gt;Como uma mutation chega ao Lambda&lt;/h3&gt;
&lt;p&gt;Posts de formulário e server functions viram uma chamada HTTPS que passa pelo CloudFront até a sua função. Aqui, cada framework tende a falhar de um jeito diferente.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Uma requisição de mutation indo do navegador, pelo CloudFront, até uma ou mais instâncias do Lambda&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h4&gt;Next.js&lt;/h4&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Lambda = várias instâncias&lt;/li&gt;&lt;li&gt;Defina &lt;code&gt;NEXT_SERVER_ACTIONS_ENCRYPTION_KEY&lt;/code&gt; ou você vai tomar um "Failed to find Server Action"&lt;/li&gt;&lt;li&gt;Em deploys graduais, defina &lt;code&gt;deploymentId&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Fonte: o &lt;a href="https://nextjs.org/docs/app/guides/self-hosting"&gt;guia de self-hosting&lt;/a&gt;, que descreve isso para configurações com vários servidores.&lt;/p&gt;
&lt;p class="opinion inf"&gt;O Lambda roda várias instâncias, então estou aplicando essa orientação a ele. A documentação não diz isso especificamente sobre o Lambda.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;h4&gt;TanStack Start&lt;/h4&gt;
&lt;ul class="chips pro"&gt;&lt;li&gt;O build troca o handler por um stub RPC&lt;/li&gt;&lt;li&gt;O validador (ex.: Zod) é parte central do design&lt;/li&gt;&lt;li&gt;Middleware de CSRF por padrão&lt;/li&gt;&lt;/ul&gt;
&lt;ul class="chips con"&gt;&lt;li&gt;Só same-origin: APIs públicas precisam de server routes&lt;/li&gt;&lt;li&gt;Ao definir &lt;code&gt;src/start.ts&lt;/code&gt;, você precisa recolocar o CSRF&lt;/li&gt;&lt;li&gt;A autenticação fica em cada handler&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Do &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/server-functions"&gt;guia de server functions&lt;/a&gt;: &lt;code&gt;beforeLoad&lt;/code&gt; "is not the data boundary".&lt;/p&gt;
&lt;p class="opinion"&gt;Trate toda server function como um endpoint público e faça a autorização dentro dela. O mesmo vale para as Server Actions do Next.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Armadilhas que valem para os dois&lt;/h3&gt;
&lt;ul class="chips con"&gt;
&lt;li&gt;Requisições POST pelo OAC exigem &lt;code&gt;x-amz-content-sha256&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Nada no caminho pode fazer buffer de respostas em streaming&lt;/li&gt;
&lt;li&gt;Com muitas instâncias do Lambda, o Next precisa de cache compartilhado e da mesma chave de criptografia&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verdict"&gt;Veredito&lt;/h2&gt;
&lt;p&gt;Escolha o &lt;span class="next"&gt;Next.js&lt;/span&gt; se o conteúdo precisa ir ao ar em segundos, se você quer imagens e middleware resolvidos para você e se aceita ser responsável pelas peças do OpenNext. Escolha o &lt;span class="tan"&gt;TanStack Start&lt;/span&gt; se você quer o menor número de peças na AWS, uma separação explícita entre servidor e cliente e portabilidade, e consegue construir os extras por conta própria.&lt;/p&gt;
&lt;p class="opinion"&gt;Faça um protótipo da sua rota mais difícil nos dois antes de decidir e meça os Core Web Vitals no seu próprio app.&lt;/p&gt;
&lt;h2 id="sources"&gt;Fontes&lt;/h2&gt;
&lt;ul class="sources"&gt;&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/guides/self-hosting"&gt;Next.js: Self-hosting&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/getting-started/server-and-client-components"&gt;Next.js: Server and Client Components&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/guides/how-revalidation-works"&gt;Next.js: How revalidation works&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://opennext.js.org/aws"&gt;OpenNext for AWS&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://sst.dev/docs/component/aws/nextjs"&gt;SST: Nextjs component (AWS resources, limits)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/hosting"&gt;TanStack Start: Hosting&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/execution-model"&gt;TanStack Start: Execution model&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/server-functions"&gt;TanStack Start: Server functions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/selective-ssr"&gt;TanStack Start: Selective SSR&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/static-prerendering"&gt;TanStack Start: Static prerendering&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/start-vs-nextjs"&gt;TanStack: Start vs Next.js&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://nitro.build/deploy/providers/aws"&gt;Nitro: AWS Lambda preset&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda.html"&gt;AWS: Restrict access to a Lambda function URL origin&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://web.dev/articles/vitals"&gt;web.dev: Core Web Vitals&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://web.dev/articles/ttfb"&gt;web.dev: TTFB&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://www.infoq.com/news/2025/11/tanstack-start-v1"&gt;InfoQ: TanStack Start release candidate (Nov 2025)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://blog.logrocket.com/tanstack-start-rsc-vs-next-js-rsc-performance-dx-production-readiness/"&gt;LogRocket: TanStack Start RSC vs Next.js RSC&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;</content:encoded></item></channel></rss>