23/05/2026
Por que pausei meu CMS custom pra apostar no EmDash
Passei boas horas construindo um CMS na stack da Cloudflare (Workers + D1 + R2 + Hono) com Claude Code pra substituir o Keystatic. Funcionou. E mesmo assim resolvi pausar. Esse post conta o porquê, o que aprendi com a stack, e por que o EmDash chegou na hora certa.
Esse site usa Astro e implementei o Keystatic, e eu gosto bastante. Conteúdo no repo, admin leve, deploy via git. Pra projeto pessoal, recomendo sem hesitar.
Mas algumas coisas começaram a apertar quando pensei em rodar Keystatic em projetos de clientes da Toledo Interactive: ordenação ruim e sem persistir, sem drafts reais (precisa criar branch separado), sem armazenar dados, não tem como customizar muito o admin e os componentes custom dependem do @keystatic/astro que ainda não suporta Astro 6 até o momento de publicação desse post. Nenhum desses pontos é dealbreaker pra um site. Mas pra rodar cinco ou dez, vira atrito.
Em vez de pular pra outro CMS pronto, decidi construir o meu. Era pra ser projeto de aprendizado e ferramenta de produção ao mesmo tempo: rodar nas mesmas APIs que ofereço pros meus clientes (Workers, D1, R2), usar Claude Code o tempo todo como par de programação, e validar até onde dá pra ir com a stack da Cloudflare sem depender de SaaS de terceiro.
Foram aproximadamente 8 horas espalhadas ao longo de alguns dias. Cheguei num CMS funcional, deployado em cms.marciotoledo.com, com todo o conteúdo do site migrado pra dentro do D1. Funciona. E ainda assim, resolvi pausar antes de plugar ele no site público.
Esse post conta os dois lados. Primeiro o que eu construí (vale como referência técnica pra quem quer fazer algo parecido). Depois por que o EmDash chegou e fez sentido apostar nele em vez de seguir com o CMS.
A stack que escolhi pra construir o CMS
Decisão número um: nada de framework de CMS pronto (Payload, Strapi, Directus). A ideia era controlar 100% da superfície e aprender. Stack:
- Cloudflare Workers como runtime. Plano free dá 100k requests/dia, mais que suficiente. O CMS é um Worker único hospedado em
cms.marciotoledo.com(rota da zona). - D1 (SQLite gerenciada da Cloudflare) pro banco. Posts, works, tools, skills, quotes, tags, users, sessions, media metadata.
- R2 pro storage de mídia. Uploads de imagem vão direto pra lá, servidos via rota do Worker (
/media/*) com cacheimmutable. - Hono como router HTTP. Pequeno (14KB gzipped), bindings nativos pra D1 e R2, suporte a Hono JSX (renderização server-side com sintaxe React, sem React no bundle).
- Tailwind CSS via CDN pra UI do admin. Não precisa build step, página carrega em 80ms.
- Alpine.js + SortableJS via CDN pra interações mínimas (toggles de idioma, drag-and-drop). Sem framework JS no bundle.
- PBKDF2-SHA256 nativo (via
crypto.subtle) pra hash de senha. Tentei scrypt no início e estourou o limite de 10ms de CPU do Workers Free, com erro 1102. Trocar pra PBKDF2 resolveu na hora. - HMAC-signed cookie pra sessão. Sem JWT, sem magic link, single-user com email/senha. ~80 linhas de código pro auth inteiro.
Bundle final do Worker: 158KB, 36KB gzipped. Bem dentro do limite do plano free (3MB).
O que ele faz hoje
| Feature | Status |
|---|---|
| Login com sessão de 30 dias (rate limit 10/min/IP) | pronto |
| CRUD de posts com EasyMDE (markdown editor) | pronto |
| Upload de imagens pro R2 inline no editor | pronto |
| CRUD genérico de works, tools, skills, quotes | pronto |
| Drag-and-drop pra reordenar collections | pronto |
| Toggle PT/EN nos campos bilíngues, com persistência via localStorage | pronto |
| Slug auto-gerado do título + botão pra regenerar | pronto |
API REST pública /api/v1/* com Bearer token | pronto |
| Backup semanal automatizado via cron (D1 → R2 com retenção de 12) | pronto |
| Botão “Publicar site” pra disparar deploy hook do Pages | armado |
Conteúdo migrado: 10 posts (com markdown + custom directives reescritas), 44 works, 2 tools, 2 skills, 29 quotes, 7 singletons. Tudo em D1, imagens em R2 com paths flat (media/foo.jpg, sem subpasta por slug pra desacoplar imagem do post).
O que aprendi construindo isso
Algumas coisas que só descobri colocando a mão.
A stack da Cloudflare é absurdamente boa pra esse caso. Cold start zero, latência baixa, custo previsível. O conjunto D1 + R2 + Workers cobre 100% do que um CMS pequeno precisa, sem precisar de Postgres, Redis, S3, ou qualquer outra peça externa. Pra projetos da Toledo Interactive (sites institucionais, blogs, landing com formulário), isso é o nível certo de complexidade.
Workers Free tem armadilhas reais. O limite de 10ms de CPU é mais apertado do que parece. Scrypt estoura. Markdown rendering pesado também (não cheguei a testar, mas vi gente reclamando). Pra um CMS read-mostly que serve HTML estático e API JSON, é tranquilo. Pra qualquer transformação CPU-intensa, plano pago é necessário.
Image Transformations da Cloudflare não funciona sobre origem externa no plano free. O /cdn-cgi/image/.../<url-externa> retorna 403. Solução: o bucket R2 vai ser montado como binding no Pages do site também, expondo em marciotoledo.com/media/*. Assim o helper de imagem do site continua usando path local e a transformação funciona.
Schema em código declarativo é poderoso. Cada singleton e cada collection do CMS é descrita por um objeto TypeScript ({ fields: [...], hasSlug: true, sortable: true }), e a UI inteira é gerada genericamente desse schema. Adicionar um campo novo é trocar uma linha, não escrever um form do zero. Esse é o padrão de Sanity e Payload, e funciona bem mesmo numa versão minimalista.
Claude Code mudou minha velocidade de desenvolvimento. Foram dias inteiros em que eu descrevia o que queria e ia validando o resultado, sem escrever quase nada de código manualmente. Pra projeto de aprendizado, vale principalmente porque eu ainda lia tudo e entendia o que estava sendo gerado. Pra projeto de produção crítica, talvez deixaria de fazer review-line-by-line e isso preocupa. Mas a aceleração é real.
Construir CMS é um buraco sem fundo. Eu cobri o básico. Falta: media library com busca, image cropping, versionamento de conteúdo, multi-user com permissões, 2FA, preview de drafts no site público, integração com forms de contato. Cada um desses é uma feature séria, com edge cases. A versão “boa o suficiente” pra Marcio Toledo não é a versão “boa o suficiente” pra clientes pagantes.
Aí o EmDash apareceu
A Cloudflare lançou o EmDash em abril de 2026. Não tinha radar pra ele quando comecei o projeto. Quando vi, foi um soco no estômago e ao mesmo tempo um alívio.
Vou listar exatamente por que ele bateu fundo:
- Mesma stack que eu escolhi. Astro + Workers + D1 + R2. Não um SaaS proprietário, não um Postgres caro. A infra que eu já entendo, no provedor que eu já uso.
- Open-source MIT. Sem licenciamento por site, sem cobrança por seat. Hospedando no Cloudflare, custo mínimo: $5/mês de Workers Paid pra ter os limites maiores e desbloquear features como Worker Loader (sandboxing de plugins). Sem surpresa no fim do mês.
- Spiritual successor do WordPress. Essa parte é importante pra mim. Tenho mais de uma década evitando WordPress (segurança, performance, manutenção, a experiência de admin que envelheceu mal). Sempre faltou um substituto sério pra quando o cliente precisa do “eu mesmo edito meu site” de verdade, sem ser Webflow ou Wix. Sanity é técnico demais pra cliente não-dev, Strapi é pesado demais pra hospedar, Payload é caro pra escalar. EmDash propõe ser o que o WordPress foi: o default razoável.
- Integração nativa com Astro. Uma linha no
astro.config.mjs. O CMS roda no mesmo Worker do site, lê do mesmo D1, escreve no mesmo R2. Sem ponte, sem API entre eles. - Admin pronto em
/_emdash/admin. Schema vive na DB (não em código), seedado de umseed.json. Editar schema é editar JSON e rodar reseed. Tipo Strapi mas mais leve. - MCP server embutido. Documentação acessível direto via Claude Code como ferramenta. Plus: ferramentas pra agentes IA interagirem com conteúdo do site direto da CLI.
- Plugin system com sandboxing. Webhooks, forms, integrações. Plugins rodam isolados em Dynamic Workers (no Cloudflare). Comunidade já tá publicando coisa interessante.
- 190 releases em ~1 ano. Ritmo de quem está entregando. 10k stars no GitHub. Beta declarado, mas com supply-chain hardening sério (cooldown de release, bloqueio de subdeps exóticos). Não é vibe-coded.
Resumindo: é literalmente o CMS que eu estava construindo, mas pronto, mantido por uma comunidade, com features que eu não ia ter tempo de fazer.
A conta de custo de oportunidade
A pergunta óbvia: você gastou várias horas (não muitas por causa do Claude Code) e vai jogar fora?
Não exatamente. A conta é outra.
Cenário A: continuar com o CMS custom.
- Fase 2 (refatorar o site pra consumir a API do CMS, remover Keystatic + React): estimadas 25-35 horas (bem menos com Claude Code).
- Validação paralela (1-2 semanas usando o admin de verdade).
- Manutenção contínua: ~20h/ano de patches, edge cases, upgrades de stack. Sozinho.
- Adicionar features que clientes vão pedir (forms, multi-user, media library decente): mais 100-200h ao longo de 6-12 meses (ou bem menos usando Claude Code).
- Resultado: CMS proprietário que só eu conheço, com superfície de bug que só eu vejo.
Cenário B: pausar o CMS e apostar no EmDash.
- Construir o site da toledointeractive.com no EmDash do zero (já está instalado, schema starter, lab real).
- 2-4 semanas usando ele em produção pra sentir UX, validar i18n bilíngue, descobrir limitações.
- Contribuir com issues e PRs (já tenho stack mental do problema). Especialização em vez de manutenção isolada.
- Resultado: stack que clientes podem herdar, comunidade que carrega o peso comigo, features novas chegam sem eu trabalhar.
A diferença é energia. Manter um CMS proprietário consome energia que eu prefiro gastar entregando projeto pra cliente.
O que faço agora
Pausei o CMS. Não deletei nada. O Worker continua no ar em cms.marciotoledo.com, com todo o conteúdo migrado, como referência e backup. O site do marciotoledo.com segue rodando em Keystatic em produção, intocado. A Fase 2 (refator pra consumir do CMS) está pausada por tempo indefinido.
Em paralelo, instalei o EmDash em um ambiente de testes e vou construir o site da agência nele de verdade. Daqui umas semanas, com EmDash debaixo da unha, decido se migro este site também ou se sigo no Keystatic enquanto o EmDash amadurece pra cobrir os casos mais complexos (i18n bilíngue por field, markdown com directives custom, design opinionado).
O que valeu
Mesmo pausando o projeto, valeu cada hora. O que ganhei:
- Domínio real da stack Cloudflare. Workers, D1, R2, secrets, cron triggers, deploy via wrangler, bindings, rate limits do plano free. Tudo isso é base pra qualquer projeto que eu monte da agência daqui pra frente.
- Senso de proporção do que é construir um CMS. Os 80% mais visíveis são os 20% do trabalho. Os 20% chatos (auth, cache invalidation, edge cases de upload, gestão de schema, reorder, busca) são os 80% que ninguém vê e dão dor de cabeça.
- Validação de Claude Code como par de programação. A diferença de velocidade quando o código pode ser descrito em texto e revisado em tela é gigante. Pra projetos onde eu domino o domínio, é multiplicador real. Pra projetos onde eu não domino, é faca de dois gumes.
- Material pra esse post. Esse aqui é um caso onde a jornada vale mais que o destino.
Resumo
Construí um CMS custom em uma semana pra substituir o Keystatic, na stack Workers + D1 + R2 + Hono + Astro. Funcionou. Logo depois a Cloudflare lançou o EmDash, que é literalmente o mesmo CMS, pronto, open-source, com comunidade.
Pausei o meu, montei o EmDash em outro projeto da agência pra validar, e vou contribuir com a comunidade dele em vez de manter código que só eu conheço. O CMS custom continua no ar como referência. O site marciotoledo.com segue em Keystatic até a decisão final.
Não foi tempo perdido. Foi o melhor jeito de eu chegar com base própria pra entender o que um CMS sério precisa fazer, e por que vale apostar num projeto coletivo em vez de carregar o meu sozinho.
Referências
- Não sabe o que é Astro? astro.build
- Não sabe o que é Keystatic? keystatic.com
- EmDash CMS. emdashcms.com
- EmDash no GitHub. github.com/emdash-cms/emdash
- Cloudflare. Workers Free Plan limits
- Cloudflare. D1 pricing
- Cloudflare. R2 pricing
- Hono. hono.dev
- Marcio Toledo. Alternativas de CMS para Astro em 2026 (post anterior, comparação técnica)
- Marcio Toledo. Preços e planos dos CMSs para Astro em 2026 (post anterior, números)