Como migrei um site WordPress para o Cloudflare Pages usando IA (e o que quebrou)

WordPress faz tudo. Isso é o problema e a solução ao mesmo tempo.

Passei anos no ecossistema WordPress: plugins, temas, WooCommerce, Gutenberg, tudo. É uma plataforma madura, poderosa, que resolve casos complexos muito bem. Não estou aqui pra dizer que é ruim.

Mas com IA surgiu uma categoria de site onde ele virou overkill: landing pages, sites de produto, sites institucionais, portfólios. Páginas que são essencialmente conteúdo estático com um formulário de contato. Você está rodando uma stack PHP inteira, gerenciando atualização de plugin, pagando hospedagem, tudo por uma página que poderia ser HTML puro servido da edge.

Pra esses casos, comecei a migrar tudo. Destino: Cloudflare Pages. O processo: em boa parte assistido por IA.

Foi assim que foi.

Por que Cloudflare Pages

Alguns motivos pra ele ter ganhado da Vercel, Netlify e GitHub Pages nesse projeto:

  • Rede edge global sem cold start
  • O plano gratuito é genuinamente generoso — requisições ilimitadas, banda ilimitada
  • Cloudflare Workers pra qualquer lógica dinâmica que sobreviva à migração
  • D1 pra necessidades leves de banco, se aparecerem depois
  • Tudo em um ecossistema só

Se você já usa Cloudflare pro DNS (a maioria usa), o caminho da migração é mais curto do que parece.

A stack depois da migração

  • Gerador de site estático: Astro — gera HTML puro, hidratação parcial pra qualquer componente interativo, build muito rápido
  • Hospedagem: Cloudflare Pages
  • Formulários: FormRoute — falo mais sobre isso adiante
  • IA usada: Claude pra transformação de conteúdo e geração de componentes.

Passo 1 — Exportar e inventariar o conteúdo do WordPress

Primeiro passo: entender o que você realmente tem.

# Exporta tudo do WordPress
# Admin → Ferramentas → Exportar → Todo o conteúdo
# Baixa um arquivo XML

O export XML te dá posts, páginas, categorias, tags, autores e as referências de mídia. Ele não te dá os arquivos de mídia em si — esses vêm à parte.

Joguei o XML no Claude e pedi pra ele:

  1. Listar cada post e página com título, slug, data e categoria
  2. Identificar qual conteúdo tinha tráfego ativo e qual estava parado
  3. Sinalizar qualquer post com shortcode embutido que precisaria de atenção manual

O resultado foi um inventário limpo em uns 30 segundos. Em um site grande, só isso já economiza horas.

Prompt que eu usei:

Aqui está um export XML do WordPress. Me dê:
1. Uma tabela de todos os posts: título, slug, data de publicação, categoria, contagem de palavras
2. Uma lista de todos os shortcodes usados no conteúdo
3. Qualquer iframe ou script de terceiro que você conseguir identificar
Formate como markdown.

Passo 2 — Converter o conteúdo pra Markdown

O WordPress guarda conteúdo como HTML com shortcode misturado. O Astro quer Markdown com frontmatter. O Claude cuidou da maior parte dessa conversão automaticamente.

Prompt pra conversão de conteúdo:

Converta este HTML de post do WordPress pra Markdown com frontmatter do Astro.
Preserve todos os títulos, links, imagens e formatação.
Gere o frontmatter com: title, description, pubDate, slug, tags.
Sinalize qualquer shortcode que você não conseguir converter com um comentário [REVISAR MANUALMENTE].

Em 80% dos posts o resultado saiu limpo e pronto pra usar. Os 20% restantes tinham:

  • Shortcodes de formulário ([contact-form-7]) — precisavam de substituição
  • Shortcodes de galeria ([gallery ids="..."]) — precisavam de reconstrução manual
  • Shortcodes específicos de plugin — variava de plugin pra plugin

O problema dos shortcodes é onde a maioria das migrações de WordPress trava. Falo mais da substituição do formulário abaixo.

Passo 3 — Configurar o Astro no Cloudflare Pages

npm create astro@latest my-site
cd my-site
npx astro add cloudflare

O adapter do Cloudflare cuida do formato de saída do build pro Pages. Depois disso, conecte seu repo do GitHub no Cloudflare Pages e ele faz deploy a cada push.

astro.config.mjs básico:

import { defineConfig } from 'astro/config'
import cloudflare from '@astrojs/cloudflare'

export default defineConfig({
  output: 'static',
  adapter: cloudflare(),
})

Pra um site puramente estático, output: 'static' é o que você quer. Sem renderização no servidor, sem cold start, HTML puro na edge.

Passo 4 — Migrar conteúdo e assets

Pedi pro Claude gerar um script de migração que:

  1. Lesse o export XML do WordPress
  2. Criasse arquivos .md individuais pra cada post com o frontmatter certo
  3. Renomeasse os arquivos de mídia pra uma convenção limpa baseada em slug
  4. Atualizasse as referências internas de imagem no conteúdo
// Script de migração gerado pelo Claude (simplificado)
import { parseStringPromise } from 'xml2js'
import { writeFileSync, mkdirSync } from 'fs'
import { slugify } from './utils'

const xml = readFileSync('export.xml', 'utf-8')
const data = await parseStringPromise(xml)
const posts = data.rss.channel[0].item

for (const post of posts) {
  const title = post.title[0]
  const content = post['content:encoded'][0]
  const date = post.pubDate[0]
  const slug = post['wp:post_name'][0]

  const frontmatter = `---
title: "${title}"
pubDate: ${new Date(date).toISOString()}
slug: "${slug}"
---\n\n`

  writeFileSync(
    `src/content/blog/${slug}.md`,
    frontmatter + convertToMarkdown(content)
  )
}

Pra mídia, mantive os arquivos na pasta public do Cloudflare Pages e atualizei as referências nos arquivos Markdown. Pra sites maiores com muita mídia, vale olhar o R2, mas pra maioria dos sites institucionais e landing pages, servir os assets direto do Pages já basta. O Claude gerou o script de find-and-replace pra reescrever as URLs.

Passo 5 — O problema dos formulários

É aqui que toda migração de WordPress pra estático bate na mesma parede.

Contact Form 7, Gravity Forms, WPForms — todos dependem de PHP rodando no servidor. Você vai pro estático e eles simplesmente param de funcionar. Não tem servidor pra processar o envio.

As opções que avaliei:

Escrever um Cloudflare Worker — possível, mas aí você está mantendo um backend pro que é essencialmente um formulário de contato. Configurar envio de e-mail, validação, proteção contra spam. Mais peças móveis do que eu queria.

Usar um serviço de backend de formulário — coloca um endpoint no formulário e o serviço cuida do resto. Sem servidor, sem manutenção.

Fui de FormRoute. A migração do Contact Form 7 foi direta:

Antes (shortcode do Contact Form 7):

[contact-form-7 id="123" title="Formulário de contato"]

Depois (componente Astro):

---
// src/components/ContactForm.astro
---

<form action="https://api.formroute.dev/f/YOUR_KEY" method="POST">
  <input type="text" name="name" placeholder="Seu nome" required />
  <input type="email" name="email" placeholder="Seu e-mail" required />
  <textarea name="message" placeholder="Sua mensagem" required></textarea>

  <div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_KEY"></div>

  <input type="text" name="_honeypot" style="display:none" tabindex="-1" />

  <button type="submit">Enviar</button>
</form>

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async></script>

A proteção contra spam roda via Cloudflare Turnstile, invisível pra usuário real. Os envios vão pro dashboard do FormRoute e disparam uma notificação por e-mail. O plano gratuito cobre 1.000 envios por mês.

Passo 6 — Redirects

As URLs do WordPress nem sempre batem com o que você quer num site estático. Você precisa redirecionar as URLs antigas pras novas pra não perder SEO nem quebrar links existentes.

O Cloudflare Pages lida com redirects via um arquivo _redirects na pasta public:

/old-post-url/ /new-post-url/ 301
/category/news/ /blog/ 301
/?p=123 /post-slug/ 301

Passei pro Claude a lista de URLs antigas e a lista de slugs novos e pedi pra ele gerar o arquivo de redirects. Ele resolveu o mapeamento de primeira.

Pra sites WordPress com URLs no estilo ?p=123, o padrão é:

/?p=:id /blog/:slug 301

Você vai precisar mapear cada ID pro slug manualmente ou via script — o Claude consegue gerar esse script a partir do export XML.

O que quebrou (lista honesta)

Comentários — comentários do WordPress não têm equivalente estático. Eu substituí por nada. Se comentário importa pro seu site, olhe o Giscus (baseado em GitHub Discussions) ou simplesmente remova.

Busca — a busca do WordPress é server-side. Site estático precisa de busca client-side. O Pagefind funciona bem com Astro e leva 10 minutos pra configurar.

WooCommerce — se o seu site WordPress tem e-commerce, esse caminho de migração não se aplica. WooCommerce exige servidor. Estático + Shopify Buy Button ou uma abordagem headless é a alternativa.

Alguns plugins de SEO — os dados do Yoast SEO vivem no meta do WordPress. O export XML inclui isso, mas você precisa mapear manualmente pro frontmatter do Astro. O Claude consegue gerar o script de mapeamento, mas dá uma limpada.

Shortcodes específicos de plugin — qualquer coisa de plugin que não seja conteúdo (contador regressivo, widget de agendamento, mapa interativo) precisa de substituto. Não tem resposta universal aqui — depende do que o plugin fazia.

O resultado

  • Tempo de build: menos de 10 segundos
  • Time to first byte: menos de 50ms globalmente (edge da Cloudflare)
  • Nota no Lighthouse: 98–100 em tudo
  • Custo de hospedagem: $0 (plano gratuito do Cloudflare Pages)
  • Manutenção: zero PHP, zero atualização de plugin, zero patch de servidor

A migração assistida por IA levou cerca de um dia de trabalho real pra um site de porte médio (80 posts, 20 páginas). Sem IA o mesmo trabalho teria levado uma semana — conversão de conteúdo e geração de script foram as maiores economias de tempo.

Resumo — no que a IA ajudou

TarefaTempo sem IATempo com IA
Inventário de conteúdo2–3 horas10 minutos
Conversão de HTML pra Markdown1 dia10 minutos
Scripts de migração4–6 horas10 minutos
Arquivo de redirects2 horas5 minutos
Geração de componentes3 horas20 minutos

As partes em que a IA não ajudou: decidir o que manter, o que cortar e como lidar com funcionalidade específica de plugin. Isso continua sendo com você.

Recursos

Atualização: depois escrevi sobre como isso ficou no portfólio inteiro, do ponto de vista de custo: De $39/mês para $1: como tirei mais de 10 sites da Hostinger de graça