headless-wordpress-astro-setup.html
< BACK Mesa vintage com páginas de manuscrito e gravador em luz dourada da hora de ouro

WordPress Headless + Astro: Uma Setup Funcional para Sites com Muito Conteúdo

Um cliente me ligou no início de 2023 — um publisher de mídia rodando cerca de 14.000 posts no WordPress, um tema remendado por quatro desenvolvedores ao longo de seis anos, e um score de Core Web Vitals que era genuinamente constrangedor. Seu LCP estava marcando 7,2 segundos em mobile. Eles tentaram WP Rocket, tentaram uma CDN, até removeram metade dos plugins. Ainda lento. O problema não era WordPress em si. Era que cada single render de página passava por PHP, um tema inchado, e uma corrente de queries de banco de dados que ninguém olhava desde 2018.

Esse foi o momento em que me comprometi propriamente com uma setup headless usando Astro como frontend. Não porque é moda — porque para um site com milhares de posts e conteúdo editorial pesado, era a única arquitetura que fazia sentido.Astro as the frontend. Not because it's fashionable — because for a site pushing thousands of posts with heavy editorial content, it was the only architecture that made sense.

Aqui está exatamente como configure tudo. Os tradeoffs, a config real, as partes que me prejudicaram.

---

Por que Astro e Não Next.js

Recebo essa pergunta com frequência. Next.js é a resposta óbvia se você já tem um time focado em React ou precisa de interatividade complexa no lado do cliente. Mas para sites com muito conteúdo? Astro vence, e não é nem um pouco perto.

Astro envia zero JavaScript por padrão. Para um blog, um site de notícias ou um portal de documentação, esse é o padrão correto. Você opta por JavaScript onde precisa dele, em vez de optar por sair de um bundle React de 200kb que você provavelmente não quer. Os docs do Astro sobre hidratação parcial — eles chamam de arquitetura de Islands — explicam melhor do que eu consigo em uma frase, mas a versão curta é: apenas os bits interativos recebem JS. O corpo do artigo, o header, a sidebar? HTML estático.zero JavaScript by default. For a blog, a news site, or a documentation portal, that's the correct default. You opt into JavaScript where you need it, rather than opting out of a 200kb React bundle you mostly don't want. The Astro docs on partial hydration — they call it the Islands architecture — explain it better than I can in a sentence, but the short version is: only the interactive bits get JS. The article body, the header, the sidebar? Static HTML.

Construí um site de conteúdo legal no final de 2022 com Next.js e WordPress. Rápido o suficiente, mas o cliente não parava de perguntar por que o Lighthouse score dele era 74 no mobile quando "supostamente deveria ser rápido agora". Overhead de hidratação. Com Astro, esse mesmo tipo de site agora rotineiramente atinge 95–98. Não estou me gabando — é só o que a arquitetura te dá de graça.

O Que Astro Não É Bom

Vale ser honesto. Se seu site precisa de personalização em tempo real, um carrinho de compras pesado, ou qualquer coisa que realmente dependa de estado no cliente entre muitos componentes, Astro começa a parecer desconfortável. Não é um app React. O padrão Islands é poderoso, mas é um modelo mental diferente do que construir SPAs. Tentei forçar um dashboard do cliente em um projeto Astro em meados de 2023 e acabei revertendo para Next.js em duas semanas. Saiba o que você está construindo.

---

Configurando WordPress como um CMS Headless

WordPress é genuinamente um bom backend headless. A WP REST API vem no core, é bem documentada, e seu time editorial não precisa aprender nada novo. Esse último ponto importa mais do que os desenvolvedores geralmente admitem.WP REST API ships in core, it's well-documented, and your editorial team doesn't have to learn anything new. That last point matters more than developers usually admit.

Aqui está a configuração que uso:

  1. Instale WordPress em um subdomínio — eu uso cms.yourdomain.com ou api.yourdomain.com. Mantenha por trás de basic auth ou no mínimo restrinja tráfego público direto. O frontend é yourdomain.com. Dois deployments separados. — I use cms.yourdomain.com or api.yourdomain.com. Keep it behind basic auth or at minimum restrict direct public traffic. The frontend is yourdomain.com. Two separate deployments.
  2. Instale o [plugin WPGraphQL](https://www.wpgraphql.com/) — eu prefiro GraphQL em vez de REST para sites de conteúdo porque você pode colocar suas queries junto com seus componentes e buscar exatamente os campos que precisa. Sem over-fetching. A REST API funciona bem, mas quando você tem 15+ campos customizados por tipo de post, a abordagem GraphQL é notavelmente mais limpa. — I prefer GraphQL over REST for content sites because you can co-locate your queries with your components and fetch exactly the fields you need. No over-fetching. The REST API is fine, but once you've got 15+ custom fields per post type, the GraphQL approach is noticeably cleaner.
  3. Instale o Advanced Custom Fields (ACF) e a extensão WPGraphQL for ACF. Essa combinação é o que torna o WordPress genuinamente flexível como um modelo de conteúdo headless — você pode definir dados estruturados por tipo de post, expô-los através do GraphQL, e o Astro os consome de forma limpa. and the WPGraphQL for ACF extension. This combo is what makes WordPress genuinely flexible as a headless content model — you can define structured data per post type, expose it through GraphQL, and Astro consumes it cleanly.
  4. Desabilite comentários, emojis e o XML-RPC padrão se ainda não o fez. Eles adicionam sobrecarga e superfície de ataque que você não precisa. if you haven't already. These add overhead and attack surface you don't need.
  5. Configure os permalinks para algo sensato antes de começar a construir. Mudá-los no meio do projeto quando suas rotas do Astro já estão definidas é genuinamente incômodo. to something sensible before you start building. Changing them mid-project when your Astro routes are already set is a genuine pain.

Uma coisa que pega as pessoas: CORS. Por padrão, o WordPress não deixa seu servidor dev do Astro (rodando em localhost:4321) fazer requisições para sua instalação do WP. Coloque isso no functions.php do seu tema ou em um pequeno plugin utilitário durante o desenvolvimento:localhost:4321) make requests to your WP install. Drop this into your theme's functions.php or a small utility plugin during development:

`` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); `` add_action('init', function() { header("Access-Control-Allow-Origin: *"); }); ``

Aperte isso para origens específicas em produção. Obviamente.

---

A Estrutura do Projeto Astro

Mantenho isso opinado e consistente entre projetos. Depois de uma dúzia ou mais de builds headless, isso é o que funciona:

`` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← toda a lógica de query WP fica aqui styles/ `` src/ components/ layouts/ pages/ index.astro blog/ [slug].astro lib/ wpgraphql.ts ← all WP query logic lives here styles/ ``

O arquivo lib/wpgraphql.ts é onde centralizo cada fetch GraphQL. Sem chamadas inline espalhadas por arquivos de página. Cada query é uma função async nomeada e exportada. Debugar isso em 14 mil posts quando algo quebra às 2 da manhã — você vai se agradecer depois.lib/wpgraphql.ts file is where I centralise every GraphQL fetch. No inline fetch calls scattered across page files. Every query is a named, exported async function. Debugging this across 14,000 posts when something breaks at 2am — you'll thank yourself later.

Buscando Posts no Momento da Build

O getStaticPaths do Astro é seu pão com manteiga aqui. Para um blog com milhares de posts:getStaticPaths is your bread and butter here. For a blog with thousands of posts:

`` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // chama WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } `` export async function getStaticPaths() { const posts = await getAllPostSlugs(); // calls WPGraphQL return posts.map(post => ({ params: { slug: post.slug }, })); } ``

getAllPostSlugs pagina através do WPGraphQL usando after cursors — a camada GraphQL do WordPress retorna 100 posts por request por padrão, então para 14 mil posts você está fazendo 140 requests no momento da build. Parece assustador. Na prática, em um servidor decente, a build completa roda em cerca de 4–5 minutos. Perfeitamente aceitável para um site que faz rebuild algumas vezes por dia. paginates through WPGraphQL using after cursors — WordPress's GraphQL layer returns 100 posts per request by default, so for 14,000 posts you're making 140 requests at build time. That sounds scary. In practice, on a decent server, the full build runs in about 4–5 minutes. Perfectly acceptable for a site that rebuilds a few times per day.

---

Lidando com Imagens Sem Perder a Sanidade

Essa é a parte que ninguém fala o suficiente. WordPress armazena URLs de imagens apontando para o subdomínio do seu CMS. Quando Astro faz build estática, essas imagens ainda vivem em cms.seudominio.com — o que significa que os navegadores dos seus visitantes estão buscando imagens do seu servidor WordPress, potencialmente pulando seu CDN.cms.yourdomain.com — which means your visitors' browsers are fetching images from your WordPress server, potentially bypassing your CDN.

Algumas formas que lido com isso:

  • Cloudflare na frente de ambos os domínios. Opção mais simples. Faça proxy de yourdomain.com e cms.yourdomain.com através do Cloudflare, configure cache agressivo em /wp-content/uploads/*, e você está praticamente pronto. Simplest option. Proxy both yourdomain.com and cms.yourdomain.com through Cloudflare, configure aggressive caching on /wp-content/uploads/*, and you're mostly fine.
  • Use um plugin de offload de mídia. Gosto do WP Offload Media — ele move uploads para S3 (ou armazenamento compatível) e reescreve URLs automaticamente. Essa é a abordagem que uso para qualquer site esperando tráfego sério. Seu servidor WordPress para de servir imagens completamente. I like WP Offload Media — it moves uploads to S3 (or compatible storage) and rewrites URLs automatically. This is the approach I use for any site expecting serious traffic. Your WordPress server stops serving images entirely.
  • Componente Image do Astro. Para imagens que você controla no tempo de build (imagens destacadas puxadas via GraphQL), você pode passar a URL remota para o componente <Image> do Astro e ele vai otimizar, redimensionar e servir do seu output de build. Funciona brilhantemente. Não funciona para imagens embutidas no HTML do corpo do post — isso requer uma abordagem diferente. For images you control at build time (featured images pulled via GraphQL), you can pass the remote URL into Astro's <Image> component and it'll optimise, resize, and serve them from your build output. Works brilliantly. Does not work for images embedded in post body HTML — that requires a different pass.

Seahawk teve um cliente de conteúdo de viagens ano passado — cerca de 8.000 posts, extremamente pesado em imagens, média de 12 imagens por artigo. O servidor WordPress deles estava sendo martelado puramente por requisições de imagem mesmo com o setup headless. Migrar para S3 + CloudFront reduziu a largura de banda de origem deles em 94%. Genuinamente transformador para a conta de hosting deles.

---

Builds Incrementais e o Problema do Rebuild

Aqui está um problema real com geração estática em escala: seu editor publica uma correção de post às 3 da tarde e tem que esperar 5 minutos por um rebuild completo. Isso não é aceitável em uma redação.

Algumas abordagens que usei:

Opção 1: Netlify ou Vercel com ISR sob demanda. Astro suporta server-side rendering com adapters — você pode rodar Astro em modo híbrido onde a maioria das páginas é estática mas rotas específicas são renderizadas sob demanda. Para um site de notícias, frequentemente vou pré-renderizar estaticamente os últimos 30 dias de posts (tráfego alto, precisa de velocidade) e configurar páginas de arquivo mais antigas para server-render sob demanda. Melhor dos dois mundos. Astro supports server-side rendering with adapters — you can run Astro in hybrid mode where most pages are static but specific routes are rendered on-demand. For a news site, I'll often statically pre-render the last 30 days of posts (high traffic, needs speed) and set older archive pages to server-render on demand. Best of both worlds.

Opção 2: Builds parciais acionadas por webhook. WordPress dispara um webhook ao salvar post (fácil com o plugin WP Webhooks). Esse webhook chega a um deploy hook do Netlify ou Vercel. O build roda, ele busca apenas o que mudou. Não é verdadeiramente parcial — Astro ainda reconstrói tudo — mas se você mantiver seu build rápido, 4 minutos é viável. WordPress fires a webhook on post save (easy with the WP Webhooks plugin). That webhook hits a Netlify or Vercel deploy hook. The build runs, it fetches only what's changed. It's not truly partial — Astro still rebuilds everything — but if you keep your build fast, 4 minutes is workable.

Opção 3: Apenas use SSR para tudo. Deploy do Astro com o adapter Node em uma VPS (eu uso Hetzner para isso — barato, rápido, confiável). Cada página renderiza sob demanda, você faz cache agressivo no nível do Nginx ou Cloudflare, e tem atualizações de posts instantâneas. Isso é o que eu faria para uma operação de publicação adequada com mais de 50.000 posts. Deploy Astro with the Node adapter to a VPS (I use Hetzner for this — cheap, fast, reliable). Every page renders on request, you cache aggressively at the Nginx or Cloudflare level, and you have instant post updates. This is what I'd do for a proper publishing operation over 50,000 posts.

Opinião honesta? A maioria dos sites não precisa da complexidade da Opção 1 ou 3. Um rebuild de 4 minutos acionado por webhook é ok para 90% dos sites de conteúdo.

---

Performance: O Que Você Realmente Consegue

No projeto de publisher do começo — aqui está o que aconteceu após a migração do Astro:

  • LCP caiu de 7.2s para 1.1s em mobile (testado com WebPageTest de um nó em Londres)1.1s on mobile (tested with WebPageTest from a London node)
  • Total Blocking Time foi de ~800ms para 0ms (zero JS por padrão, lembre-se)0ms (zero JS by default, remember)
  • O relatório Core Web Vitals do Google Search Console deles foi de 3% de URLs "Good" para 91% "Good" em seis semanas após o deployment91% "Good" within six weeks of deployment
  • Os custos de hosting caíram porque seu servidor WordPress não estava mais servindo páginas, apenas respostas de API

Nada disso é mágica. É só o que acontece quando você remove a renderização de PHP do caminho crítico e para de enviar um bundle JavaScript de tema de 400kb para cada leitor.

---

Os Detalhes Que Vão Te Confundir

Falando sério — coisas que tive que debugar em projetos reais:

  • Previsualizações de rascunhos. Isso é genuinamente chato em uma configuração headless. A prévia nativa do WordPress depende de renderização no front-end. Você precisa construir um endpoint customizado de prévia no Astro que aceite um nonce de prévia do WordPress e busque o rascunho via WPGraphQL. Não é difícil, mas leva um dia para fazer direito. This is genuinely annoying in a headless setup. WordPress's native preview relies on front-end rendering. You need to build a custom preview endpoint in Astro that accepts a WordPress preview nonce and fetches the draft via WPGraphQL. Not hard, but it takes a day to do properly.
  • Redirecionamentos. Se o site antigo tinha centenas de redirecionamentos em .htaccess, eles agora vivem no servidor WordPress. Você precisa replicá-los na config do Astro, ou manter WordPress acessível e fazer proxy de caminhos específicos. Já fiz os dois. Replicar no Astro é mais limpo a longo prazo. If the old site had hundreds of redirects in .htaccess, those live on the WordPress server now. You need to either replicate them in Astro's config, or keep WordPress accessible and proxy specific paths. I've done both. Replicating in Astro is cleaner long-term.
  • Busca. A busca integrada do WordPress é inútil em uma configuração headless. Eu uso Algolia com o plugin WP Search with Algolia. Indexe seus posts no WP, consulte o Algolia a partir de um componente Astro Island. Funciona bem. WordPress's built-in search is useless in a headless setup. I use Algolia with the WP Search with Algolia plugin. Index your posts in WP, query Algolia from an Astro Island component. Works well.
  • Menus e navegação. Menus do WordPress são estranhamente fiddly para expor via WPGraphQL. A rota wpgraphql-acf geralmente acaba sendo mais limpa — apenas modele sua nav como um repetidor ACF e está feito. WordPress menus are weirdly fiddly to expose through WPGraphQL. The wpgraphql-acf route often ends up being cleaner — just model your nav as an ACF repeater and call it done.

---

FAQ

Preciso de WPGraphQL ou posso apenas usar a REST API?

Você pode absolutamente usar a REST API — ela é construída no WordPress core e não requer plugins extras. Para sites simples com tipos de post padrão e campos personalizados mínimos, funciona bem. Onde GraphQL se destaca é quando você tem modelos de conteúdo complexos com muitos campos personalizados por tipo. Conseguir buscar exatamente os campos que precisa em uma única requisição, sem lidar com parâmetros _embed e chamadas REST aninhadas, economiza tempo em cada query que você escreve. Fica a seu critério. Eu só acho GraphQL mais limpo acima de um certo nível de complexidade._embed parameters and nested REST calls, saves time on every query you write. Up to you. I just find GraphQL cleaner past a certain complexity threshold.

Como faço para lidar com autenticação WordPress para conteúdo exclusivo de membros?

Autenticação JWT é a abordagem padrão. Instale o plugin JWT Authentication for WP REST API, emita tokens no login e passe-os nos headers da sua requisição GraphQL. Do lado do Astro, você lidaria com isso usando uma rota SSR (não estática) para que o conteúdo específico do usuário seja buscado no servidor a cada requisição. Não tente fazer isso estaticamente — aí é que mora a loucura.JWT Authentication for WP REST API plugin, issue tokens on login, pass them in your GraphQL request headers. On the Astro side, you'd handle this with an SSR route (not static) so the user-specific content is fetched server-side per request. Don't try to do this statically — that way lies madness.

Isso não é exagero para um blog pequeno?

Sim, provavelmente. Se você tem menos de 500 posts e um editor, o overhead de manter uma configuração headless não vale a pena. Basta usar um bom tema WordPress, otimizar suas imagens e seguir com a vida. Essa arquitetura compensa quando você tem volume, complexidade editorial ou níveis de tráfego onde a performance realmente impacta a receita.

Como é a configuração de hospedagem em produção?

WordPress (CMS apenas) em uma VPS pequena ou host WordPress gerenciado — eu uso Kinsta ou Cloudways. Frontend Astro no Vercel, Netlify ou uma VPS Hetzner com Nginx dependendo do projeto. Cloudflare na frente de tudo. O custo mensal total para um site de conteúdo de médio porte geralmente fica entre £60–£120, o que muitas vezes é menos do que os clientes pagavam em um host WordPress all-in-one que estava sofrendo com a carga.less than what clients were paying for an all-in-one WordPress host that was struggling under the load.

---

O resumo honesto é este: WordPress headless com Astro é uma das melhores coisas que aconteceram para sites de conteúdo em um tempo. Não porque é novo, mas porque o tooling finalmente alcançou a ideia. WPGraphQL é estável, o sistema de build do Astro é rápido, e os ganhos de performance são reais e mensuráveis.

Acerte a arquitetura cedo — especialmente sua estratégia de imagens e sua abordagem de rebuild — e você gastará muito menos tempo apagando incêndios depois. É realmente só isso.

< BACK