WordPress para Site Estático Com Claude Code: Como Reconstruí Este Site em uma Tarde
- Categoria
- IA e LLM Local
- Publicado em
- 11 julho 2026
- Por
- Jacob Lloyd — escrito com ajuda de IA, depois do projeto
- Tempo de leitura
- 9 min de leitura
Em termos simples: A história de como este exato site foi reconstruído em uma tarde por um assistente de codificação com IA, saindo do WordPress para uma pasta de arquivos simples. O novo site é mais rápido, gratuito para rodar e não tem tela de login para hackers atacarem. Termina com a receita passo a passo para fazer o mesmo no seu site.
Este site rodava em WordPress. Agora é uma pasta de arquivos Markdown e um script Python de 183 linhas, construído a partir de uma exportação do WordPress por um agente de codificação com IA em uma tarde. Aqui está o que aconteceu, e por que o mesmo fluxo de trabalho ainda toca a casa.
- O que é: um gerador de site estático feito do zero (Python + Jinja2) que substituiu o WordPress, mais um importador único para o conteúdo antigo
- Quanto custa: nada. Sem plugins, sem licença de tema, a mesma hospedagem compartilhada barata de antes
- O que você precisa: sua exportação do WordPress (um arquivo WXR), sua pasta de uploads e um agente de codificação com IA — eu usei o Claude Code
- No que dá: arquivos HTML puros, sem banco de dados, sem tela de login para forçarem a entrada
No que isso dá
Primeiro, os comprovantes — a saída real do terminal no dia em que a reconstrução ficou pronta:
$ python3 _build/build.py
Built 15 files (7 projects, 3 pages, 4 categories).
Quinze páginas HTML era o site inteiro na época da migração: home, quatro páginas de categoria, sete posts de projeto, três páginas de informação. Tudo pré-renderizado, tudo arquivo simples, zero código rodando no servidor a cada requisição.
A exportação do WordPress que deu origem a isso tinha 572 KB e 123 itens — posts, páginas, registros de anexos de imagem e uma pilha de rascunhos abandonados. Sete posts de verdade sobreviveram à viagem. O resto era lixo que o importador pulou de propósito.
E a máquina que constrói tudo cabe em uma tabela:
| Peça | Linhas |
|---|---|
| build.py (o gerador) | 183 |
| migrate.py (o importador único) | 166 |
| Templates (5 layouts de página) | 139 |
| Parciais (nav, rodapé, card) | 51 |
| site.yaml (toda a configuração do site) | 36 |
| serve.sh (pré-visualização local) | 12 |
| .htaccess (regras do servidor) | 57 |
| Total | 644 |
Some o CSS (322 linhas) e o único arquivo JavaScript (31 linhas — um botão de menu mobile, só isso) e o site inteiro fica abaixo de 1.000 linhas. Sem node_modules, sem framework de build, sem lockfile para discutir.
Parte 1: saindo do WordPress
Eu não escrevi nada disso à mão. Descrevi o que eu queria para o Claude Code e ele escreveu o importador, depois o gerador, depois os templates, enquanto meu trabalho era revisar a saída e dizer "não, faça assim". Os timestamps dos arquivos confirmam: da exportação em mãos a um site construído e navegável em menos de duas horas. Uma tarde, não um fim de semana.
O pipeline em que ele chegou:
A exportação e o espelho da mídia
O WordPress exporta tudo pelo caminho Ferramentas → Exportar → Todo o conteúdo. Você recebe um arquivo WXR — XML com seus posts, páginas, categorias e registros de anexos empacotados dentro. Essa é a única entrada do importador.
A única coisa que a exportação não inclui são seus arquivos de imagem de verdade, apenas as URLs deles. Então a pasta de uploads é puxada à parte — SFTP, um gerenciador de arquivos, ou um simples espelho via wget — para dentro de assets/uploads/, mantendo a mesma estrutura de ano/mês que o WordPress usava, para que as URLs reescritas continuem resolvendo.
O que o migrate.py realmente faz
O importador é de uso único, mas seguro para rodar de novo quantas vezes precisar. Nenhuma biblioteca de XML — só regex contra o texto do WXR, o que soa frágil até você perceber que o XML de exportação do WordPress é extremamente regular, e usar grep nele dá muito menos código do que montar um parser completo. Para cada post ele extrai o título, o slug, a data, o corpo, a categoria e a imagem destacada, e então:
- Pula lixo — posts vazios e qualquer coisa com título "(draft)…" nunca chega em content/
- Mapeia categorias para um conjunto curto e limpo de chaves em vez dos nicenames bagunçados do WordPress
- Resolve imagens destacadas pelo id do anexo, recorrendo à primeira imagem enviada no corpo do texto se não houver uma
- Reescreve URLs de mídia de
wp-content/uploads/…para/assets/uploads/… - Reescreve links internos — os permalinks antigos viram as novas URLs limpas, usando um mapa de slugs montado a partir de cada post e página
- Remove os comentários de bloco do Gutenberg e apaga de vez os blocos de listagem dinâmica — a etapa de build agora gera as listagens de categoria, então as antigas ficariam ali só envelhecendo
- Gera resumos automaticamente — remove as tags, corta em 160 caracteres, usa como texto de card e meta description
A parte interessante: os posts saem do outro lado como HTML do WordPress já limpo, sentado dentro de um arquivo Markdown, e não convertido para a sintaxe do Markdown. O renderizador deixa o HTML puro passar direto, então nada foi perdido do conteúdo antigo — e os posts novos são escritos em Markdown simples. Os dois tipos constroem sem problema a partir da mesma pasta. Da primeira vez que abri um arquivo ".md" que era 90% tags HTML, achei que algo tinha quebrado. Não tinha.
O que o build.py faz
Essa é a parte que roda para sempre. Ele lê o site.yaml para nav, categorias e links, lê tudo dentro de content/, e renderiza cada arquivo por um template Jinja2 dentro de uma pasta de verdade com um index.html lá dentro — URLs limpas sem precisar de regra de reescrita no servidor. Além das próprias páginas, ele produz:
- Uma barra de navegação e um rodapé montados a partir de dados — adicione uma categoria ou uma página e ela aparece sozinha
- Título por página, meta description, URL canônica, tags Open Graph e Twitter
- sitemap.xml, robots.txt e um web manifest, regenerados a cada execução
- Uma passagem de limpeza que apaga a saída antiga primeiro, para que um post renomeado não deixe uma página morta para trás
- Zero dependências em tempo de execução: fontes hospedadas localmente, ícones SVG inline, aquele único arquivo JS pequeno, nada vindo de CDN
Adicionar um post é: soltar um arquivo Markdown em content/projects/, rodar o build. Esse é o fluxo de trabalho inteiro.
Pré-visualize, depois faça o deploy
A pré-visualização local é um único script — constrói e depois serve. Clique em cada página antes de qualquer coisa chegar perto do servidor de verdade.
./serve.sh
# builds, then serves the whole site at http://localhost:8099
O deploy é um envio via rsync guiado por um manifest que lista exatamente o que é enviado (HTML, CSS, JS, imagens, fontes, .htaccess) e o que é negado (os scripts Python, o código-fonte em Markdown, a exportação do WordPress, os templates). A configuração do servidor adiciona compressão, cabeçalhos de cache, cabeçalhos de segurança e uma negação total ao acesso direto a qualquer arquivo de origem — então mesmo que alguém saiba que o gerador existe, não há nada para baixar.
Parte 2: como este site é mantido agora
Aqui está o que fez valer a pena reconstruir com capricho: o site também não é mais mantido à mão. O mesmo fluxo de trabalho de agentes que o construiu o mantém atualizado — incluindo a atualização que você está lendo agora.
Esta revisão começou com um enxame de auditoria. Uma equipe checou os fatos de cada página publicada e clicou em cada link externo; outra reuniu capturas de tela e fatos técnicos concretos sobre como o site funciona. Tudo isso alimentou uma única especificação escrita: construa isto, não invente números, nunca publique um segredo.
A partir daí o trabalho se espalhou — um agente de plataforma (estrutura de categorias, um segundo idioma, a parte de mecanismos de busca), um designer, e um agente de escrita por artigo, este incluído. Um modelo rápido rascunha cada página a partir do arquivo de fatos dela. Um modelo mais forte edita para voz e precisão. Depois uma passagem adversarial tenta quebrar tudo — reconferindo números contra os fatos de origem, reclicando links, caçando qualquer coisa que soe inventada. Ou, sendo honesto, qualquer coisa que soe como se uma IA tivesse escrito. O que, neste caso, uma escreveu mesmo.
Passe esse trabalho para o seu próprio agente
Está com um site antigo em WordPress que você não quer mais ficar remendando para sempre? Esta é a receita que eu passei para o meu. Descreva cada etapa para o seu agente e deixe que ele escreva o código.
- Exporte. No admin do WordPress → Ferramentas → Exportar → Todo o conteúdo. Deixe o arquivo WXR onde o agente possa lê-lo, por exemplo
_build/source.xml. - Espelhe a mídia. Copie
wp-content/uploads/do host antigo paraassets/uploads/, mantendo a estrutura de ano/mês. - Peça um script de migração. Analise os itens do WXR, pule rascunhos e itens vazios, mapeie categorias para uma lista curta e limpa, resolva imagens destacadas (recorrendo à primeira imagem inline se faltar), reescreva URLs de upload e links internos, remova comentários de bloco do Gutenberg, gere resumos automaticamente, escreva arquivos Markdown com frontmatter.
- Peça um gerador. Leia uma configuração de site mais a pasta de conteúdo, renderize por templates (base, home, categoria, post, página, parciais compartilhadas de nav/rodapé/card), gere pastas com URL limpa, produza sitemap.xml, robots.txt e um web manifest.
- Reconstrua o design do zero. Não migre o CSS do tema antigo — uma folha de estilo única e limpa, fontes abertas hospedadas localmente, ícones SVG inline, zero requisições externas.
- Pré-visualize tudo. Um script de uma linha que constrói e depois serve localmente. Clique em cada página.
- Escreva a configuração do servidor. Compressão, cabeçalhos de cache, cabeçalhos de segurança, regras de negação para arquivos de origem, um fallback para URLs sem extensão.
- Faça o deploy com um manifest. Envie a saída via rsync, excluindo explicitamente as pastas de origem e os scripts, depois confira que a URL ao vivo retorna 200.
- Guarde a exportação. O arquivo WXR fica no projeto — rodar o script de migração de novo regenera a pasta de conteúdo do zero, de forma limpa, sempre que você precisar de uma importação nova.
A lista completa de dependências em tempo de execução, na íntegra:
pip install jinja2 pyyaml markdown
É isso.
Pegadinhas
- Esquecer de espelhar a pasta de uploads e nada avisa você. O build funciona perfeitamente enquanto cada miniatura dá 404. Descobri da forma difícil.
- Saída e origem compartilham a mesma pasta. O HTML gerado cai na mesma raiz do projeto que o código-fonte em Python e Markdown — a separação do deploy é inteiramente a lista de allow/deny. Pareceu estranho no começo; é o que mantém o deploy sendo um rsync simples.
- Renomear uma categoria pode deixar uma pasta morta para trás. A etapa de limpeza tem os nomes das categorias digitados diretamente nela. Renomeie uma na configuração, esqueça essa lista, e a pasta antiga fica ali publicada e órfã.
- Datas são ordenadas como texto, não como datas. Sem problema desde que toda data seja escrita como
AAAA-MM-DD. Erre uma e seu post mais recente afunda silenciosamente para o final.