# EduardoVillão — full content > Every post and page in Markdown, concatenated for LLM ingestion. Curated index at https://eduardovillao.me/llms.txt # From $39/Month to $1: How I Moved 10+ Sites Off Hostinger for Free URL: https://eduardovillao.me/from-39-month-to-1-how-i-moved-10-sites-off-hostinger-for-free/ Language: en Published: 2026-07-15 Last month I finally did some math I'd been putting off: how much I was actually paying to keep a bunch of sites online. $39/month on Hostinger (about R$200, I'm in Brazil). For hosting 10+ sites: product landing pages, blogs, a couple of small tools. Every month, on autopilot, straight off the card. Then I asked myself the obvious question I'd been avoiding: **out of those 10+ sites, how many actually need a server running 24/7?** Answer: none. ## What these sites actually are A product landing page doesn't need PHP processing a request. A blog doesn't need a database query on every page view. A marketing site doesn't change its content every second. That's HTML, CSS, and JS you can generate once and serve from a CDN. In other words: a static site. A few real examples I migrated: - [eduardovillao.me](/) → my personal blog, built with Astro - [formroute.dev](https://formroute.dev/) → a SaaS landing page, plain HTML - [wpfeatureloop.com](https://wpfeatureloop.com/) → a dev tool landing page, plain HTML Three different kinds of sites (blog, SaaS, dev tool), two different stacks, and none of them needed a server running around the clock just to exist. The reason I hadn't migrated sooner wasn't technical. It was inertia. "It's already paid for, it already works, leave it alone." Classic. ## The migration I moved everything to **Cloudflare Pages**. The reasoning is boring because it's so simple: it's free, global CDN, automatic SSL, Git-based deploys, custom domains at no extra cost. For static sites, there's really nothing to debate. The process, in short: - Each site became a repo (or a folder inside a monorepo, depending on the case) - Connected the repo to Cloudflare Pages - Set up the build, mostly plain HTML, Astro for the blog where I wanted content collections and a proper writing workflow - Pointed the domain, SSL came up on its own - Cancelled hosting for that domain on Hostinger Repeated that site by site. No magic, just repetitive work, but each one took about 20-30 minutes. (If you want the technical deep dive on one specific migration, including the AI-assisted scripts and what actually broke, [I wrote that up separately](/how-i-migrated-a-wordpress-site-to-cloudflare-pages-using-ai-and-what-broke/). This post is more about the bigger picture across the whole portfolio.) Result: **11 sites, $0/month in hosting.** ## What was left: email Static hosting solves the site, but not email. If you have contact@yourdomain.com, that's a separate service, not "hosting," and most people don't even realize they're paying for it bundled into a hosting plan. For that I moved to EmailSpaces: custom domain email for $0.98/month, per domain. Point the MX records, done. Doing the math: $39/month became roughly $1 per domain that actually needs professional email (not all of them do). In practice, total cost dropped over 95%. ## Every new product I ship now starts this way This wasn't just a one-time cleanup, it became the default. wpfeatureloop.com, for example, launched static from day one, no "I'll figure out hosting later." The question changed from "where do I host this" to "does this actually need to stop being static at some point." Spoiler: it rarely does. ## The piece that's still missing: forms There's a real limitation, though. A static site doesn't run a backend. So how do you receive a contact form, a waitlist signup, or a lead if there's no server to process that POST request? That's literally the problem that led me to build [FormRoute](https://formroute.dev): a form backend you point your `
`'s `action` to, and that's it, you get the submissions without needing your own server or backing out of the decision to go fully static. It's the piece that closes the loop: free static hosting + ~$1/month email + working forms with no server = a product live with zero infrastructure cost until it actually gets traction. ## The final numbers - Hosting: $39/month → $0/month - Email: bundled (and hidden) in the plan → ~$1/month per domain, only where needed - Forms: would've needed a server → solved with FormRoute This isn't really about saving $38/month, though that already covers a coffee a day. It's about realizing that most of the sites we keep on "real hosting" never actually needed it. We just never stopped to question the plan that was already running. And this matters more now than it did a couple of years ago. With AI, spinning up a new landing page, a new experiment, a new idea you want to test in public takes minutes, not days. If shipping the idea is fast but hosting it still feels like a decision (pick a plan, pay monthly, remember to renew), that friction is the thing slowing you down, not the idea itself. Free static hosting removes that friction completely. You ship, you see if it sticks, and if it doesn't, you didn't lose anything by trying. If you've got a list of sites sitting on Hostinger, GoDaddy, or any traditional host today, it's worth asking that question for each one: "Does this site change in real time, or is it content I generate and serve?" If it's the second one, you're probably paying for something that should be free. --- # De $39/mês para $1: como tirei mais de 10 sites da Hostinger de graça URL: https://eduardovillao.me/pt-br/de-39-mes-para-1-como-tirei-mais-de-10-sites-da-hostinger-de-graca/ Language: pt-br Published: 2026-07-15 Mês passado eu finalmente fiz uma conta que vinha empurrando com a barriga: quanto eu estava realmente pagando pra manter um monte de site no ar. $39/mês na Hostinger (uns R$200). Pra hospedar mais de 10 sites: landing pages de produto, blogs, algumas ferramentinhas. Todo mês, no automático, direto no cartão. Aí eu me fiz a pergunta óbvia que estava evitando: **desses 10+ sites, quantos realmente precisam de um servidor rodando 24 horas por dia?** Resposta: nenhum. ## O que esses sites são de verdade Uma landing page de produto não precisa de PHP processando requisição. Um blog não precisa de query no banco a cada visualização. Um site institucional não muda o conteúdo a cada segundo. Isso é HTML, CSS e JS que você pode gerar uma vez e servir de um CDN. Ou seja: um site estático. Alguns exemplos reais que eu migrei: - [eduardovillao.me](/pt-br/) → meu blog pessoal, feito com Astro - [formroute.dev](https://formroute.dev/) → landing de um SaaS, HTML puro - [wpfeatureloop.com](https://wpfeatureloop.com/) → landing de uma ferramenta pra devs, HTML puro Três tipos diferentes de site (blog, SaaS, ferramenta), duas stacks diferentes, e nenhum deles precisava de um servidor ligado o tempo todo só pra existir. O motivo de eu não ter migrado antes não era técnico. Era inércia. "Já tá pago, já funciona, deixa quieto." Clássico. ## A migração Movi tudo pro **Cloudflare Pages**. O raciocínio é chato de tão simples: é grátis, CDN global, SSL automático, deploy via Git, domínio customizado sem custo extra. Pra site estático não tem muito o que debater. O processo, resumido: - Cada site virou um repo (ou uma pasta dentro de um monorepo, dependendo do caso) - Conectei o repo no Cloudflare Pages - Configurei o build, quase tudo HTML puro, Astro no blog onde eu queria content collections e um fluxo de escrita decente - Apontei o domínio, o SSL subiu sozinho - Cancelei a hospedagem daquele domínio na Hostinger Repeti isso site por site. Sem mágica, só trabalho repetitivo, mas cada um levou uns 20-30 minutos. (Se você quiser o mergulho técnico em uma migração específica, incluindo os scripts com IA e o que realmente quebrou, [escrevi sobre isso separadamente](/pt-br/como-migrei-um-site-wordpress-para-o-cloudflare-pages-usando-ia-e-o-que-quebrou/). Este post é mais sobre o quadro geral do portfólio inteiro.) Resultado: **11 sites, $0/mês de hospedagem.** ## O que sobrou: e-mail Hospedagem estática resolve o site, mas não o e-mail. Se você tem contato@seudominio.com, isso é um serviço separado, não é "hospedagem", e a maioria das pessoas nem percebe que está pagando por isso embutido no plano. Pra isso eu fui pro EmailSpaces: e-mail com domínio próprio por $0,98/mês, por domínio. Aponta os registros MX e pronto. Fazendo a conta: $39/mês virou mais ou menos $1 por domínio que realmente precisa de e-mail profissional (nem todos precisam). Na prática, o custo total caiu mais de 95%. ## Todo produto novo que eu lanço já começa assim Isso não foi só uma faxina pontual, virou o padrão. O wpfeatureloop.com, por exemplo, nasceu estático desde o dia um, sem "depois eu resolvo a hospedagem". A pergunta mudou de "onde eu hospedo isso" pra "isso realmente vai precisar deixar de ser estático em algum momento". Spoiler: quase nunca precisa. ## A peça que ainda faltava: formulários Só que existe uma limitação real. Site estático não roda backend. Então como você recebe um formulário de contato, uma inscrição em waitlist ou um lead se não tem servidor pra processar aquele POST? Esse é literalmente o problema que me levou a construir o [FormRoute](https://formroute.dev): um backend de formulário pra onde você aponta o `action` do seu ``, e pronto, você recebe as respostas sem precisar de servidor próprio nem voltar atrás na decisão de ir 100% estático. É a peça que fecha o ciclo: hospedagem estática grátis + ~$1/mês de e-mail + formulários funcionando sem servidor = produto no ar com custo zero de infraestrutura até ele realmente engrenar. ## Os números finais - Hospedagem: $39/mês → $0/mês - E-mail: embutido (e escondido) no plano → ~$1/mês por domínio, só onde precisa - Formulários: precisariam de servidor → resolvido com o FormRoute Não é realmente sobre economizar $38/mês, embora isso já pague um café por dia. É sobre perceber que a maioria dos sites que a gente mantém em "hospedagem de verdade" nunca precisou disso. A gente só nunca parou pra questionar o plano que já estava rodando. E isso importa mais hoje do que importava dois anos atrás. Com IA, subir uma landing page nova, um experimento novo, uma ideia que você quer testar em público leva minutos, não dias. Se lançar a ideia é rápido mas hospedar ainda parece uma decisão (escolher plano, pagar mensalidade, lembrar de renovar), esse atrito é o que está te segurando, não a ideia. Hospedagem estática grátis elimina esse atrito completamente. Você lança, vê se cola, e se não colar você não perdeu nada tentando. Se você tem uma lista de sites parados na Hostinger, GoDaddy ou qualquer host tradicional hoje, vale fazer aquela pergunta pra cada um: "esse site muda em tempo real, ou é conteúdo que eu gero e sirvo?" Se for a segunda opção, você provavelmente está pagando por algo que deveria ser de graça. --- # GitHub Issues Is Going to Be My Support Channel URL: https://eduardovillao.me/github-issues-is-going-to-be-my-support-channel/ Language: en Published: 2026-07-13 I run a few products at once. Each one has its own domain, each domain has its own support inbox. I'm done managing a different tool for every single one of them. Here's the fix: support becomes an GitHub issue. A customer emails `support@mydomain.com`. That turns into an issue in one of my repos, private, mine only. I reply by commenting, same as I reply to anything else on GitHub. The customer gets my reply in their inbox and never has to know GitHub is involved at all. No new account for the customer. No new tool for me. History stays versioned, searchable, and I inherit everything that already works on top of Issues for free: labels, projects, saved replies, the mobile app to reply on the go. ## The flow 1. Email lands at `support@domain.com` 2. Cloudflare Email Routing forwards it for free 3. A Worker parses it and creates the issue through the GitHub API 4. I reply in the comments 5. A GitHub webhook (`issue_comment`) fires another Worker 6. That Worker sends my reply back as an email Two small Workers, one free routing rule, the GitHub API. Nothing new to run. ## The real value isn't the bridge Email-to-issue is a weekend of glue code, that part's obvious. The layer actually worth building sits on top of it: every day, an AI sweeps the open issues, checks each one against everything I've already closed, and flags the matches: "this looks like #142, already solved." For each match it drafts a reply based on how I handled it last time. I open my queue and the context is already there, the draft is already there, I just edit and hit send. No auto-reply, no black box answering customers on its own. I'm still the one deciding what goes out, the AI just kills the part where I retype the same answer for the sixth time. ## Does this exist already? I checked. Plenty of helpdesk tools integrate with GitHub (Zendesk, HappyFox, HelpDesk.com), but they all keep the ticket in the helpdesk and just mirror it to GitHub for the dev team's visibility. One small tool, HubDesk, does the real version of this, email becomes the issue, no helpdesk behind it, but there's no visible traction behind it: no reviews, no case studies, a GitHub org with one follower. **If you run a product solo and you're tired of a different support tool for every domain: does this solve something real for you, or is it a problem only I have?** --- # GitHub Issues vai ser meu canal de suporte URL: https://eduardovillao.me/pt-br/github-issues-vai-ser-meu-canal-de-suporte/ Language: pt-br Published: 2026-07-13 Eu toco alguns produtos ao mesmo tempo. Cada um tem seu domínio, cada domínio tem sua caixa de suporte. Cansei de gerenciar uma ferramenta diferente pra cada um deles. A solução: suporte vira issue do GitHub. O cliente manda um e-mail pra `suporte@meudominio.com`. Aquilo vira uma issue em um dos meus repos, privado, só meu. Eu respondo comentando, do mesmo jeito que respondo qualquer outra coisa no GitHub. O cliente recebe a resposta na caixa de entrada dele e nunca precisa saber que tem GitHub envolvido. Nenhuma conta nova pro cliente. Nenhuma ferramenta nova pra mim. O histórico fica versionado, pesquisável, e eu herdo de graça tudo que já funciona em cima de Issues: labels, projects, respostas salvas, o app no celular pra responder na rua. ## O fluxo 1. O e-mail chega em `suporte@dominio.com` 2. O Cloudflare Email Routing encaminha de graça 3. Um Worker faz o parse e cria a issue pela API do GitHub 4. Eu respondo nos comentários 5. Um webhook do GitHub (`issue_comment`) dispara outro Worker 6. Esse Worker manda minha resposta de volta como e-mail Dois Workers pequenos, uma regra de roteamento grátis, a API do GitHub. Nada novo pra manter rodando. ## O valor real não está na ponte E-mail virando issue é um fim de semana de código de cola, essa parte é óbvia. A camada que realmente vale a pena construir fica em cima disso: todo dia, uma IA varre as issues abertas, compara cada uma com tudo que eu já fechei e sinaliza as parecidas: "essa aqui parece a #142, já resolvida." Pra cada match, ela rascunha uma resposta baseada em como eu tratei da última vez. Eu abro minha fila e o contexto já está lá, o rascunho já está lá, eu só edito e envio. Sem resposta automática, sem caixa-preta respondendo cliente sozinha. Continuo sendo eu quem decide o que sai, a IA só mata a parte de redigitar a mesma resposta pela sexta vez. ## Isso já existe? Eu procurei. Tem bastante ferramenta de helpdesk que integra com GitHub (Zendesk, HappyFox, HelpDesk.com), mas todas mantêm o ticket no helpdesk e só espelham pro GitHub pra dar visibilidade ao time de dev. Uma ferramenta pequena, a HubDesk, faz a versão de verdade disso, o e-mail vira a issue, sem helpdesk por trás, mas não tem tração visível nenhuma: sem reviews, sem cases, uma org no GitHub com um seguidor. **Se você toca um produto sozinho e está cansado de uma ferramenta de suporte diferente pra cada domínio: isso resolve algo real pra você, ou é um problema só meu?** --- # AI doesn't fix direction. It accelerates the one you already have URL: https://eduardovillao.me/ai-doesnt-fix-direction-it-accelerates-the-one-you-already-have/ Language: en Published: 2026-06-29 If you're heading in the wrong direction, AI will get you there faster. Years of research on how to get the best out of people. Decades of literature on management, leadership, processes, teams, feedback, and organizational culture. And now companies are dropping AI into the middle of all that thinking it fixes things. It doesn't. ## AI doesn't correct your route. It accelerates the one you already have. If your team delivers well, with clear processes and good communication, AI will amplify that. Results get better, faster, with less friction. If your team delivers poorly, with messy processes and weak management, AI will amplify that too. Results get worse, faster, with more invisible friction. The tool has no opinion about direction. It just accelerates. ## What people think AI will fix Some examples of what happens when you put AI on top of broken structure: **"Let's use AI to document our processes."** Great. But if the processes don't exist or are a mess, AI will document the mess with polished language and an air of authority. Now the mess has a well-formatted PDF. **"Let's use AI to give feedback to the team."** But if the manager doesn't know what they want from the team, AI will generate vague feedback with the right words. It looks like feedback. It isn't. **"Let's use AI to speed up onboarding."** But if onboarding has no structure, now you have bad onboarding faster. The new hire gets lost in half the time. The pattern is always the same: AI executes well what you ask for. The problem is you don't know exactly what to ask for. ## What was always the problem is still the problem AI didn't create new problems in companies. It exposed the ones that were already there. Teams that already had standards, clarity, and good management keep delivering well, now with more speed. Teams that didn't keep delivering poorly, now with more speed too, and with the illusion that they're evolving because they're using new tools. AI adoption has become a metric for modernity. But modernity isn't installing tools. It's knowing how to use them with intention, inside a structure that actually works. ## What actually needs to happen first Before adding AI to any process, it's worth asking: **Does this process work without AI?** If it doesn't, AI won't fix it. It'll just execute what doesn't work with more efficiency. **Does whoever is managing this process know what they want as an outcome?** AI only delivers well when the person asking knows exactly what they're asking for. Vague management leads to vague prompts leads to vague output. **Does the team have clarity on the problem they're solving?** AI amplifies execution. If execution is heading in the wrong direction, it'll amplify the mistake. ## The one sentence that sums it all up If you're heading in the wrong direction, AI will get you there faster. AI is an accelerator. Not a course corrector. Not a substitute for management. Not a solution for missing process. It's a powerful tool that will get you faster to wherever you were already going. Before adding speed, make sure the direction is right. --- # IA não corrige direção. Ela acelera a que você já tem URL: https://eduardovillao.me/pt-br/ia-nao-corrige-direcao-ela-acelera-a-que-voce-ja-tem/ Language: pt-br Published: 2026-06-29 Se você tá indo na direção errada, a IA vai te fazer chegar errado mais rápido. Anos de estudo sobre como extrair o melhor das pessoas. Décadas de literatura sobre gestão, liderança, processos, times, feedback, cultura organizacional. E agora as empresas estão adicionando IA no meio disso tudo e achando que o problema tá resolvido. Não tá. ## A IA não corrige rota. Ela acelera a rota que você já tem. Se o seu time entrega bem, com clareza de processo e comunicação boa, a IA vai amplificar isso. O resultado vai ser melhor, mais rápido, com menos atrito. Se o seu time entrega mal, com processo confuso e gestão fraca, a IA vai amplificar isso também. O resultado vai ser pior, mais rápido, com mais atrito invisível. A ferramenta não tem opinião sobre a direção. Ela só acelera. ## O que as pessoas acham que a IA vai resolver Alguns exemplos do que acontece quando alguém coloca IA em cima de estrutura ruim: **"Vamos usar IA pra documentar nossos processos."** Ótimo. Mas se os processos não existem ou são uma bagunça, a IA vai documentar a bagunça com linguagem bonita e ar de autoridade. Agora a bagunça tem um PDF bem formatado. **"Vamos usar IA pra dar feedback pra equipe."** Mas se o gestor não sabe o que quer do time, a IA vai gerar feedback vago com palavras certas. Parece feedback. Não é. **"Vamos usar IA pra acelerar o onboarding."** Mas se o onboarding não tem estrutura, agora você tem onboarding ruim mais rápido. O novo funcionário fica perdido em metade do tempo. O padrão é sempre o mesmo: a IA executa bem o que você pede. O problema é que você não sabe exatamente o que precisa pedir. ## O que sempre foi o problema continua sendo o problema A IA não criou problemas novos nas empresas. Ela escancarou os que já existiam. Times que já tinham padrão, clareza e boa gestão continuam entregando bem, agora com mais velocidade. Times que não tinham continuam entregando mal, agora com mais velocidade também, e com a ilusão de que estão evoluindo porque usam ferramentas novas. A adoção de IA virou métrica de modernidade. Mas modernidade não é instalar ferramenta. É saber usá-la com intenção, dentro de uma estrutura que funciona. ## O que realmente precisa acontecer antes Antes de colocar IA em qualquer processo, vale a pena perguntar: **Esse processo funciona sem IA?** Se não funciona, a IA não vai consertar. Vai só executar o que não funciona com mais eficiência. **Quem está gerindo esse processo sabe o que quer como resultado?** A IA só entrega bem quando quem pede sabe exatamente o que está pedindo. Gestão vaga gera prompt vago gera output vago. **O time tem clareza sobre o problema que está resolvendo?** IA amplifica execução. Se a execução está indo na direção errada, ela vai amplificar o erro. ## A frase que resume tudo Se você tá indo na direção errada, a IA vai te fazer chegar errado mais rápido. A IA é um acelerador. Não é um corretor de rota. Não é um substituto pra gestão. Não é uma solução pra falta de processo. É uma ferramenta poderosa que vai fazer você chegar mais rápido aonde você já estava indo. Antes de adicionar velocidade, garanta que a direção está certa. --- # Stop Paying AI to Forget What You Already Know URL: https://eduardovillao.me/stop-paying-ai-to-forget-what-you-already-know/ Language: en Published: 2026-06-26 I'm currently building many apps/things in parallel: a form backend, a WhatsApp review alert tool, my WordPress plugins and a few others. Every single one needs Stripe. Every single one needs transactional emails. Most of them use Cloudflare Workers, D1, and R2. For a while, I let AI write those integrations from scratch every time. That was expensive. And dumb. ## What Was Actually Happening When you ask Claude Code (or any coding AI) to "integrate Stripe checkout," it doesn't teleport to a finished implementation. It explores. It writes, evaluates, adjusts. It handles errors, then reconsiders the error handling. It figures out where to put the webhook handler, what metadata to attach, how to structure the response. All of that costs tokens. And time. Now multiply that by every project. Every time I started something new, the AI was rebuilding the same mental model for integrations I had already solved. The code usually came out fine. But I was paying in tokens and in review time for decisions that were already made. ## The Fix: `ai-boilerplates` I created a private repo called `ai-boilerplates`. The idea is simple: every integration pattern I've solved and validated goes in there, ready to be used as a reference. ```plaintext ai-boilerplates/ stripe/ checkout.ts webhook.ts customer.ts resend/ transactional.ts cloudflare/ d1-client.ts r2-upload.ts auth/ jwt.ts session.ts ``` But the important part isn't just the code. It's the context block at the top of each file: ```typescript /** * BOILERPLATE: Stripe Checkout Session * * Use: single product or dynamic price_id checkout * Don't use: subscriptions (see stripe/subscription.ts) * * Decisions already made: * - success_url always redirects to /dashboard with session_id * - metadata always includes userId to reconcile in webhook * - errors return 500 with generic message, never expose Stripe details */ ``` That block is the real value. The AI doesn't just copy code. It understands intent, constraints, and prior decisions. It skips the exploration phase entirely. ## Wiring It Into Claude Code I added a dedicated rule file at `~/.claude/rules/ai-boilerplates.md`: ```markdown ## ai-boilerplates Repo at ~/ai-boilerplates/ contains standard boilerplate implementations. Before implementing any new integration, check if a boilerplate exists. If it does, use it as the base, don't rewrite from scratch. When using a boilerplate: - Adapt to the project context, don't copy blindly - Preserve the documented decisions - If you need to deviate from a decision, comment why in the code When writing code that could become a boilerplate, suggest adding it to the repo. ``` Keeping it in `rules/` rather than `CLAUDE.md` is intentional. `CLAUDE.md` is for general project context: stack, conventions, who you are. Rules are modular and focused, each with a single responsibility. Now every Claude Code session across every project starts with that context loaded. The AI knows to check before it builds. ## What Changed The difference is noticeable in a few ways: **Fewer tokens on solved problems.** The AI isn't re-exploring Stripe webhook verification or Resend error handling. It reads the boilerplate, adapts it, moves on. **More consistent code across projects.** The same decisions (metadata structure, error response shape, logging pattern) show up the same way everywhere. That matters when you're context-switching between four codebases. **Less review time.** I'm not second-guessing whether the AI made the right call on something I've already thought through. The call was made once, documented, and that's it. ## The Cost Angle Is Real AI inference isn't free, and it's only going to get more complex as agents and subagents become the norm. Running parallel agents on a large feature already costs real money. Anything that reduces redundant work compounds across every session, every project, every team member who touches the same stack. A boilerplate repo is one of the cheapest optimizations you can make. It costs you an hour to set up and pays back on every single integration after that. ## What Goes In a Good Boilerplate Not every piece of code deserves to be a boilerplate. A good candidate is something you: 1. Integrate in more than one project 2. Have already debugged at least once 3. Have made explicit architectural decisions about If all three are true, document it. If you're just writing throwaway code, don't bother. ## Context Is the Real Asset Most developers think about AI productivity in terms of prompts: how to ask better questions, how to get better output. That's valid, but it misses the bigger lever. The real asset is structured context. Boilerplates are one layer. ADRs are another. Rules that encode how your team works, shared memory of why decisions were made. When that context exists and is loaded into every session, the AI isn't starting from scratch. It's building on top of what you've already figured out. That compounds. Every solved problem makes the next one cheaper. Every documented decision saves tokens, review time, and the cognitive overhead of explaining your conventions again. A `ai-boilerplates` repo is a small, concrete place to start. But the habit it builds is what matters. --- # Pare de pagar a IA para esquecer o que você já sabe URL: https://eduardovillao.me/pt-br/pare-de-pagar-a-ia-para-esquecer-o-que-voce-ja-sabe/ Language: pt-br Published: 2026-06-26 No momento eu estou construindo várias coisas em paralelo: um backend de formulários, uma ferramenta de alerta de reviews no WhatsApp, meus plugins de WordPress e mais algumas. Todas precisam de Stripe. Todas precisam de e-mail transacional. A maioria usa Cloudflare Workers, D1 e R2. Por um tempo, eu deixei a IA escrever essas integrações do zero toda vez. Isso era caro. E burro. ## O que realmente estava acontecendo Quando você pede pro Claude Code (ou qualquer IA de código) "integrar o checkout do Stripe", ele não teleporta pra implementação pronta. Ele explora. Escreve, avalia, ajusta. Trata os erros, depois repensa o tratamento de erro. Descobre onde colocar o handler do webhook, qual metadata anexar, como estruturar a resposta. Tudo isso custa token. E tempo. Agora multiplique por cada projeto. Toda vez que eu começava algo novo, a IA estava reconstruindo o mesmo modelo mental pra integrações que eu já tinha resolvido. O código geralmente saía bom. Mas eu estava pagando em token e em tempo de review por decisões que já estavam tomadas. ## A solução: `ai-boilerplates` Criei um repo privado chamado `ai-boilerplates`. A ideia é simples: todo padrão de integração que eu resolvi e validei vai pra lá, pronto pra ser usado como referência. ```plaintext ai-boilerplates/ stripe/ checkout.ts webhook.ts customer.ts resend/ transactional.ts cloudflare/ d1-client.ts r2-upload.ts auth/ jwt.ts session.ts ``` Mas a parte importante não é só o código. É o bloco de contexto no topo de cada arquivo: ```typescript /** * BOILERPLATE: Stripe Checkout Session * * Use: single product or dynamic price_id checkout * Don't use: subscriptions (see stripe/subscription.ts) * * Decisions already made: * - success_url always redirects to /dashboard with session_id * - metadata always includes userId to reconcile in webhook * - errors return 500 with generic message, never expose Stripe details */ ``` Esse bloco é o valor real. A IA não só copia o código. Ela entende a intenção, as restrições e as decisões anteriores. Ela pula a fase de exploração inteira. ## Conectando isso no Claude Code Adicionei um arquivo de regra dedicado em `~/.claude/rules/ai-boilerplates.md`: ```markdown ## ai-boilerplates Repo at ~/ai-boilerplates/ contains standard boilerplate implementations. Before implementing any new integration, check if a boilerplate exists. If it does, use it as the base, don't rewrite from scratch. When using a boilerplate: - Adapt to the project context, don't copy blindly - Preserve the documented decisions - If you need to deviate from a decision, comment why in the code When writing code that could become a boilerplate, suggest adding it to the repo. ``` Manter isso em `rules/` em vez do `CLAUDE.md` é intencional. O `CLAUDE.md` é pro contexto geral do projeto: stack, convenções, quem você é. As regras são modulares e focadas, cada uma com uma responsabilidade só. Agora toda sessão do Claude Code, em qualquer projeto, começa com esse contexto carregado. A IA sabe que precisa checar antes de construir. ## O que mudou A diferença aparece de algumas formas: **Menos tokens em problemas já resolvidos.** A IA não fica reexplorando verificação de webhook do Stripe ou tratamento de erro do Resend. Ela lê o boilerplate, adapta e segue. **Código mais consistente entre projetos.** As mesmas decisões (estrutura de metadata, formato da resposta de erro, padrão de log) aparecem do mesmo jeito em todo lugar. Isso importa quando você troca de contexto entre quatro codebases. **Menos tempo de review.** Eu não fico questionando se a IA tomou a decisão certa em algo que eu já pensei a fundo. A decisão foi tomada uma vez, documentada, e acabou. ## A questão de custo é real Inferência de IA não é de graça, e só vai ficar mais complexa conforme agentes e subagentes viram norma. Rodar agentes em paralelo numa feature grande já custa dinheiro de verdade. Qualquer coisa que reduza trabalho redundante acumula em cada sessão, cada projeto, cada pessoa do time que encosta na mesma stack. Um repo de boilerplates é uma das otimizações mais baratas que existem. Custa uma hora pra montar e se paga em toda integração dali pra frente. ## O que entra em um bom boilerplate Nem todo pedaço de código merece virar boilerplate. Um bom candidato é algo que você: 1. Integra em mais de um projeto 2. Já debugou pelo menos uma vez 3. Já tomou decisões arquiteturais explícitas a respeito Se os três forem verdade, documente. Se for código descartável, não perca tempo. ## Contexto é o ativo de verdade A maioria dos devs pensa em produtividade com IA em termos de prompt: como fazer perguntas melhores, como conseguir output melhor. Isso é válido, mas passa longe da alavanca maior. O ativo de verdade é contexto estruturado. Boilerplates são uma camada. ADRs são outra. Regras que codificam como seu time trabalha, memória compartilhada do porquê das decisões. Quando esse contexto existe e é carregado em toda sessão, a IA não está começando do zero. Ela está construindo em cima do que você já descobriu. Isso acumula. Todo problema resolvido deixa o próximo mais barato. Toda decisão documentada economiza tokens, tempo de review e o custo mental de explicar suas convenções de novo. Um repo `ai-boilerplates` é um ponto de partida pequeno e concreto. Mas o hábito que ele cria é o que importa. --- # How I migrated a WordPress site to Cloudflare Pages using AI (and what broke) URL: https://eduardovillao.me/how-i-migrated-a-wordpress-site-to-cloudflare-pages-using-ai-and-what-broke/ Language: en Published: 2026-06-26 WordPress does everything. That's both the problem and the solution. I've spent years in the WordPress ecosystem: plugins, themes, WooCommerce, Gutenberg, the whole thing. It's a mature, powerful platform that handles complex cases really well. I'm not here to say it's bad. But with AI there's a category of site where it became overkill: landing pages, product sites, company pages, portfolios. Pages that are essentially static content with a contact form. You're running a full PHP stack, managing plugin updates, paying for server hosting, all for a page that could be pure HTML served from the edge. For those cases, I've started migrating everything. Destination: Cloudflare Pages. The process: largely AI-assisted. Here's how it went. ## Why Cloudflare Pages A few reasons it won over Vercel, Netlify, and GitHub Pages for this project: - Global edge network with zero cold starts - Free tier is genuinely generous — unlimited requests, unlimited bandwidth - Cloudflare Workers for any dynamic logic that survives the migration - D1 for lightweight database needs if they come up later - Everything in one ecosystem If you're already on Cloudflare for DNS (most people are), the migration path is shorter than it looks. ## The stack after migration - **Static site generator:** Astro — outputs pure HTML, partial hydration for any interactive component, excellent build performance - **Hosting:** Cloudflare Pages - **Forms:** FormRoute — more on this later - **AI used:** Claude for content transformation, component generation. ## Step 1 — Export and inventory the WordPress content First step: understand what you actually have. ```bash # Export everything from WordPress # Admin → Tools → Export → All content # Downloads an XML file ``` The XML export gives you posts, pages, categories, tags, authors, and media references. It doesn't give you the actual media files — those come separately. I fed the XML export to Claude and asked it to: 1. List every post and page with title, slug, date, and category 2. Identify which content was actively trafficked vs stale 3. Flag any posts with embedded shortcodes that would need manual attention The output was a clean inventory in about 30 seconds. On a large site this alone saves hours. **Prompt I used:** ```xml Here's a WordPress XML export. Give me: 1. A table of all posts: title, slug, publish date, category, word count 2. A list of all shortcodes used across the content 3. Any embedded iframes or third-party scripts you can identify Format as markdown. ``` ## Step 2 — Convert content to Markdown WordPress stores content as HTML with shortcodes mixed in. Astro wants Markdown with frontmatter. Claude handled most of this conversion automatically. **Prompt for content conversion:** ```html Convert this WordPress post HTML to Markdown with Astro frontmatter. Preserve all headings, links, images, and formatting. Output the frontmatter with: title, description, pubDate, slug, tags. Flag any shortcodes you can't convert with a [MANUAL REVIEW NEEDED] comment. ``` For 80% of posts, the output was clean and ready to use. The remaining 20% had: - Contact form shortcodes (`[contact-form-7]`) — needed replacement - Gallery shortcodes (`[gallery ids="..."]`) — needed manual rebuild - Plugin-specific shortcodes — varied by plugin The shortcode problem is where most WordPress migrations get stuck. More on the form replacement below. ## Step 3 — Set up Astro on Cloudflare Pages ```bash npm create astro@latest my-site cd my-site npx astro add cloudflare ``` The Cloudflare adapter handles the build output format for Pages. After that, connect your GitHub repo to Cloudflare Pages and it deploys on every push. Basic `astro.config.mjs`: ```js import { defineConfig } from 'astro/config' import cloudflare from '@astrojs/cloudflare' export default defineConfig({ output: 'static', adapter: cloudflare(), }) ``` For a pure static site, `output: 'static'` is what you want. No server-side rendering, no cold starts, pure HTML at the edge. ## Step 4 — Migrate content and assets I asked Claude to generate a migration script that: 1. Read the WordPress XML export 2. Created individual `.md` files for each post with proper frontmatter 3. Renamed media files to a clean slug-based convention 4. Updated internal image references in the content ```js // Claude-generated migration script (simplified) 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) ) } ``` For media, I kept the files in the Cloudflare Pages public folder and updated the references in the Markdown files. For larger sites with heavy media, R2 is worth looking at, but for most company pages and landing pages, serving assets directly from Pages is enough. Claude generated the find-and-replace script for URL rewriting. ## Step 5 — The forms problem This is where every WordPress-to-static migration hits the same wall. Contact Form 7, Gravity Forms, WPForms — they all depend on PHP running on the server. Move to static and they stop working entirely. There's no server to process the submission. The options I evaluated: **Write a Cloudflare Worker** — possible, but you're now maintaining a backend for what is essentially a contact form. Wiring up email delivery, validation, spam protection. More moving parts than I wanted. **Use a form backend service** — drop one endpoint into the form, the service handles everything. No server, no maintenance. I went with [FormRoute](https://formroute.dev). The migration from Contact Form 7 was straightforward: **Before (Contact Form 7 shortcode):** ```plaintext [contact-form-7 id="123" title="Contact form"] ``` **After (Astro component):** ```astro --- // src/components/ContactForm.astro ---
``` Spam protection runs via Cloudflare Turnstile, invisible to real users. Submissions go to the FormRoute dashboard and trigger an email notification. Free tier covers 1,000 submissions a month. ## Step 6 — Redirects WordPress URLs don't always match what you want for a static site. You need to redirect old URLs to new ones so you don't lose SEO or break existing links. Cloudflare Pages handles redirects via a `_redirects` file in the public folder: ```nginx /old-post-url/ /new-post-url/ 301 /category/news/ /blog/ 301 /?p=123 /post-slug/ 301 ``` I fed Claude the old URL list and the new slug list and asked it to generate the redirects file. It handled the mapping in one pass. For WordPress sites with `?p=123` style URLs, the pattern is: ```nginx /?p=:id /blog/:slug 301 ``` You'll need to map each ID to its slug manually or via a script — Claude can generate that script from the XML export. ## What broke (honest list) **Comments** — WordPress comments don't have a static equivalent. I replaced them with nothing. If comments matter for your site, look at Giscus (GitHub Discussions-based) or just remove them. **Search** — WordPress search is server-side. Static sites need client-side search. Pagefind works well with Astro and takes 10 minutes to set up. **WooCommerce** — if your WordPress site has ecommerce, this migration path doesn't apply. WooCommerce requires a server. Static + Shopify Buy Button or a headless approach is the alternative. **Some SEO plugins** — Yoast SEO data lives in WordPress meta. The XML export includes it but you need to map it manually to your Astro frontmatter. Claude can generate the mapping script but it takes some cleanup. **Plugin-specific shortcodes** — anything from a plugin that isn't content (countdown timers, booking widgets, interactive maps) needs a replacement. There's no universal answer here — depends on what the plugin did. ## The result - **Build time:** under 10 seconds - **Time to first byte:** under 50ms globally (Cloudflare edge) - **Lighthouse score:** 98–100 across the board - **Hosting cost:** $0 (Cloudflare Pages free tier) - **Maintenance:** zero PHP, zero plugin updates, zero server patches The AI-assisted migration took about a day of actual work for a mid-size site (80 posts, 20 pages). Without AI the same work would have taken a week — content conversion and script generation were the biggest time savings. ## Summary — what AI helped with | Task | Time without AI | Time with AI | |---|---|---| | Content inventory | 2–3 hours | 10 minutes | | HTML to Markdown conversion | 1 day | 10 minutes | | Migration scripts | 4–6 hours | 10 minutes | | Redirects file | 2 hours | 5 minutes | | Component generation | 3 hours | 20 minutes | The parts AI couldn't help with: judgment calls on what to keep, what to cut, and how to handle plugin-specific functionality. That's still on you. ## Resources - [Astro docs](https://docs.astro.build) - [Cloudflare Pages docs](https://developers.cloudflare.com/pages) - [FormRoute](https://formroute.dev) — form backend for static sites **Update:** I later wrote about what this looks like across my whole portfolio, cost-wise: [From $39/Month to $1: How I Moved 10+ Sites Off Hostinger for Free](/from-39-month-to-1-how-i-moved-10-sites-off-hostinger-for-free/) --- # Como migrei um site WordPress para o Cloudflare Pages usando IA (e o que quebrou) URL: https://eduardovillao.me/pt-br/como-migrei-um-site-wordpress-para-o-cloudflare-pages-usando-ia-e-o-que-quebrou/ Language: pt-br Published: 2026-06-26 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. ```bash # 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:** ```xml 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:** ```html 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 ```bash 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: ```js 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 ```js // 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](https://formroute.dev). A migração do Contact Form 7 foi direta: **Antes (shortcode do Contact Form 7):** ```plaintext [contact-form-7 id="123" title="Formulário de contato"] ``` **Depois (componente Astro):** ```astro --- // src/components/ContactForm.astro ---
``` 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: ```nginx /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 é: ```nginx /?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 | Tarefa | Tempo sem IA | Tempo com IA | |---|---|---| | Inventário de conteúdo | 2–3 horas | 10 minutos | | Conversão de HTML pra Markdown | 1 dia | 10 minutos | | Scripts de migração | 4–6 horas | 10 minutos | | Arquivo de redirects | 2 horas | 5 minutos | | Geração de componentes | 3 horas | 20 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 - [Documentação do Astro](https://docs.astro.build) - [Documentação do Cloudflare Pages](https://developers.cloudflare.com/pages) - [FormRoute](https://formroute.dev) — backend de formulários pra sites estáticos **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](/pt-br/de-39-mes-para-1-como-tirei-mais-de-10-sites-da-hostinger-de-graca/) --- # How to add a contact form to a Next.js app without a backend URL: https://eduardovillao.me/how-to-add-a-contact-form-to-a-nextjs-app-without-a-backend/ Language: en Published: 2026-06-26 If you've ever built a Next.js app and needed a contact form, you've probably hit the same wall: you don't want to set up a backend just to receive an email. You have a few options. You could write an API route, wire up an email service like Resend or SendGrid, handle validation, add spam protection, deploy, and maintain it forever. Or you could use a form backend service and skip all of that. This tutorial shows the second path. We'll build a working contact form in Next.js — App Router — that validates fields, blocks spam, and delivers submissions to your inbox. No backend, no server to maintain. We'll use FormRoute as the form backend. Free tier covers 1,000 submissions a month with a dashboard included. ## What we're building A contact form with: - Name, email, and message fields - Client-side and server-side validation - Spam protection with Cloudflare Turnstile - Success and error states - Zero backend code ## 1. Create your FormRoute endpoint Request an invite at [formroute.dev](https://formroute.dev/#early-access), since FormRoute is in private access for now. Once you're in, create a new form and copy your endpoint URL. It looks like this: ```http https://api.formroute.dev/f/YOUR_KEY ``` That's the only setup you need on the FormRoute side. ## 2. Add Turnstile to your page FormRoute uses Cloudflare Turnstile for spam protection. It's invisible to real users — no "click the traffic lights" — and it's handled automatically when you add two lines to your page. Add the Turnstile script to your `layout.tsx`: ```tsx // app/layout.tsx import Script from 'next/script' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ``` ## Wrap up Contact forms are one of those features that look simple but hide complexity. Validation, spam, deliverability, storage — each one is a small problem that adds up. A form backend handles all of it so you don't have to. The tradeoff is a dependency on a third-party service — but for most projects, that's a good tradeoff. FormRoute free tier covers 1,000 submissions a month, includes a dashboard, and has no time limit. [formroute.dev](https://formroute.dev) --- # Como adicionar um formulário de contato em um app Next.js sem backend URL: https://eduardovillao.me/pt-br/como-adicionar-um-formulario-de-contato-em-um-app-nextjs-sem-backend/ Language: pt-br Published: 2026-06-26 Se você já construiu um app Next.js e precisou de um formulário de contato, provavelmente bateu na mesma parede: você não quer montar um backend só pra receber um e-mail. Você tem algumas opções. Pode escrever uma API route, plugar um serviço de e-mail como Resend ou SendGrid, tratar validação, adicionar proteção contra spam, fazer deploy e manter isso pra sempre. Ou pode usar um serviço de backend de formulário e pular tudo isso. Este tutorial mostra o segundo caminho. Vamos montar um formulário de contato funcional no Next.js — App Router — que valida campos, bloqueia spam e entrega os envios na sua caixa de entrada. Sem backend, sem servidor pra manter. Vamos usar o FormRoute como backend do formulário. O plano gratuito cobre 1.000 envios por mês, com dashboard incluído. ## O que vamos construir Um formulário de contato com: - Campos de nome, e-mail e mensagem - Validação no client e no servidor - Proteção contra spam com Cloudflare Turnstile - Estados de sucesso e erro - Zero código de backend ## 1. Crie seu endpoint no FormRoute Peça um convite em [formroute.dev](https://formroute.dev/#early-access), já que o FormRoute está em acesso privado por enquanto. Com o acesso liberado, crie um formulário novo e copie a URL do endpoint. Ela é assim: ```http https://api.formroute.dev/f/YOUR_KEY ``` Essa é toda a configuração necessária do lado do FormRoute. ## 2. Adicione o Turnstile na sua página O FormRoute usa o Cloudflare Turnstile pra proteção contra spam. Ele é invisível pra usuário real — nada de "clique nos semáforos" — e é tratado automaticamente quando você adiciona duas linhas na sua página. Adicione o script do Turnstile no seu `layout.tsx`: ```tsx // app/layout.tsx import Script from 'next/script' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ``` ## Fechando Formulário de contato é uma daquelas features que parecem simples mas escondem complexidade. Validação, spam, entregabilidade, armazenamento — cada uma é um probleminha que vai somando. Um backend de formulário cuida de tudo isso pra você não ter que cuidar. O trade-off é a dependência de um serviço de terceiro — mas pra maioria dos projetos, é um bom trade-off. O plano gratuito do FormRoute cobre 1.000 envios por mês, inclui dashboard e não tem prazo de validade. [formroute.dev](https://formroute.dev) --- # IA sem padrão é só caos mais rápido URL: https://eduardovillao.me/pt-br/ia-sem-padrao-e-so-caos-mais-rapido/ Language: pt-br Published: 2026-04-27 Todo time de engenharia que eu converso está usando IA. Ninguém debate mais isso. O que ninguém está falando é que cada dev no mesmo time está usando de um jeito diferente. ## O problema que ninguém admite Dez devs. Dez setups diferentes. Dez arquivos de regras diferentes. Dez hábitos de prompting diferentes. Mesmo codebase, mesmos tickets, mesmo sprint – dez abordagens diferentes de como a IA toca no código. Um dev tem um CLAUDE.md meticuloso com decisões de arquitetura, convenções de teste e regras de estilo de código. O dev do lado não tem nada – só intuição e uma context window em branco. Os dois entregam código. Os dois passam no review. A inconsistência é invisível até que deixa de ser. A IA não criou esse problema. Ela amplificou o que já estava quebrado: a falta de padrões compartilhados de engenharia que são realmente aplicados. ## Três coisas que quebram **Zero visibilidade.** Você não sabe como seu time usa IA. Quem segue boas práticas. Quem não segue. O que é revisado. O que vai pra produção no escuro. Engineering managers têm dashboards pra frequência de deploy, cobertura de testes, cycle time de PR – mas zero visibilidade sobre como a IA está moldando o código que o time escreve todo dia. **Zero consistência.** Mesmo ticket, dez abordagens diferentes. Um dev pede pra IA escrever testes primeiro. Outro pula testes e pede a implementação. Um terceiro dá todo o contexto arquitetural pra IA. Um quarto não dá nada. O output varia absurdamente – não porque a IA é inconsistente, mas porque os humanos são. E não é só sobre código diferente. É sobre onde no codebase a mudança acontece, qual deveria ser o escopo, por que essa abordagem e não outra. Tem mil jeitos de resolver o mesmo problema. A IA vai executar qualquer um deles com a mesma confiança. Sem um padrão compartilhado de como o time toma essas decisões, cada PR gerada por IA vira um peso no review. Alguém precisa desfazer, refazer ou remodelar um trabalho que estava tecnicamente correto mas arquiteturalmente errado. Isso é retrabalho em escala, e se acumula rápido. **Conhecimento vai embora.** Quando um dev sai, o contexto dele, os padrões, os atalhos, saem junto. As regras que ele construiu, os prompts que refinou, as decisões arquiteturais que codificou no setup local – tudo vai embora. O próximo contratado começa do zero. Isso já era problema antes da IA. Agora é pior, porque a quantidade de conhecimento implícito no setup de IA de um dev é massiva e completamente não-documentada. ## Por que isso é mais difícil do que parece A reação óbvia é “só crie um arquivo de regras compartilhado”. É por aí que a maioria dos times começa. E também onde a maioria dos times para. Um arquivo de regras compartilhado num repo resolve o problema de formato, mas não o problema de enforcement. Ninguém checa se os devs realmente usam. Ninguém sabe se está atualizado. Ninguém sabe se o dev que entrou mês passado sequer sabe que o arquivo existe. E regras são só o começo. E quanto a quais modelos são aprovados? E quais fases do trabalho deveriam usar IA e quais não? E o custo? E as decisões arquiteturais que deveriam restringir como a IA gera código no seu codebase específico? Padronizar IA num time de engenharia não é um arquivo. É um sistema. E a maioria dos times não tem um. ## O que realmente precisa acontecer Eu penso nisso em três camadas: **Camada 1: Contexto compartilhado.** Todo dev trabalhando no mesmo codebase deveria começar com o mesmo contexto base. Decisões de arquitetura, convenções de código, estratégia de testes, regras de dependência – isso não é opcional, e não deveria depender de setup individual. Se seus ADRs existem mas só duas pessoas sabem onde estão, eles não existem. **Camada 2: Guardrails.** Nem tudo deveria ser delegado pra IA. Algumas decisões exigem julgamento humano. Alguns caminhos de código são críticos demais pra geração sem supervisão. O time precisa definir onde a IA adiciona valor e onde adiciona risco – e aplicar essa distinção, não só documentar. **Camada 3: Visibilidade.** Você precisa saber o que está acontecendo. Não vigilância – sinal. Quais partes do codebase são tocadas pela IA. Quais padrões estão emergindo. Onde estão as inconsistências. Sem isso, você está gerenciando um processo que não consegue ver. A maioria dos times não tem nenhuma dessas camadas. Alguns têm uma Camada 1 parcial. Quase ninguém tem Camada 2 ou 3. ## A vantagem competitiva real O que eu acho que a maioria das pessoas não percebe: a vantagem competitiva da IA em engenharia não é velocidade. Velocidade é table stakes. Todo mundo fica mais rápido. A vantagem está em quão consistentemente e confiavelmente você usa ela em todo o time. O time que entrega rápido com padrões compartilhados vai superar o time que entrega rápido com dez abordagens diferentes – porque o segundo time está acumulando inconsistência invisível que se compõe ao longo do tempo. Caos mais rápido continua sendo caos. Só demora mais pra perceber. ## Isso não é sobre ferramentas Não estou vendendo solução aqui. Estou descrevendo um problema que vejo em todo lugar e que acho subexplorado. A indústria passou os últimos dois anos falando sobre adotar IA. A próxima conversa precisa ser sobre governar ela. Não de um jeito burocrático e pesado de compliance – de um jeito prático, engineering-first. Contexto compartilhado, guardrails claros, visibilidade básica. Todo time que adotou IA sem padronizar está rodando um experimento sem controles. Alguns desses experimentos vão dar certo. Alguns não. Os que não derem vão ser muito caros de consertar – porque a dívida técnica de uso inconsistente de IA é invisível até ser sistêmica. A pergunta não é se seu time deveria usar IA. É se todos deveriam usar da mesma forma. Acho que a resposta é óbvia. --- # AI Without Standards Is Just Faster Chaos URL: https://eduardovillao.me/ai-without-standards-is-just-faster-chaos/ Language: en Categories: IA Published: 2026-04-27 Every engineering team I talk to is using AI. Nobody’s debating that anymore. What nobody’s talking about is that every developer on the same team is using it differently. ## The Problem Nobody Admits Ten developers. Ten different setups. Ten different rules files. Ten different prompting habits. Same codebase, same tickets, same sprint – ten different approaches to how AI touches the code. One dev has a meticulous CLAUDE.md with architecture decisions, testing conventions, and code style rules. The dev next to them has nothing – just vibes and a blank context window. Both ship code. Both pass review. The inconsistency is invisible until it isn’t. AI didn’t create this problem. It amplified what was already broken: the lack of shared engineering standards that actually get enforced. ## Three Things That Break **No visibility.** You don’t know how your team uses AI. Who follows best practices. Who doesn’t. What gets reviewed. What ships blind. Engineering managers have dashboards for deployment frequency, test coverage, PR cycle time – but zero visibility into how AI is shaping the code their team writes every day. **No consistency.** Same ticket, ten different approaches. One dev asks the AI to write tests first. Another skips tests entirely and asks for the implementation. A third gives the AI the full architectural context. A fourth gives it nothing. The output varies wildly – not because the AI is inconsistent, but because the humans are. And this isn’t just about different code styles. It’s about where in the codebase the change happens, what the scope should be, why this approach and not another. There are a thousand ways to solve the same problem. The AI will confidently execute any of them. Without a shared standard for how your team makes these decisions, every AI-generated PR becomes a review burden. Someone has to undo, redo, or reshape work that was technically correct but architecturally wrong. That’s rework at scale, and it compounds fast. **Knowledge walks out.** When a developer leaves, their context, patterns, and shortcuts leave with them. The rules they built, the prompts they refined, the architectural decisions they encoded in their local setup – gone. The next hire starts from zero. This was already a problem before AI. Now it’s worse, because the amount of implicit knowledge in a developer’s AI setup is massive and completely undocumented. ## Why This Is Harder Than It Looks The obvious reaction is “just create a shared rules file.” That’s where most teams start. It’s also where most teams stop. A shared rules file in a repo solves the format problem but not the enforcement problem. Nobody checks if developers actually use it. Nobody knows if it’s up to date. Nobody knows if the dev who joined last month even knows it exists. And rules are just the beginning. What about which models are approved? What about which phases of work should use AI and which shouldn’t? What about cost? What about the architectural decisions that should constrain how AI generates code in your specific codebase? Standardizing AI in an engineering team isn’t a file. It’s a system. And most teams don’t have one. ## What Actually Needs to Happen I think about this in three layers: **Layer 1: Shared context.** Every developer working on the same codebase should start with the same foundational context. Architecture decisions, code conventions, testing strategy, dependency rules – this isn’t optional, and it shouldn’t depend on individual setup. If your ADRs exist but only two people know where they are, they don’t exist. **Layer 2: Guardrails.** Not everything should be delegated to AI. Some decisions require human judgment. Some code paths are too critical for unsupervised generation. The team needs to define where AI adds value and where it adds risk – and enforce that distinction, not just document it. **Layer 3: Visibility.** You need to know what’s happening. Not surveillance – signal. Which parts of the codebase are AI-touched. What patterns are emerging. Where the inconsistencies are. Without this, you’re managing a process you can’t see. Most teams have none of these layers. Some have a partial Layer 1. Almost nobody has Layer 2 or 3. ## The Real Competitive Advantage Here’s what I think most people miss: the competitive advantage of AI in engineering isn’t speed. Speed is table stakes. Everyone gets faster. The advantage is in how consistently and reliably you use it across the entire team. The team that ships fast with shared standards will outperform the team that ships fast with ten different approaches – because the second team is accumulating invisible inconsistency that compounds over time. Faster chaos is still chaos. It just takes longer to notice. ## This Isn’t About Tools I’m not pitching a solution here. I’m describing a problem I see everywhere and that I think is underexplored. The industry spent the last two years talking about adopting AI. The next conversation needs to be about governing it. Not in a bureaucratic, compliance-heavy way – in a practical, engineering-first way. Shared context, clear guardrails, basic visibility. Every team that adopted AI without standardizing it is running an experiment with no controls. Some of those experiments will work out fine. Some won’t. The ones that don’t will be very expensive to fix – because the technical debt from inconsistent AI usage is invisible until it’s systemic. The question isn’t whether your team should use AI. It’s whether they should all use it the same way. I think the answer is obvious. --- # Seus logs de 404 são um relatório de segurança que você está ignorando URL: https://eduardovillao.me/pt-br/seus-logs-de-404-sao-um-relatorio-de-seguranca-que-voce-esta-ignorando/ Language: pt-br Published: 2026-04-22 A maioria dos devs WordPress instala um plugin de redirect, configura uns 301, e nunca mais olha pros logs de 404. Isso é um erro. Seus logs de 404 são um mapa em tempo real de quem está sondando seu site e o que estão procurando. Bots escaneando arquivos de ambiente expostos, vazamento de credenciais, arquivos de configuração, tentativas de path traversal, tudo aparece como 404 antes de aparecer como uma brecha. Puxei os logs de 404 de dois dos meus próprios sites na última semana. Aqui vai o que encontrei, e o que bloqueei no nível da CDN pra que esses requests nunca mais batam no meu servidor. ## Os números Dois sites WordPress. Uma semana de dados. - **Site A:** 1.388 requests 404 no total, 1.118 URLs únicas - **Site B:** 2.693 requests 404 no total, 1.877 URLs únicas Mais de 4.000 requests que não são usuários reais, não são mecanismos de busca e não são links quebrados. São scans automatizados procurando vulnerabilidades. ## Os padrões de ataque Todo log de 404 conta uma história. Aqui vão as cinco categorias que encontrei nos meus. ### 1\. Varredura de Arquivos de Ambiente (.env) De longe o padrão mais comum. Bots varrem sistematicamente todo caminho possível onde um arquivo `.env` possa existir: ``` /.env → 49 hits /staging/.env → 20 hits /backend/.env → 20 hits /react/.env → 15 hits /.env.local → 15 hits /shared/.env → 13 hits /api/.env → 12 hits /.env.production → 12 hits /app/.env → 10 hits /server/.env → 10 hits ``` E não para aí. Encontrei requests pra `.env.backup`, `.env.bak`, `.env.save`, `.env.old`, `.env_copy`, `.env_secret`, `.env~`, `.env.swp` — mais de 80 variações diferentes de `.env` no total. **O que estão procurando:** Credenciais de banco de dados, chaves de API, senhas SMTP, chaves do Stripe, secrets da AWS. Um arquivo `.env` exposto = acesso total à sua infraestrutura. ### 2\. Coleta de credenciais cloud ``` /.aws/credentials → 17 hits /.aws/config → 5 hits /.AwS/CrEdEnTiAlS → 5 hits (variação de case pra burlar regras) /.terraform/terraform.tfstate → 6 hits /serviceAccountKey.json → 4 hits /credentials.json → 5 hits ``` Repara na variação de case `.AwS/CrEdEnTiAlS` — isso é projetado especificamente pra burlar regras de pattern matching ingênuas que só checam lowercase. Estão caçando chaves AWS, service accounts do GCP e arquivos de state do Terraform que possam conter secrets de infraestrutura. ### 3\. Sondagem de arquivos de config e secrets ``` /config/initializers/secret_token.rb → 7 hits (Rails) /config/storage.yml → 6 hits (Rails) /application.yml → 5 hits (Spring Boot) /docker-compose.yml → 4 hits /sftp-config.json → 5 hits (Sublime SFTP) /.vscode/sftp.json → 3 hits (VS Code SFTP) /config.php.bak → 5 hits /secrets.json → 6 hits /sendgrid.env → 7 hits ``` Os bots não se importam com qual framework você usa. Escaneiam pra Rails, Laravel, Spring Boot, Node.js, Django — tudo na mesma varredura. Se você acidentalmente fez deploy de um arquivo de config, eles vão encontrar. ### 4\. Sondagem específica de WordPress ``` /wp-config → 3 hits /wp-config~ → 1 hit /wp-config.production → 1 hit /wp-configbak → 1 hit ``` Estão procurando cópias de backup do `wp-config.php` — o arquivo que contém suas credenciais de banco de dados. Arquivos temporários de editor (`wp-config~`), backups manuais (`wp-configbak`), cópias por ambiente. Um descuido de `cp wp-config.php wp-config.bak` e você expôs tudo. ### 5\. Path traversal e tentativas de execução de código ``` /etc/passwd?raw?? → 3 hits /@fs/etc/passwd?import&raw?? → 3 hits /admin/config?cmd=cat /root/.aws/credentials → 1 hit /vendor/phpunit/phpunit/phpunit.xsd → 2 hits /pms?module=logging&file_name=../../~/.aws/credentials → 1 hit ``` Essas são tentativas ativas de exploração, não só varredura. Estão tentando ler arquivos do sistema, executar comandos e explorar vulnerabilidades conhecidas no PHPUnit e outros pacotes. ## O que fazer: bloquear na CDN O insight principal: **cada um desses requests bateu no seu servidor**. Sua instalação WordPress processou, seu PHP executou, seu banco de dados foi consultado pra gerar uma página 404 — tudo pra um bot que está tentando hackear você. A solução é simples: bloqueie esses padrões no nível da CDN (Cloudflare, Fastly, CloudFront) pra que eles nunca cheguem no seu servidor. Zero carga na sua infra, zero execução de PHP, zero risco. ### Regras WAF no Cloudflare Aqui vão as regras que configurei baseado nos meus dados reais de 404: *Importante: use `lower()` aqui — atacantes usam variações de case como `.AwS/CrEdEnTiAlS` pra burlar regras ingênuas.* **Regra 1: Bloquear acesso a arquivos .env** ``` (lower(http.request.uri.path) contains ".env") Action: Block ``` **Regra 2: Bloquear acesso a arquivos de credenciais/config** ``` (lower(http.request.uri.path) contains ".aws/credentials" or lower(http.request.uri.path) contains "terraform.tfstate" or lower(http.request.uri.path) contains "serviceaccountkey" or lower(http.request.uri.path) contains "docker-compose.yml" or lower(http.request.uri.path) contains "sftp-config" or lower(http.request.uri.path) contains "sftp.json" or lower(http.request.uri.path) contains "secrets.json" or lower(http.request.uri.path) contains "credentials.json" or lower(http.request.uri.path) contains "sendgrid" or lower(http.request.uri.path) contains "secret_token.rb" or lower(http.request.uri.path) contains "storage.yml" or lower(http.request.uri.path) contains "application.yml") Action: Block ``` **Regra 3: Bloquear sondagem de wp-config** ``` (lower(http.request.uri.path) contains "wp-config" and not http.request.uri.path eq "/wp-admin/") Action: Block ``` **Regra 4: Bloquear tentativas de path traversal** ``` (lower(http.request.uri.path) contains "etc/passwd" or lower(http.request.uri.path) contains "phpunit" or lower(http.request.uri.path) contains "update.cgi" or http.request.uri contains "cmd=") Action: Block ``` **Regra 5: Bloquear sondagem de diretórios comuns de backup/instalação** ``` (lower(http.request.uri.path) eq "/old/" or lower(http.request.uri.path) eq "/new/" or lower(http.request.uri.path) eq "/backup/" or lower(http.request.uri.path) eq "/wordpress/" or lower(http.request.uri.path) eq "/wp/" or lower(http.request.uri.path) eq "/test/") Action: Block ``` Comparação de strings no Cloudflare é case-sensitive por padrão. A função `lower()` converte o URI path pra lowercase antes de comparar, então `/.AwS/CrEdEnTiAlS` é capturado por uma regra que checa `.aws/credentials`. Sem `lower()`, esse request passa direto. ### Por que bloquear na CDN importa Quando você bloqueia na CDN: - O request nunca chega no seu servidor - Sem execução PHP, sem query no banco, sem uso de CPU - Os recursos do seu servidor vão pra usuários reais, não bots - Você reduz sua superfície de ataque a zero pra padrões conhecidos - Os logs ficam limpos, facilitando identificar padrões novos Bloquear no nível da aplicação (plugins WordPress, `.htaccess`) ainda funciona, mas o request já consumiu recursos do servidor quando é bloqueado. Bloqueio na CDN é a diferença entre um segurança na porta e um segurança dentro do bar. ## Faça disso um hábito Puxe seus logs de 404 uma vez por mês. Procure padrões. Adicione novas regras no Cloudflare. Os bots evoluem. Novos padrões de varredura aparecem. O truque de variação de case `.AwS/CrEdEnTiAlS` é um exemplo perfeito — alguém projetou isso especificamente pra burlar regras que só checam lowercase. Seus logs de 404 não são só links quebrados. São uma auditoria de segurança gratuita. Leia eles. * * * *Os dados nesse post vêm de logs reais de 404 coletados ao longo de uma semana de dois sites WordPress em produção. Nenhum IP ou informação identificável dos bots atacantes foi incluído.* --- # Your 404 Logs Are a Security Report You’re Ignoring URL: https://eduardovillao.me/your-404-logs-are-a-security-report-youre-ignoring/ Language: en Published: 2026-04-22 Most WordPress developers install a redirect plugin, set up a few 301s, and never look at their 404 logs again. If they do, it’s for SEO, fixing broken links, cleaning up crawl errors. That’s the obvious use. But there’s something else in those logs that almost everyone ignores. Your 404 logs are a real-time map of who’s probing your site and what they’re looking for. Bots scanning for exposed environment files, credential leaks, config files, path traversal exploits, all of it shows up as 404s before it shows up as a breach. I pulled the 404 logs from two of my own sites over the past week. Here’s what I found, and what I blocked at the CDN level so these requests never hit my server again. ## The Numbers Two WordPress sites. One week of data. - **Site A:** 1,388 total 404 requests across 1,118 unique URLs - **Site B:** 2,693 total 404 requests across 1,877 unique URLs Over 4,000 requests that aren’t real users, aren’t search engines, and aren’t broken links. They’re automated scans looking for vulnerabilities. ## The Attack Patterns Every 404 log tells a story. Here are the five categories I found in mine. ### 1\. Environment File Scanning (.env) This is by far the most common pattern. Bots systematically scan every possible path where a `.env` file might exist: ``` /.env → 49 hits /staging/.env → 20 hits /backend/.env → 20 hits /react/.env → 15 hits /.env.local → 15 hits /shared/.env → 13 hits /api/.env → 12 hits /.env.production → 12 hits /app/.env → 10 hits /server/.env → 10 hits ``` And it doesn’t stop there. I found requests for `.env.backup`, `.env.bak`, `.env.save`, `.env.old`, `.env_copy`, `.env_secret`, `.env~`, `.env.swp` — over 80 different `.env` variations in total. **What they’re looking for:** Database credentials, API keys, SMTP passwords, Stripe keys, AWS secrets. One exposed `.env` file = full access to your infrastructure. ### 2\. Cloud Credential Harvesting ``` /.aws/credentials → 17 hits /.aws/config → 5 hits /.AwS/CrEdEnTiAlS → 5 hits (case variation to bypass rules) /.terraform/terraform.tfstate → 6 hits /serviceAccountKey.json → 4 hits /credentials.json → 5 hits ``` Notice the case variation on `.AwS/CrEdEnTiAlS` — that’s specifically designed to bypass naive pattern matching rules that only check lowercase. They’re hunting for AWS keys, GCP service accounts, and Terraform state files that might contain infrastructure secrets. ### 3\. Config & Secret File Probing ``` /config/initializers/secret_token.rb → 7 hits (Rails) /config/storage.yml → 6 hits (Rails) /application.yml → 5 hits (Spring Boot) /docker-compose.yml → 4 hits /sftp-config.json → 5 hits (Sublime SFTP) /.vscode/sftp.json → 3 hits (VS Code SFTP) /config.php.bak → 5 hits /secrets.json → 6 hits /sendgrid.env → 7 hits ``` Bots don’t care what framework you use. They scan for Rails, Laravel, Spring Boot, Node.js, Django — all in the same sweep. If you accidentally deployed a config file, they’ll find it. ### 4\. WordPress-Specific Probing ``` /wp-config → 3 hits /wp-config~ → 1 hit /wp-config.production → 1 hit /wp-configbak → 1 hit ``` They’re looking for backup copies of `wp-config.php` — the file that contains your database credentials. Editor temp files (`wp-config~`), manual backups (`wp-configbak`), environment-specific copies. One careless `cp wp-config.php wp-config.bak` and you’ve exposed everything. ### 5\. Path Traversal & Code Execution Attempts ``` /etc/passwd?raw?? → 3 hits /@fs/etc/passwd?import&raw?? → 3 hits /admin/config?cmd=cat /root/.aws/credentials → 1 hit /vendor/phpunit/phpunit/phpunit.xsd → 2 hits /pms?module=logging&file_name=../../~/.aws/credentials → 1 hit ``` These are active exploitation attempts, not just scanning. They’re trying to read system files, execute commands, and exploit known vulnerabilities in PHPUnit and other packages. ## What to Do About It: Block at the CDN Here’s the key insight: **every one of these requests hit your server**. Your WordPress installation processed them, your PHP executed, your database was queried for a 404 page, all for a bot that’s trying to hack you. The fix is simple: block these patterns at the CDN level (Cloudflare, Fastly, CloudFront…) so they never reach your server. Zero load on your infrastructure, zero PHP execution, zero risk. ### Cloudflare WAF Rules Here are the rules I set up based on my actual 404 data: *Important: use `lower()` in all of then, attackers use case variations like `.AwS/CrEdEnTiAlS` to bypass naive rules.* **Rule 1: Block .env file access** ``` (lower(http.request.uri.path) contains ".env") Action: Block ``` **Rule 2: Block credential/config file access** ``` (lower(http.request.uri.path) contains ".aws/credentials" or lower(http.request.uri.path) contains "terraform.tfstate" or lower(http.request.uri.path) contains "serviceaccountkey" or lower(http.request.uri.path) contains "docker-compose.yml" or lower(http.request.uri.path) contains "sftp-config" or lower(http.request.uri.path) contains "sftp.json" or lower(http.request.uri.path) contains "secrets.json" or lower(http.request.uri.path) contains "credentials.json" or lower(http.request.uri.path) contains "sendgrid" or lower(http.request.uri.path) contains "secret_token.rb" or lower(http.request.uri.path) contains "storage.yml" or lower(http.request.uri.path) contains "application.yml") Action: Block ``` **Rule 3: Block wp-config probing** ``` (lower(http.request.uri.path) contains "wp-config" and not http.request.uri.path eq "/wp-admin/") Action: Block ``` **Rule 4: Block path traversal attempts** ``` (lower(http.request.uri.path) contains "etc/passwd" or lower(http.request.uri.path) contains "phpunit" or lower(http.request.uri.path) contains "update.cgi" or http.request.uri contains "cmd=") Action: Block ``` **Rule 5: Block common backup/install directory probing** ``` (lower(http.request.uri.path) eq "/old/" or lower(http.request.uri.path) eq "/new/" or lower(http.request.uri.path) eq "/backup/" or lower(http.request.uri.path) eq "/wordpress/" or lower(http.request.uri.path) eq "/wp/" or lower(http.request.uri.path) eq "/test/") Action: Block ``` Cloudflare string comparison is case-sensitive by default. The `lower()` function converts the URI path to lowercase before comparison, so `/.AwS/CrEdEnTiAlS` gets matched by a rule checking for `.aws/credentials`. Without `lower()`, that request slips through. ### Why CDN-Level Blocking Matters When you block at the CDN: - The request never reaches your server - No PHP execution, no database query, no CPU usage - Your server resources go to real users, not bots - You reduce your attack surface to zero for known patterns - Logs stay clean, making it easier to spot new patterns Blocking at the application level (WordPress plugins, `.htaccess`) still works, but the request already consumed server resources by the time it’s blocked. CDN-level blocking is the difference between a bouncer at the door and a bouncer inside the bar. ## Make This a Habit Pull your 404 logs once a month. Look for patterns. Add new Cloudflare rules. The bots evolve. New scanning patterns appear. The `.AwS/CrEdEnTiAlS` case variation trick is a perfect example, someone specifically designed that to bypass lowercase-only rules. Your 404 logs aren’t just broken links. They’re a free security audit. Read them. * * * *The data in this post comes from real 404 logs collected over one week from two production WordPress sites. No IPs or identifying information from the attacking bots are included.* --- # O que 150+ votos de feature me ensinaram sobre construir as coisas erradas URL: https://eduardovillao.me/pt-br/o-que-150-votos-de-feature-me-ensinaram-sobre-construir-as-coisas-erradas/ Language: pt-br Published: 2026-04-22 Estou usando o [WPFeatureLoop](https://wpfeatureloop.com) nos meus próprios plugins há pouco mais de um mês. Nesse período, coletei mais de 150 votos em 11 feature requests, e isso já mudou o que vou construir a seguir. Aqui vai como os dados realmente são, e o que eles me ensinaram sobre meus próprios pontos cegos como desenvolvedor. ## Os Números Brutos Dois plugins, duas bases de usuários diferentes, um mês de dados: | Feature request | Votos | Comentários | | --- | --- | --- | | Feature A | 41 up · 9 down | — | | Feature B | 51 up · 29 down | — | | Feature C | 4 up | — | | Feature D | 14 up · 4 down | 2 comentários | | Feature E | 8 up · 1 down | — | | Feature F | 7 up · 1 down | — | | Feature G | 5 up · 1 down | — | | Feature H | 6 up | — | | Feature I | 5 up | — | | Feature J | 4 up · 1 down | — | | Feature K | 1 up | — | Nada revolucionário à primeira vista. Mas a parte interessante não é a tabela, é o que ela me disse que eu estava errado. ## Eu ia pular a feature mais pedida Um desses plugins é um sistema de delivery pra WordPress. Quando o cliente faz um pedido, o fluxo termina com uma mensagem de WhatsApp pro dono da loja. Simples, efetivo. Eu estava considerando adicionar uma tela de confirmação de pedido, daquelas que você vê em qualquer checkout de e-commerce: “Seu pedido foi realizado, aqui está o número, aqui está o que você pediu.” Quase não construí. Minha premissa era que adicionaria fricção no fluxo de checkout. O usuário já recebe a confirmação pelo WhatsApp. Pra que mais uma etapa? Aí vieram os votos. Mais de 20 upvotes pra tela de confirmação de pedido. Era uma das features mais pedidas, bem mais do que coisas que eu achava que tinham prioridade maior, como regras avançadas de cupom. O sinal foi claro. Meus usuários não viam como fricção. Viam como confiança. Uma tela de confirmação diz pro cliente “seu pedido foi feito”. Sem ela, alguns clientes não tinham certeza se a mensagem de WhatsApp realmente foi enviada. Movi pro topo do roadmap. Já está construída e sendo entregue. O ponto não é que eu estava errado. O ponto é que sem os dados, eu teria pulado com confiança uma feature que meus usuários ativamente queriam. Esse é o tipo mais perigoso de erro de produto: o que você nem sabe que está cometendo. ## Um feature request que virou sinal de negócio O segundo plugin é uma extensão pra WooCommerce. Um feature request se destacou imediatamente: integração com a API oficial do WhatsApp Business. Mais de 50 upvotes. E não foram só votos, usuários se inscreveram pra ser notificados quando a feature estivesse pronta. Isso não é só “as pessoas querem isso”. Isso é validação de demanda. Quando usuários ativamente se inscrevem numa feature que ainda não podem usar, estão dizendo: “vou prestar atenção quando isso lançar. Talvez eu pague por isso.” Isso muda a conversa. Não é mais uma questão de se construir. É uma questão de como monetizar e quando lançar. Sem os dados de votação, isso seria mais um item no backlog. Com eles, virou uma prioridade estratégica com um sinal claro de disposição pra engajar. ## Por que isso importa pra devs de plugin Se você constrói plugins WordPress, provavelmente está tomando decisões de roadmap baseado numa combinação de tickets de suporte, intuição e o que os concorrentes estão fazendo. Tickets de suporte são enviesados pra problemas, não oportunidades. Intuição é pouco confiável em escala. Análise de concorrentes diz o que outros estão construindo, não o que seus usuários querem. Votação de features preenche um gap específico: diz o que seus usuários valorizam antes de você construir. O que eu aprendi em um mês: **Downvotes são dados também.** A Feature B teve 51 upvotes mas também 29 downvotes. Isso não é consenso, é uma feature polarizante. Construir significa agradar parte dos usuários e frustrar outros. Esse contexto importa pra priorização e pra como você entrega (talvez como configuração opcional, não padrão). **Volume não é tudo.** A Feature K teve 1 upvote. Não significa que não tem valor, significa que é nicho. Se aquele usuário é high-value ou representa um segmento que você quer crescer, ainda pode valer construir. Mas você sabe que não deve priorizar sobre um request com 40 votos. **Silêncio é um sinal.** As features em que ninguém vota são igualmente informativas. Se você estava planejando construir algo e tem zero engajamento da sua base, isso é informação útil antes de gastar três semanas nisso. ## O valor real está nas premissas que você pega A história da tela de confirmação é a que fica voltando na minha cabeça. Eu tinha uma opinião forte. Parecia a decisão certa. E sem dado nenhum, teria entregue um roadmap que pulava uma das features mais desejadas. É isso que votação de features realmente faz. Não substitui seu julgamento, desafia suas premissas antes que elas virem decisões caras. Um mês. Dois plugins. Mais de 150 votos. Duas mudanças de roadmap que eu não teria feito de outra forma. Esse é o caso pra colocar um loop de feedback dentro do seu plugin, não numa página separada que seus usuários nunca vão visitar. * * * *[WPFeatureLoop](https://wpfeatureloop.com) é uma plataforma de votação de features pra devs de plugins WordPress. Adicione um widget de votação direto no admin do seu plugin — sem páginas externas, sem fricção. [Experimente grátis](https://app.wpfeatureloop.com/).* --- # What 150+ Feature Votes Taught Me About Building the Wrong Things URL: https://eduardovillao.me/what-150-feature-votes-taught-me-about-building-the-wrong-things/ Language: en Published: 2026-04-22 I’ve been using [WPFeatureLoop](https://wpfeatureloop.com) on two of my own plugins for just over a month. In that time, I collected over 150 votes across 11 feature requests, and it already changed what I’m building next. Here’s what the data actually looks like, and what it taught me about my own blind spots as a developer. ## The Raw Numbers Two plugins, two different user bases, one month of data: | Feature request | Votes | Comments | | --- | --- | --- | | Feature A | 41 up · 9 down | — | | Feature B | 51 up · 29 down | — | | Feature C | 4 up | — | | Feature D | 14 up · 4 down | 2 comments | | Feature E | 8 up · 1 down | — | | Feature F | 7 up · 1 down | — | | Feature G | 5 up · 1 down | — | | Feature H | 6 up | — | | Feature I | 5 up | — | | Feature J | 4 up · 1 down | — | | Feature K | 1 up | — | Nothing groundbreaking at first glance. But the interesting part isn’t the table — it’s what it told me I was wrong about. ## I Was Going to Skip the Most Requested Feature One of these plugins is a delivery system for WordPress. When a customer places an order, the flow ends with a WhatsApp message to the store owner. Simple, effective. I had been considering adding a proper order confirmation screen — the kind you see on any e-commerce checkout: “Your order has been placed, here’s your order number, here’s what you ordered.” I almost didn’t build it. My assumption was that it would add friction to the checkout flow. The user already gets the WhatsApp confirmation. Why add another step? Then the votes came in. Over 20 upvotes for an order confirmation screen. It was one of the most requested features, significantly more than things I assumed were higher priority, like advanced coupon rules. That was a clear signal. My users didn’t see it as friction. They saw it as trust. A confirmation screen tells the customer “your order went through.” Without it, some customers weren’t sure if the WhatsApp message actually sent. I moved it to the top of the roadmap. It’s now built and shipping. The point isn’t that I was wrong. The point is that without the data, I would have confidently skipped a feature my users actively wanted. That’s the most dangerous kind of product mistake: the one you don’t even know you’re making. ## A Feature Request That Became a Business Signal The second plugin is a WooCommerce extension. One feature request stood out immediately: integration with the official WhatsApp Business API. 50+ upvotes. And not just votes — users subscribed to get notified when the feature would be ready. This isn’t just “people want this.” This is demand validation. When users actively subscribe to a feature they can’t use yet, they’re telling you: “I will pay attention when this ships. I might pay for this.” That changes the conversation. It’s no longer a question of whether to build it. It’s a question of how to monetize it and when to ship. Without the voting data, this would have been one more item on a backlog. With it, it became a strategic priority with a clear signal of willingness to engage. ## Why This Matters for Plugin Developers If you’re building WordPress plugins, you’re probably making roadmap decisions based on some combination of support tickets, gut feeling, and what competitors are doing. Support tickets are biased toward problems, not opportunities. Gut feeling is unreliable at scale. Competitor analysis tells you what others are building, not what your users want. Feature voting fills a specific gap: it tells you what your users value before you build it. Here’s what I learned in one month: **Downvotes are data too.** Feature B had 51 upvotes but also 29 downvotes. That’s not consensus, it’s a polarizing feature. Building it means pleasing some users and frustrating others. That context matters for prioritization and for how you ship it (maybe as an optional setting, not a default). **Volume isn’t everything.** Feature K had 1 upvote. That doesn’t mean it’s worthless — it means it’s niche. If that one user is a high-value customer or represents a segment you want to grow, it might still be worth building. But you know not to prioritize it over a 40-vote request. **Silence is a signal.** The features nobody votes on are just as informative. If you were planning to build something and it gets zero engagement from your user base, that’s useful information before you spend three weeks on it. ## The Real Value Is in the Assumptions You Catch The confirmation screen story is the one that keeps coming back to me. I had a strong opinion. It felt like the right call. And without any data, I would have shipped a roadmap that skipped one of the most wanted features. That’s what feature voting actually does. It doesn’t replace your judgment, it challenges your assumptions before they become expensive decisions. One month. Two plugins. Over 150 votes. Two roadmap changes I wouldn’t have made otherwise. That’s the case for putting a feedback loop inside your plugin, not on a separate page your users will never visit. * * * *[WPFeatureLoop](https://wpfeatureloop.com) is a feature voting platform for WordPress plugin developers. Add a voting widget directly inside your plugin’s admin, no external pages, no friction. [Try it free](https://app.wpfeatureloop.com/).* --- # Na Era da IA, Soft Skills São as Hard Skills URL: https://eduardovillao.me/pt-br/na-era-da-ia-soft-skills-sao-as-hard-skills/ Language: pt-br Categories: IA Published: 2026-04-14 Quanto mais poderosa a IA fica, mais humano o gargalo se torna. Você abre um chat novo, digita um prompt vago, recebe uma resposta medíocre e culpa o modelo. O modelo não é o problema. ## O Que Realmente Mudou Na última década, a coisa mais valiosa que um desenvolvedor podia fazer era executar. Escrever o código, entregar a feature, corrigir o bug. Velocidade e precisão técnica eram os diferenciais. A IA não só acelerou isso. Ela mudou quem faz. Um agente de IA bem orientado consegue escrever uma feature funcionando em minutos. Consegue refatorar um módulo, gerar testes, tratar edge cases e documentar o resultado. Tudo antes de você terminar seu segundo café. A camada de execução não é mais o gargalo. O que sobra? A parte que a IA não consegue fazer sozinha: descobrir o que construir, por que importa, e como enquadrar isso com clareza suficiente pra que outra coisa execute bem. O gargalo migrou da execução pra clareza. ## O Que Clareza Realmente Significa Clareza não é só “comunicar melhor”. Isso é vago demais pra ser útil. Na prática, clareza significa: **Saber o que construir.** Nem todo feature request merece ser construído. Nem todo bug merece ser corrigido agora. A capacidade de avaliar, priorizar e dizer não é uma habilidade. E se torna mais valiosa quando execução é barata. **Saber quando construir.** Sequência importa. Construir a coisa certa na ordem errada gera retrabalho. A IA amplifica esse problema: ela consegue gerar muito código muito rápido, e se a direção estiver errada, você tem muito código errado muito rápido. **Saber o que delegar.** Nem tudo deve ir pra IA. Algumas decisões exigem julgamento humano, contexto ou responsabilidade. O dev que joga tudo pro modelo e torce pelo melhor vai produzir sistemas instáveis, caros e difíceis de manter. Conhecer os limites da delegação é uma habilidade. **Saber como enquadrar.** É aqui que a maioria das pessoas subestima o trabalho. Um brief vago produz um resultado vago. Quanto melhor você define o problema — restrições, output esperado, edge cases, contexto — melhor o resultado. Isso não é prompting como truque. É comunicação como disciplina. Nada disso é técnico no sentido tradicional. Tudo isso determina se o output técnico presta. ## Gerenciar IA É Gerenciar Pessoas O ponto é: nada disso é novo. As melhores práticas pra trabalhar com IA são basicamente princípios de gestão de livro-texto. Quebre o trabalho em tarefas menores. Dê escopo claro antes de delegar. Revise o output antes de entregar. Não sobrecarregue com contexto. Uma coisa de cada vez. Gestores experientes descobriram isso com humanos. A diferença é que a maioria dos desenvolvedores nunca precisou gerir ninguém. Só precisavam codar. Agora estão gerenciando uma IA que consegue executar melhor que eles tecnicamente, e estão descobrindo gestão do jeito difícil: recebendo resultados ruins e se perguntando por quê. O dev que passou anos ignorando dinâmica de time, documentação e comunicação clara agora tem um gap. O dev que construiu esses hábitos — mesmo informalmente — tem uma vantagem que não esperava. Fazer bons prompts é liderar bem. Escopar uma tarefa pra IA é o mesmo trabalho cognitivo que escopar uma tarefa pra um engenheiro júnior. O loop de feedback é só mais rápido. ## As Habilidades Que Sempre Estiveram Lá Soft skills nunca foram realmente soft. Só eram subvalorizadas porque o mercado pagava bem por execução pura. Comunicação. Pensamento estruturado. Priorização. Quebrar problemas complexos em partes. A capacidade de dar zoom out do código e enxergar o produto. Eram nice-to-haves quando entregar código era difícil. Agora que entregar código é fácil, são a competência central. Os devs que vão prosperar não são necessariamente os melhores em escrever código. São os melhores em saber qual código escrever, por quê, e como garantir que seja feito direito — seja por eles mesmos, um time, ou um modelo. Isso não é soft skill. Isso é o trabalho agora. ## O Que Fazer Com Isso Se você é dev e está lendo isso, a pergunta não é se a IA vai te substituir. É se você está construindo as habilidades que a IA não consegue substituir. Comece pequeno: da próxima vez que abrir um chat com uma IA, escreva o prompt como se estivesse fazendo um briefing pra um engenheiro júnior capaz, mas sem contexto nenhum. Defina o objetivo, as restrições, como “pronto” se parece, e o que evitar. Veja se o output muda. Vai mudar. Esse gap — entre um pedido mal pensado e um bem estruturado — é exatamente onde o valor está agora. Não só como você comunica, mas quão claramente você pensou antes de digitar qualquer coisa. Começar por aí é o primeiro passo. Mas vai mais fundo: desenvolvimento orientado por spec, harness engineering, workflows estruturados de IA, como times estão repensando todo o processo de desenvolvimento ao redor dessas ferramentas. É sobre isso que vou continuar escrevendo aqui. Se quiser acompanhar, se inscreve. --- # wp_add_inline_script não funciona em Block Themes (FSE): Causa e Solução URL: https://eduardovillao.me/pt-br/wp_add_inline_script-nao-funciona-em-block-themes-fse-causa-e-solucao/ Language: pt-br Categories: Quick Tips Published: 2026-04-14 Você chama `wp_add_inline_script()` dentro de um shortcode. Funciona perfeitamente em temas clássicos. Aí você troca pra um block theme — ou seu usuário troca — e de repente: nada. Sem erro PHP, sem warning, só uma falha silenciosa e um `X is not defined` no console do browser. Isso é uma inconsistência conhecida, rastreada no WordPress Trac #54958. Aqui vai o que está realmente acontecendo e como corrigir direito. ## O Problema A diferença está em *quando* os shortcodes rodam em relação ao `wp_head()`. **Em um tema clássico:** - `wp_enqueue_scripts` dispara dentro do `wp_head()` → seu handle é registrado - Conteúdo renderiza → shortcode executa → `wp_add_inline_script('my-handle', ...)` ✅ handle já existe **Em um block theme (FSE):** - `get_the_block_template_html()` renderiza blocks e shortcodes *antes* do `wp_head()` - Shortcode executa → `wp_add_inline_script('my-handle', ...)` ❌ handle ainda não existe - `wp_head()` dispara → `wp_enqueue_scripts` → `wp_register_script(...)` — tarde demais Esse é um comportamento intencional no core do WordPress. O comentário no `template-canvas.php` diz explicitamente: > “This needs to run before `` so that blocks can add scripts and styles in `wp_head()`.” ## Por que o wp\_add\_inline\_script especificamente quebra Essa é a parte sutil. Nem tudo quebra — só o `wp_add_inline_script`. `wp_enqueue_script('handle')` sem registro prévio ainda funciona. O WordPress adiciona o handle a uma fila e tem um mecanismo pra re-enqueue automaticamente um script enfileirado prematuramente assim que ele é registrado — resolve tudo depois na hora de imprimir os scripts. `wp_add_inline_script('handle', ...)` é diferente. Ele acessa `$wp_scripts->registered['handle']` imediatamente no momento da chamada. Se o handle ainda não está registrado, retorna `false` — silenciosamente. Sem erro, sem warning. Ou seja: enqueue tolera um handle ainda não registrado. Inline script não. Essa é a armadilha inteira. ## A Solução: registrar no init Mova o `wp_register_script` pro hook `init`. Ele roda antes dos blocks renderizarem, então o handle já está pronto quando o shortcode precisar dele. ```php // ❌ Antes — registro no wp_enqueue_scripts (tarde demais pro FSE) add_action( 'wp_enqueue_scripts', function() { wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true ); wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] ); }); // ✅ Depois — registro no init (antes da renderização dos blocks) add_action( 'init', function() { wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true ); }); add_action( 'wp_enqueue_scripts', function() { // wp_localize_script funciona aqui — handle já está registrado wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] ); }); // Shortcode — sem mudanças necessárias function my_shortcode() { wp_add_inline_script( 'my-handle', 'const config = ' . wp_json_encode($data) . ';', 'before' ); wp_enqueue_script( 'my-handle' ); return '
...
'; } ``` O shortcode fica intocado. Só move o registro pra mais cedo. Isso funciona tanto pra temas clássicos quanto block themes — registrar no `init` é explicitamente documentado como válido na referência de desenvolvimento do WordPress. ## O que NÃO funciona como alternativa **`enqueue_block_assets`:** dispara dentro do `wp_enqueue_scripts` — mesmo problema de timing. **Condicional com `wp_is_block_theme()`:** frágil, duplica código, quebra se o usuário trocar de tema. Te força a manter dois caminhos de código pra algo que tem uma solução limpa única. ## Ordem de execução dos hooks (referência) ``` plugins_loaded └─ init ← registre scripts aqui └─ wp_loaded └─ template_redirect └─ [FSE] get_the_block_template_html() ← shortcodes rodam aqui └─ wp_head() └─ wp_enqueue_scripts ← localize/enqueue aqui └─ [Classic] the_content / shortcodes ← temas clássicos rodam aqui └─ wp_footer └─ wp_print_footer_scripts ``` ## Regra Prática Se seu plugin usa `wp_add_inline_script()` dentro de um shortcode: registre o script no `init`, localize no `wp_enqueue_scripts`, enqueue dentro do shortcode. Zero lógica condicional, funciona em qualquer lugar. Eu esbarrei nisso trabalhando num plugin que usa shortcodes pra renderizar conteúdo dinâmico. Levei mais tempo do que gostaria de admitir pra descobrir que a solução era uma mudança de uma linha na prioridade do hook — não algo dentro do shortcode em si. ## Referências - [WordPress Trac #54958](https://core.trac.wordpress.org/ticket/54958) — “Inconsistent behaviour for wp\_add\_inline\_script between block-based and standard themes” - [wp\_add\_inline\_script()](https://developer.wordpress.org/reference/functions/wp_add_inline_script/) — Developer Reference - [wp\_register\_script()](https://developer.wordpress.org/reference/functions/wp_register_script/) — Developer Reference - [template-canvas.php](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/template-canvas.php) — WordPress Core (GitHub) - [Block Editor Handbook — Enqueueing Assets](https://developer.wordpress.org/block-editor/how-to-guides/enqueueing-assets-in-the-editor/) - [Theme Handbook — Including Assets](https://developer.wordpress.org/themes/basics/including-css-javascript/) --- # Como Eu Melhorei a Taxa de Ativação do WPFeatureLoop em 80% com WordPress Playground URL: https://eduardovillao.me/pt-br/como-eu-melhorei-a-taxa-de-ativacao-do-wpfeatureloop-em-80-com-wordpress-playground/ Language: pt-br Categories: Quick Tips Published: 2026-04-14 A maioria dos produtos SaaS tem um gap entre o signup e o momento em que o usuário realmente *entende* o produto. No WPFeatureLoop, esse gap era brutal. Usuários criavam uma conta, olhavam o dashboard, e… nada. O próximo passo era instalar um SDK via Composer. Pra um dev WordPress que só queria ver se a ferramenta valia seu tempo, isso é pedir demais. Tínhamos 72% de drop-off entre criação de conta e primeira instalação do SDK. A maioria desses usuários nunca voltou. Então eu corrigi o onboarding. E a solução veio de um lugar inesperado: WordPress Playground. ## O Problema do Onboarding “Instale Primeiro” O WPFeatureLoop é um SaaS de votação de features pra donos de plugins WordPress. Você instala um SDK PHP no seu plugin, e seus usuários ganham um widget embutido onde podem sugerir features, votar e comentar. Você gerencia tudo por um dashboard Kanban. A proposta de valor é clara quando você vê funcionando. O problema era que os usuários não conseguiam ver funcionando sem antes integrar o SDK — o que significa configurar Composer, adicionar uma dependência, configurar chaves e fazer deploy. São 20 a 40 minutos de trabalho antes de conseguir responder a pergunta “isso vale a pena?” Esse é o problema clássico de ativação: você está pedindo pro usuário investir antes de ver o retorno. O número que tornou isso concreto: nossa taxa de ativação — a porcentagem de signups que completavam a integração do SDK e submetiam pelo menos um feature request — estava em 28%. Tudo que vinha depois (retenção, upgrades) ficava prejudicado por esse número. ## A Solução: Deixa Ele Experimentar Primeiro O insight foi simples. Em vez de pedir pro usuário instalar o SDK pra ver o widget, e se eu pudesse mostrar o widget primeiro, com zero setup? É aí que entra o WordPress Playground. WordPress Playground é um ambiente WordPress completo que roda inteiramente no browser, via WebAssembly. Sem servidor. Sem instalação. Um site WordPress real rodando localmente numa aba. E o mais importante: ele suporta Blueprints — um formato de configuração em JSON que permite definir exatamente o que acontece quando o Playground carrega: quais plugins instalar, quais settings configurar, qual página abrir, até com qual usuário logar. Isso significava que eu podia construir um ambiente Playground que: 1. Instala automaticamente um plugin demo com o SDK do WPFeatureLoop já embutido 2. Pré-configura com as chaves de API reais do projeto do usuário 3. Abre direto na página onde o widget está 4. Faz login como admin automaticamente O usuário clica em “Try it live”, uma nova aba abre, e em segundos ele está olhando pro widget real do WPFeatureLoop, conectado ao projeto real dele. ## Como Funciona Tecnicamente O Blueprint é gerado server-side no momento em que o usuário clica em “Try it live”, com as chaves do projeto injetadas dinamicamente. Aqui vai uma versão simplificada da estrutura: ```json { "landingPage": "/sample-page/", "login": true, "plugins": ["wpfeatureloop-demo"], "steps": [ { "step": "defineWpConfigConsts", "consts": { "WPFL_PROJECT_KEY": "chave-real-do-usuario", "WPFL_API_URL": "https://api.wpfeatureloop.com" } } ] } ``` A URL do Playground fica: ``` https://playground.wordpress.net/?blueprint-url=https://app.wpfeatureloop.com/api/blueprint/{project_id} ``` É isso. O usuário cai num widget funcionando, conectado ao dashboard dele. Ele pode submeter um feature request, votar, e depois voltar pro dashboard do WPFeatureLoop e ver aparecer em tempo real. Um detalhe importante: o sandbox do Playground é temporário e não persiste dados entre sessões. Mas pra onboarding isso não importa — o objetivo é demonstrar valor, não armazenar dados. ## A Mudança na UI No dashboard, quando o usuário cria um novo projeto, um modal “Get Started” abre com três abas: - **Try it live** (padrão, destacada): o fluxo do Playground descrito acima - **Install SDK**: as instruções tradicionais de setup via Composer - **See in action**: um vídeo demo embutido Fazer do “Try it live” a aba padrão foi uma escolha deliberada. A maioria dos usuários vai clicar no que já está selecionado. Ao tornar o caminho de zero fricção o padrão, você remove a decisão inteiramente pra maioria dos usuários. ## Os Resultados Depois de lançar isso, a taxa de ativação foi de 28% pra 51% — uma melhoria de 80%. A métrica que mais me importa é a porcentagem de novos signups que completam uma ação significativa na primeira sessão. Esse número se moveu de forma relevante, e a mudança é quase inteiramente atribuível a usuários que antes teriam abandonado no passo “instale o SDK”. Algumas coisas que notei: - Usuários que passaram pelo fluxo do Playground primeiro tinham mais chance de completar a integração do SDK depois. Ver o resultado final primeiro dava uma razão pra fazer o trabalho. - Chamados de suporte sobre setup do SDK caíram: usuários chegavam no passo de instalação com um modelo mental mais claro do que estavam construindo. - A aba “Try it live” é clicada em 92% das criações de projeto. ## Por Que Mais Produtos SaaS Deveriam Fazer Isso O padrão aqui não é específico do WordPress. A ideia central é: não faça o usuário imaginar o valor — mostre o valor primeiro. WordPress Playground tornou isso especialmente fácil pra um produto baseado em WordPress, mas o mesmo princípio se aplica em qualquer lugar onde você consiga criar um ambiente sandbox. A pergunta que vale fazer pra qualquer onboarding de SaaS é: qual é a experiência mínima viável que faz o usuário sentir o produto funcionando? Pro WPFeatureLoop, a resposta era um widget funcionando numa aba do browser. Tudo antes disso — o signup, a criação do projeto, a configuração de chaves — é setup, não valor. WordPress Playground me permitiu comprimir todo esse setup pra zero na primeira experiência do usuário. Se você está construindo um produto WordPress e ainda não olhou os Blueprints do Playground, vale uma tarde investida. A documentação é sólida e a API é direta. * * * *[WPFeatureLoop](https://wpfeatureloop.com/) é uma plataforma de votação de features pra donos de plugins WordPress. Se você está construindo um plugin e quer saber o que seus usuários realmente querem, [experimente](https://wpfeatureloop.com/).* --- # In the AI Era, Soft Skills Are the Hard Skills URL: https://eduardovillao.me/in-the-ai-era-soft-skills-are-the-hard-skills/ Language: en Published: 2026-04-10 The more powerful AI gets, the more human the bottleneck becomes. You open a new chat, type a vague prompt, get a mediocre response, and blame the model. The model isn’t the problem. ## What Actually Changed For the past decade, the most valuable thing a developer could do was execute. Write the code, ship the feature, fix the bug. Speed and technical precision were the differentiators. AI didn’t just speed that up. It changed who does it. A well-prompted AI agent can write a working feature in minutes. It can refactor a module, generate tests, handle edge cases, and document the result. All before you’ve finished your second coffee. The execution layer is no longer the bottleneck. What’s left? The part AI can’t do on its own: figuring out *what* to build, *why* it matters, and *how to frame it* clearly enough that something else can execute it well. The bottleneck shifted from execution to clarity. ## What Clarity Actually Means Clarity isn’t just “communicate better.” That’s too vague to be useful. In practice, clarity means: **Knowing what to build.** Not every feature request deserves to be built. Not every bug deserves to be fixed right now. The ability to evaluate, prioritize, and say no is a skill. It becomes more valuable when execution is cheap. **Knowing when to build it.** Sequencing matters. Building the right thing in the wrong order creates rework. AI amplifies this problem: it can generate a lot of code very fast, and if the direction is wrong, you have a lot of wrong code very fast. **Knowing what to delegate.** Not everything should go to AI. Some decisions require human judgment, context, or accountability. The developer who throws everything at the model and hopes for the best will produce unstable, expensive, hard-to-maintain systems. Knowing the limits of delegation is a skill. **Knowing how to frame it.** This is where most people underestimate the work. A vague brief produces a vague result. The better you can define the problem (constraints, expected output, edge cases, context) the better the output. That’s not prompting as a trick. That’s communication as a discipline. None of this is technical in the traditional sense. All of it determines whether the technical output is any good. ## Managing AI Is Managing People Here’s the thing: none of this is new. The best practices for working with AI are basically textbook management principles. Break work into smaller tasks. Give clear scope before delegating. Review output before shipping. Don’t overload with context. One thing at a time. Experienced managers figured this out with humans. The difference is that most developers never had to manage anyone. They just had to code. Now they’re managing an AI that can out-execute them technically, and they’re discovering management the hard way: by getting bad results and wondering why. The developer who spent years ignoring team dynamics, documentation, and clear communication now has a gap. The developer who built those habits, even informally, has an advantage they didn’t expect. Prompting well is leading well. Scoping a task for AI is the same cognitive work as scoping a task for a junior engineer. The feedback loop is just faster. ## The Skills That Were Always There Soft skills were never actually soft. They were just undervalued because the market paid well for execution alone. Communication. Structured thinking. Prioritization. Breaking down complex problems. The ability to zoom out from the code and see the product. These were nice-to-haves when shipping code was hard. Now that shipping code is easy, they’re the core competency. The developers who will thrive aren’t necessarily the best at writing code. They’re the best at knowing what code to write, why, and how to make sure it gets done right. Whether by themselves, a team, or a model. That’s not a soft skill. That’s the job now. ## What to Do With This If you’re a developer reading this, the question isn’t whether AI will replace you. It’s whether you’re building the skills that AI can’t replace. Start small: next time you open a chat with an AI, write the prompt as if you were briefing a capable but context-free junior engineer. Define the goal, the constraints, what done looks like, and what to avoid. See if the output changes. It will. That gap, between a poorly thought-out request and a well-structured one, is exactly where the value is now. Not just how you communicate it, but how clearly you’ve thought it through before you type anything. Starting there is the first step. But it goes deeper: spec-driven development, harness engineering, structured AI workflows, how teams are rethinking the entire dev process around these tools. That’s what I’ll keep writing about here. *Subscribe if you want to follow along.* --- # wp_add_inline_script doesn’t work in Block Themes (FSE) — Cause and Fix URL: https://eduardovillao.me/wp_add_inline_script-doesnt-work-in-block-themes-fse-cause-and-fix/ Language: en Published: 2026-03-24 You call `wp_add_inline_script()` inside a shortcode. It works fine on classic themes. You switch to a block theme — or your user does — and suddenly: nothing. No PHP error, no warning, just a silent failure and a `X is not defined` in the browser console. This is a known inconsistency tracked in [WordPress Trac #54958](https://core.trac.wordpress.org/ticket/54958). Here’s what’s actually happening and how to fix it properly. ## The Problem The difference comes down to **when** shortcodes run relative to `wp_head()`. In a **classic theme**: 1. `wp_enqueue_scripts` fires inside `wp_head()` → your handle gets registered 2. Content renders → shortcode executes → `wp_add_inline_script('my-handle', ...)` ✅ handle already exists In a **block theme (FSE)**: 1. `get_the_block_template_html()` renders blocks and shortcodes **before** `wp_head()` 2. Shortcode executes → `wp_add_inline_script('my-handle', ...)` ❌ handle doesn’t exist yet 3. `wp_head()` fires → `wp_enqueue_scripts` → `wp_register_script(...)` — too late This is intentional behavior in WordPress core. The comment in [template-canvas.php](https://github.com/WordPress/WordPress/blob/master/wp-includes/template-canvas.php) says it explicitly: > “This needs to run before `` so that blocks can add scripts and styles in `wp_head()`.” ## Why wp\_add\_inline\_script specifically breaks This is the subtle part. Not everything breaks — just `wp_add_inline_script`. `wp_enqueue_script('handle')` without prior registration still works. WordPress adds the handle to a queue and has a mechanism to automatically re-enqueue a prematurely enqueued script once it gets registered — it resolves everything later when printing scripts. `wp_add_inline_script('handle', ...)` is different. It immediately accesses `$wp_scripts->registered['handle']` at call time. If the handle isn’t registered yet, it returns `false` — silently. No error, no warning. So: enqueue tolerates a not-yet-registered handle. Inline script doesn’t. That’s the whole trap. ## The Fix: register on init Move `wp_register_script` to the `init` hook. It runs before blocks render, so the handle is ready when the shortcode needs it. ```php // ❌ Before — registration on wp_enqueue_scripts (too late for FSE) add_action( 'wp_enqueue_scripts', function() { wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true ); wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] ); }); // ✅ After — registration on init (before block rendering) add_action( 'init', function() { wp_register_script( 'my-handle', plugin_dir_url(__FILE__) . 'script.js', [], '1.0', true ); }); add_action( 'wp_enqueue_scripts', function() { // wp_localize_script works here — handle is already registered wp_localize_script( 'my-handle', 'myData', [ 'ajax_url' => admin_url('admin-ajax.php') ] ); }); // Shortcode — no changes needed function my_shortcode() { wp_add_inline_script( 'my-handle', 'const config = ' . wp_json_encode($data) . ';', 'before' ); wp_enqueue_script( 'my-handle' ); return '
...
'; } ``` The shortcode stays untouched. Just move the registration earlier. This works for both classic and block themes — registering on `init` is explicitly documented as valid in the [WordPress developer reference](https://developer.wordpress.org/reference/functions/wp_register_script/). ## What doesn’t work as an alternative **`enqueue_block_assets`**: fires inside `wp_enqueue_scripts` — same timing problem. **`wp_is_block_theme()` conditional**: fragile, duplicates code, breaks if the user switches themes. It forces you to maintain two code paths for something that has a single clean solution. ## Hook execution order (reference) ``` plugins_loaded └─ init ← register scripts here └─ wp_loaded └─ template_redirect └─ [FSE] get_the_block_template_html() ← shortcodes run here └─ wp_head() └─ wp_enqueue_scripts ← localize/enqueue here └─ [Classic] the_content / shortcodes ← classic themes run here └─ wp_footer └─ wp_print_footer_scripts ``` ## Practical Rule If your plugin uses `wp_add_inline_script()` inside a shortcode: register the script on `init`, localize on `wp_enqueue_scripts`, enqueue inside the shortcode. Zero conditional logic, works everywhere. I ran into this while working on a plugin that uses shortcodes to render dynamic content. Took longer than I’d like to admit to figure out that the fix was a one-line change in hook priority — not anything inside the shortcode itself. * * * **References:** - [WordPress Trac #54958](https://core.trac.wordpress.org/ticket/54958) — “Inconsistent behaviour for wp\_add\_inline\_script between block-based and standard themes” - [wp\_add\_inline\_script()](https://developer.wordpress.org/reference/functions/wp_add_inline_script/) — Developer Reference - [wp\_register\_script()](https://developer.wordpress.org/reference/functions/wp_register_script/) — Developer Reference - [template-canvas.php](https://github.com/WordPress/WordPress/blob/master/wp-includes/template-canvas.php) — WordPress Core (GitHub) - [Block Editor Handbook](https://developer.wordpress.org/block-editor/how-to-guides/enqueueing-assets-in-the-editor/) — Enqueueing Assets - [Theme Handbook](https://developer.wordpress.org/themes/core-concepts/including-assets/) — Including Assets --- # How I Improved WPFeatureLoop’s Activation Rate by 80% with WordPress Playground URL: https://eduardovillao.me/how-i-improved-wpfeatureloops-activation-rate-by-80-with-wordpress-playground/ Language: en Published: 2026-03-20 Most SaaS products have a gap between signup and the moment users actually *get it*. For WPFeatureLoop, that gap was brutal. Users would create an account, look at the dashboard, and then… nothing. The next step was installing an SDK via Composer. For a WordPress developer who just wanted to see if the tool was worth their time, that’s a big ask. We had a 72% drop-off between account creation and first SDK install. Most of those users never came back. So I fixed the onboarding. And the fix came from an unexpected place: WordPress Playground. ## **The Problem With “Install First” Onboarding** WPFeatureLoop is a feature voting SaaS for WordPress plugin owners. You install a PHP SDK into your plugin, and your users get an embedded widget where they can suggest features, vote, and comment. You manage everything from a Kanban dashboard. The value proposition is clear once you see it working. The problem was that users couldn’t see it working without first integrating the SDK, which means setting up Composer, adding a dependency, configuring keys, and deploying. That’s 20–40 minutes of work before you can answer the question “is this worth it?” This is the classic activation problem: you’re asking users to invest before they’ve seen the return. The metric that made this concrete: our activation rate, the percentage of signups who completed the SDK integration and submitted at least one feature request, was sitting at 72%. Everything else downstream (retention, upgrades) was handicapped by that number. ## **The Fix: Let Them Try It First** The insight was simple. Instead of asking users to install the SDK to see the widget, what if I could show them the widget first with zero setup? That’s where [WordPress Playground](https://wordpress.org/playground/) comes in. WordPress Playground is a full WordPress environment that runs entirely in the browser, powered by WebAssembly. No server. No install. Just a real WordPress site running locally in a tab. And crucially, it supports **Blueprints**, a JSON configuration format that lets you define exactly what happens when the Playground loads: which plugins to install, what settings to configure, which page to open, even which user to log in as. This meant I could build a Playground environment that: 1\. Automatically installs a demo plugin with the WPFeatureLoop SDK already embedded 2\. Pre-configures it with the user’s actual project API keys 3\. Opens directly on the page where the widget lives 4\. Logs them in as admin automatically The user clicks “Try it live”, a new tab opens, and in seconds they’re looking at the real WPFeatureLoop widget, running against their real project. ## **How It Works Technically** The Blueprint is generated server-side at the moment the user clicks “Try it live”, with their project keys injected dynamically. Here’s a simplified version of the structure: ```json { "landingPage": "/sample-page/", "login": true, "plugins": ["wpfeatureloop-demo"], "steps": [ { "step": "defineWpConfigConsts", "consts": { "WPFL_PROJECT_KEY": "their-actual-key", "WPFL_API_URL": "https://api.wpfeatureloop.com" } } ] } ``` The Playground URL is then: ``` https://playground.wordpress.net/?blueprint-url=https://app.wpfeatureloop.com/api/blueprint/{project_id} ``` That’s it. The user lands on a working widget connected to their dashboard. They can submit a feature request, vote on it, and then switch back to the WPFeatureLoop dashboard and see it appear in real time. One important caveat: the Playground sandbox is temporary and doesn’t persist data between sessions. But that’s fine for onboarding, the goal is to demonstrate value, not to store data. ## **The UI Change** In the dashboard, when a user creates a new project, a “Get Started” modal opens with three tabs: – **Try it live** (default, highlighted): the Playground flow described above – **Install SDK**: the traditional Composer setup instructions – **See in action**: an embedded video demo ![WPFeatureLoop get started screen](/wp/how-i-improved-wpfeatureloops-activation-rate-by-80-with-wordpress-playground/Xnip2026-03-20_17-10-17.png) Making “Try it live” the default tab was a deliberate choice. Most users will click whatever is already selected. By making the zero-friction path the default, you remove the decision entirely for the majority of users. ## **The Results** After shipping this, activation rate went from 28% to 51%, a 80% improvement. The metric I care most about is the percentage of new signups who complete a meaningful action within the first session. That number moved significantly, and the change is almost entirely attributable to users who previously would have bounced at the “install SDK” step. A few things I noticed: – Users who went through the Playground flow first were more likely to complete the SDK integration afterward. Seeing the end result first gave them a reason to do the work. – Support questions about SDK setup dropped: users arrived at the install step with a clearer mental model of what they were building toward. – The “Try it live” tab gets clicked on 92% of project creations. ## **Why More SaaS Products Should Do This** The pattern here isn’t specific to WordPress. The core idea is: **don’t make users imagine the value, show them the value first.** WordPress Playground made this unusually easy for a WordPress-based product, but the same principle applies anywhere you can create a sandbox environment. The question worth asking for any SaaS onboarding is: *what’s the minimum viable experience that makes the user feel the product working?* For WPFeatureLoop, the answer was a working widget in a browser tab. Everything before that, the signup, the project creation, the key configuration, is setup, not value. WordPress Playground let me compress all of that setup to zero for the user’s first experience. If you’re building a WordPress product and haven’t looked at Playground Blueprints yet, it’s worth an afternoon. The [documentation](https://wordpress.github.io/wordpress-playground/blueprints) is solid and the API is straightforward. *WPFeatureLoop is a feature voting platform for WordPress plugin owners. If you’re building a plugin and want to know what your users actually want,* [give it a try](https://wpfeatureloop.com)*.* --- # Why You Should Stop Using left and right in CSS URL: https://eduardovillao.me/why-you-should-stop-using-left-and-right-in-css/ Language: en Published: 2026-02-12 Anyone who has been in programming for a while has gotten used to writing margin-left, padding-right, and rounded-l almost automatically. It’s muscle memory for any developer, and most people still write CSS like this: ``` margin-left: 16px; padding-right: 8px; border-top-left-radius: 6px; ``` Or, in Tailwind: ``` rounded-l-sm border-r-0 ml-4 ``` It works. It always has! But there are situations where this stops being just a detail and starts becoming a problem. Or rather, moments where it requires a bit more attention from us. The interesting part? Modern CSS has already solved this for years. The solution is right there, ready to use. But almost nobody uses it — and many developers don’t even know it exists. ## The Problem left and right are physical properties. They know nothing about layout direction. When you change dir=”rtl”, the content flips — but left is still the physical left side of the screen. The result? • Broken borders • Incorrect rounded corners • Buttons that no longer look like part of the same block • Visually misaligned layouts > It’s not uncommon to see a simple rounded-l or rounded-r break the visual consistency when the layout direction changes. ## The Solution: Logical Properties Modern CSS includes logical properties that respect the document’s direction. ``` margin-inline-start padding-inline-end border-start-start-radius ``` These properties adapt automatically: - In LTR → start = left - In RTL → start = right You write it once. It works in both directions. In Tailwind you can use: ``` rounded-s-sm rounded-e-sm border-e-0 ms-4 ``` Simple as that! ## **It’s Not Just About RTL** Even if your product doesn’t support RTL today: - It’s more robust. - It prevents future bugs. - It keeps your code semantically correct. - It makes your design system ready for internationalization. It’s like using rem instead of px. You can ignore it… until the day you actually need it. ## Practical Rule If you’re dealing with horizontal direction in layout, use: - start / end - inline-start / inline-end - rounded-s / rounded-e - border-s / border-e Leave left and right for truly physical cases (like very specific absolute positioning). CSS has evolved. Most of us just haven’t changed the habit yet. --- # Por que você deveria parar de usar left e right no CSS URL: https://eduardovillao.me/pt-br/por-que-voce-deveria-parar-de-usar-left-e-right-no-css/ Language: pt-br Categories: Quick Tips Published: 2026-02-12 Quem já está no mundo da programação há algum tempo se acostumou a escrever margin-left, padding-right e rounded-l quase no automático. Virou muscle memory de qualquer dev e a maioria das pessoas ainda escreve CSS assim: ``` margin-left: 16px; padding-right: 8px; border-top-left-radius: 6px; ``` Ou, no Tailwind: ``` rounded-l-sm border-r-0 ml-4 ``` Funciona. Sempre funcionou! Mas existem situações em que isso deixa de ser só detalhe e começa a virar problema. Ou melhor: momentos em que isso exige um pouco mais de atenção da nossa parte. A parte curiosa? O CSS moderno já resolve isso há anos. A solução está ali, pronta. Só que quase ninguém usa, e muita gente nem sabe que existe. ## O problema O left e right são propriedades físicas. Elas não sabem nada sobre direção de layout. Quando você muda o dir=”rtl”, o conteúdo vira, mas o left continua sendo esquerda física da tela. Resultado? • Bordas quebradas • Cantos arredondados errados • Botões que deixam de parecer “parte do mesmo bloco” • Layout visualmente desalinhado > Não é difícil encontrar casos em que um simples rounded-l ou rounded-r acaba quebrando o visual quando o layout muda de direção. ## A solução: propriedades lógicas CSS moderno tem propriedades lógicas, que respeitam a direção do documento. ``` margin-inline-start padding-inline-end border-start-start-radius ``` Essas propriedades se adaptam automaticamente: - Em LTR → start = esquerda - Em RTL → start = direita Você escreve uma vez. Funciona nos dois! Já no Tailwind você usa: ``` rounded-s-sm rounded-e-sm border-e-0 ms-4 ``` Simples assim! ## **Não é só sobre RTL** Mesmo que hoje seu produto não suporte RTL: - Isso é mais robusto. - Evita bugs futuros. - Deixa seu código semanticamente correto. - Torna seu design system preparado para internacionalização. É tipo usar rem em vez de px. Você pode ignorar…até o dia que precisa. ## Regra prática Se você está falando de direção horizontal no layout, use: • start / end • inline-start / inline-end • rounded-s / rounded-e • border-s / border-e Deixe left e right para casos realmente físicos (posicionamento absoluto muito específico, por exemplo). CSS evoluiu. A maioria só ainda não mudou o hábito. --- # Meu primeiro WordCamp Brasil (Rio de Janeiro) URL: https://eduardovillao.me/pt-br/meu-primeiro-wordcamp-brasil-rio-de-janeiro/ Language: pt-br Categories: Eventos Published: 2025-12-11 No final de novembro participei do **[WordCamp Brasil 2025](https://brasil.wordcamp.org/2025/)**, realizado no Rio de Janeiro. Foi meu **primeiro WordCamp**, algo que eu já tinha vontade de vivenciar há bastante tempo, então deixo aqui esse registro pessoal da experiência. A participação no evento também contou com o apoio da **[Extendify](https://extendify.com/)**, empresa onde trabalho atualmente, o que tornou possível estar presente. Foi legal dividir essa experiência com o **[Daniel Kossmann](https://www.danielkossmann.com/pt/)**, colega de trabalho e amigo, que também participou do evento. Sempre bom trocar ideias ao vivo com alguém que já acompanha o ecossistema WordPress há bastante tempo. ## **Contributor Day** No **Contributor Day**, participei de diferentes grupos de contribuição. Atuei no grupo de **Tradução**, contribuindo com traduções do WordPress 6.9, e também participei das discussões no grupo de **IA**, onde foram feitos testes e explorações envolvendo o **MCP do WordPress** e possíveis usos de inteligência artificial dentro do ecossistema. Esse dia tem um formato que gosto bastante: menos palestras, mais conversa, colaboração e mão na massa. É uma forma bem direta de contribuir e aprender ao mesmo tempo. ## **Palestras e painéis** No segundo dia acompanhei as palestras e painéis. Um dos destaques, para mim, foi o **[Mercado WordPress no Brasil e no Mundo](https://brasil.wordcamp.org/2025/session/painel-mercado-wordpress/)**. Mesmo já trabalhando para uma empresa fora do Brasil, foi muito interessante ouvir outras histórias, experiências e desafios de pessoas que passaram por trajetórias diferentes até chegar a esse tipo de oportunidade. Algumas palestras técnicas também chamaram bastante a atenção, como: - **[Criando blocos WordPress utilizando Agentes de IA](https://brasil.wordcamp.org/2025/session/criando-blocos-wordpress-utilizando-agentes-de-ia/)** - **[Além do Core: Ferramentas PHP para Desenvolvedores WordPress](https://brasil.wordcamp.org/2025/session/alem-do-core-ferramentas-php-para-desenvolvedores-wordpress/)** Conteúdos bem alinhados com temas que já fazem parte do meu dia a dia, especialmente quando falamos de WordPress, IA e arquitetura de software. Participar do WordCamp Brasil foi uma experiência muito positiva, principalmente por ser a primeira vez. Além do conteúdo, o evento reforça algo que muitas vezes passa despercebido no trabalho remoto e do dia a dia: a importância da comunidade e da troca presencial. Fica o registro, e a vontade de participar dos próximos! --- # Como Auto-Hospedar Plugins do WordPress no GitHub e Entregar Atualizações URL: https://eduardovillao.me/pt-br/como-auto-hospedar-plugins-do-wordpress-no-github-e-entregar-atualizacoes/ Language: pt-br Categories: Quick Tips Published: 2025-02-14 Gerenciar atualizações de plugins pode ser um desafio, especialmente se você não está utilizando o repositório oficial de plugins do WordPress. Mas e se você pudesse auto-hospedar seus plugins no GitHub e entregar atualizações de forma transparente, sem a necessidade de bibliotecas complexas ou serviços de terceiros? Neste guia, apresento uma solução simples e poderosa que segue o padrão de atualizações do WordPress, tornando o processo totalmente transparente para seus usuários. Para isso, desenvolvi uma GitHub Action personalizada e um script PHP que trabalham juntos para gerenciar as atualizações de forma fácil e eficiente. É a mesma solução que uso para alguns dos meus próprios plugins, e agora estou compartilhando com a comunidade para que você também possa aproveitar. Sem dependências extras, sem complicações – apenas GitHub e um pouco de mágica com PHP. Vamos começar! 🚀 ## O Que São Plugins Auto-Hospedados? Antes de entrarmos nos detalhes, vamos entender: auto-hospedar plugins significa gerenciar o armazenamento e as atualizações de seus plugins de forma independente, fora do repositório WordPress.org. Isso permite que você controle onde seus plugins estão armazenados e como as atualizações são entregues, sem depender do repositório oficial do WordPress. Em vez de hospedar o plugin no WordPress.org, você utiliza sua própria infraestrutura – como o GitHub, um servidor privado ou qualquer outro serviço de hospedagem de arquivos – para gerenciar o ciclo de vida do plugin. Basicamente, auto-hospedar permite que os desenvolvedores criem, distribuam e atualizem plugins sob seus próprios termos, enquanto ainda fornecem aos usuários uma experiência de atualização familiar e fluida. ## O Que É Necessário Para Auto-Hospedar Plugins no GitHub? Agora que entendemos o conceito, vamos falar sobre o que é necessário e como o processo funciona. Em um nível geral, você precisará de: ### 1\. Um Servidor (No Nosso Caso, GitHub) **Hospedar os detalhes do plugin**: O servidor armazena metadados sobre o plugin, como versão mais recente, URL de download e outras informações necessárias. **Armazenar os arquivos de distribuição**: Inclui o arquivo .zip do plugin, que será baixado durante o processo de atualização. ### 2\. Um Script de Verificação de Atualizações no Plugin **Buscar dados do servidor**: O script se comunica com o servidor (neste caso, o GitHub) para obter os detalhes mais recentes do plugin. **Comparar versões**: Verifica se a versão instalada no site do usuário está desatualizada, comparando-a com a versão mais recente disponível no servidor. **Encaminhar atualizações**: Se houver uma nova versão disponível, o script integra-se ao sistema de atualização padrão do WordPress, tornando o processo transparente para o usuário. Com esses dois componentes trabalhando juntos, você pode oferecer uma experiência de atualização fluida e automatizada para seus plugins, mantendo total controle sobre o processo de distribuição. ## O Lado do Servidor no GitHub O objetivo aqui não é entrar em todos os detalhes técnicos da GitHub Action que desenvolvi, mas entender o fluxo geral do processo e por que ele é essencial para a solução. Fluxo Simplificado da GitHub Action: ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_19-33-33.png) ### 1\. Gerar Metadados JSON A Action analisa o arquivo readme.txt e outros arquivos relevantes para criar um arquivo JSON contendo informações sobre o plugin, como versão, URL de download, descrição e muito mais. Esse JSON é essencial para que o script PHP no lado do plugin possa consultar o servidor e verificar atualizações. ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_20-14-01-1024x536.png) ### 2\. Preparar o Pacote de Distribuição A Action compila os arquivos do plugin em um pacote .zip pronto para distribuição. Durante esse processo, você pode definir regras específicas para excluir pastas ou arquivos desnecessários – como diretórios de desenvolvimento, casos de teste ou artefatos de build – garantindo que o pacote final seja limpo e otimizado. ### 3\. Criar um Release A Action automatiza a criação de um novo release no GitHub. Esse release serve como ponto de hospedagem para o arquivo .zip. Ele é marcado com a versão correspondente do plugin, garantindo que o WordPress e o script PHP possam buscar corretamente a versão mais recente. ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_20-15-14-1024x392.png) ## O Lado do Plugin – Script de Verificação de Atualizações Do lado do plugin, o script PHP atua como a “ponte” entre o plugin instalado no site WordPress e os metadados hospedados no GitHub. ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_15-37-15.png) ### 1\. Adicionar um Arquivo PHP Simples para Gerenciar Atualizações O script de verificação de atualizações é integrado diretamente ao plugin. Ele monitora eventos de atualização do WordPress e utiliza filtros como plugins\_api e site\_transient\_update\_plugins para fornecer informações de atualização dinamicamente. ### 2\. Buscar Dados JSON no GitHub O script consulta o arquivo JSON hospedado no GitHub para obter informações atualizadas sobre o plugin, como versão, descrição, URL de download e outros detalhes importantes. ### 3\. Comparar a Versão Atual com a Mais Recente Após obter os dados do JSON, o script compara a versão instalada do plugin com a versão mais recente disponível nos metadados. Se forem iguais, nenhuma ação é tomada. Se houver uma versão mais recente, o script encaminha as informações de atualização ao sistema padrão do WordPress. ### 4\. Seguir o Fluxo de Atualização Padrão do WordPress Se uma nova versão estiver disponível, o WordPress assume o controle utilizando seu mecanismo padrão de atualização. Isso garante uma experiência fluida e familiar para os usuários. ## É isso! 🎉 Se você tiver dúvidas, precisar de ajuda na implementação ou quiser compartilhar seu feedback, deixe um comentário! Vamos colaborar para melhorar esta solução juntos! 🚀 👉 Repositório GitHub para a GitHub Action: [wp-self-host-updater-generator](https://github.com/eduardovillao/wp-self-host-updater-generator) 👉 Action no Marketplace do Github: [wp-self-host-updater-generator](https://github.com/marketplace/actions/wp-self-host-updater-generator) 👉 Repositório GitHub para o Script PHP: [wp-self-host-updater-checker](https://github.com/eduardovillao/wp-self-host-updater-checker) * * * ## FAQ ### 1\. Funciona com MU-Plugins? Parcialmente! O lado “servidor” no GitHub funciona perfeitamente. No entanto, como MU-Plugins não seguem o mesmo fluxo de atualização de plugins regulares, o script PHP precisa de ajustes. Já estou trabalhando nisso! ### 2\. Funciona com Temas? Ainda não! Algumas modificações são necessárias para suportar temas, mas essa funcionalidade será adicionada em breve. ### 3\. Posso validar licenças antes de entregar atualizações? Este fluxo é projetado para plugins “gratuitos”, então validação de licença não é suportada no momento. Estou explorando formas de adicionar isso no futuro. ### 4\. Funciona com repositórios privados? Quase! A Action funciona perfeitamente, mas o script PHP precisa de ajustes para usar tokens de autenticação em repositórios privados. --- # Manipule Eventos de Popups do Elementor Sem jQuery URL: https://eduardovillao.me/pt-br/manipule-eventos-de-popups-do-elementor-sem-jquery/ Language: pt-br Categories: Elementor, Formulário, Quick Tips Published: 2025-01-20 Se você já trabalhou com o Elementor e tentou manipular seus popups programaticamente, provavelmente percebeu que a documentação oficial oferece exemplos de manipulação de eventos apenas com jQuery. Porém, se você prefere uma solução moderna e leve utilizando Vanilla JavaScript, este guia é para você. Com a API **MutationObserver**, você pode monitorar alterações no DOM e detectar quando um modal de popup do Elementor é injetado no . Isso permite que você execute ações personalizadas sem depender de bibliotecas adicionais. ## **Por que Usar o MutationObserver** A API **MutationObserver** é um recurso nativo do JavaScript que permite observar mudanças no DOM, como: - Adição ou remoção de elementos. - Modificações nos atributos de elementos existentes. No caso dos popups do Elementor, essa API é perfeita para detectar quando o modal é dinamicamente adicionado ao DOM. ## **Código para Detectar Popups do Elementor** Aqui está um snippet completo que você pode usar no seu projeto: ```js // Seleciona o elemento const body = document.body; // Cria um MutationObserver const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // Verifica se novos nós foram adicionados if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { // Verifica se o nó adicionado é um modal de popup do Elementor if (node.classList && node.classList.contains("elementor-popup-modal")) { console.log("Popup do Elementor detectado:", node); // Adicione sua lógica personalizada aqui } }); } }); }); // Configura o observer para monitorar o observer.observe(body, { childList: true }); // Interrompe a observação quando não for mais necessária (opcional) // observer.disconnect(); ``` ## **Como Funciona** 1\. **Monitorando o** **:** O elemento é observado porque o Elementor injeta seus popups como nós filhos do . 2\. **MutationObserver:** Usamos o **MutationObserver** para monitorar mudanças nos nós filhos do configurando { childList: true }. 3\. **Filtrando Nós Adicionados:** Para cada nó adicionado, verificamos se ele possui a classe elementor-popup-modal, que identifica os popups do Elementor. 4\. **Ações Personalizadas:** Quando o modal é detectado, você pode executar qualquer lógica personalizada no bloco onde o console.log está localizado. ## **Caso de Uso Prático** Suponha que você queira aplicar uma animação personalizada quando um popup do Elementor aparecer. Você pode modificar o código assim: ```js if (node.classList && node.classList.contains("elementor-popup-modal")) { console.log("Popup do Elementor detectado:", node); node.style.opacity = 0; // Começa invisível setTimeout(() => { node.style.transition = "opacity 0.5s"; node.style.opacity = 1; // Efeito de fade-in }, 0); } ``` ## **Por que Escolher Vanilla JS?** 1\. **Sem Dependências:** Reduz o tamanho total do projeto eliminando bibliotecas como jQuery. 2\. **Melhor Performance:** Vanilla JavaScript geralmente é mais rápido, pois não possui o overhead de lidar com seletores e eventos adicionais. 3\. **Compatibilidade Moderna:** A API **MutationObserver** é suportada em todos os navegadores modernos, incluindo Edge e Safari. ### **É Isso!** Usar o **MutationObserver** para detectar popups do Elementor é uma solução elegante e eficiente, especialmente para desenvolvedores que preferem não depender do jQuery. Com este código, você pode personalizar totalmente como interage com os popups do Elementor, seja adicionando animações, rastreando eventos ou implementando outra lógica personalizada. Se achou essa dica útil, compartilhe com outros desenvolvedores e deixe suas ideias de personalização de popups nos comentários! 🚀 --- # Masky.js: A Lightweight Alternative to Inputmask, Cleave.js, and IMask URL: https://eduardovillao.me/masky-js-a-lightweight-alternative-to-inputmask-cleave-js-and-imask/ Language: en Published: 2025-01-20 Finding the right input masking library can be tricky. There are many options, each with its pros and cons. Some are feature-packed but heavy, while others are lightweight but miss critical functionalities like validation or mobile-friendly optimizations. In today’s world, where **performance** and **user experience** are top priorities, selecting the right library is crucial. A solution that minimizes bundle size and enhances mobile usability while providing robust validation can make a huge difference in your project. ## **Popular Libraries and Their Limitations** If you’ve needed input masking for forms, you’ve likely come across libraries like **Inputmask**, **Cleave.js**, and **IMask**. These libraries are really great, but they come with trade-offs: **Inputmask**: - Powerful and flexible, but significantly increases bundle size (~20 KB gzipped). - Lacks features like inputmode for better mobile experiences. **Cleave.js**: - Simple and supports dynamic masks. - However, it lacks built-in validation or automatic configurations like minlength and maxlength. **IMask**: - Offers great features with a moderate size (~5 KB gzipped). - It’s powerful but can be overkill for simpler use cases. While these are solid tools, I felt there was room for a solution that’s **lighter**, **flexible**, and focused on **mobile usability**. ## **Introducing Masky.js** That’s why I built **Masky.js**: an ultra-lightweight (**just 1.3 KB gzipped**) alternative that prioritizes performance without sacrificing flexibility or essential features. **Super Lightweight**: - At just **1.3 KB gzipped**, it’s one of the smallest solutions on the market. - Perfect for projects where bundle size is critical. **Mobile-Friendly**: - Automatically sets the inputmode attribute based on the mask, ensuring a better typing experience on mobile devices. **Fully Customizable**: - Supports custom masks with **prefixes**, **suffixes**, and even reverse masks. **Built-In Validation**: - Native support for **CPF** and **CNPJ** (Brazilian IDs) validation. **Automation**: - Automatically calculates and applies minlength and maxlength based on the mask. **Zero Dependencies**: - 100% Vanilla JS, making it easy to integrate with any environment or framework. ## **Comparison** **Feature** **Masky.js** **Inputmask** **Cleave.js** **IMask** **Size (Gzipped)** 1.3 KB 20 KB 8 KB 5 KB **Dependencies** None None None None **Custom Masks** ✅ ✅ ✅ ✅ **Prefixes/Suffixes** ✅ ❌ ✅ ✅ **Built-in CPF/CNPJ Validation** ✅ ❌ ❌ ❌ **inputmode for Mobile** ✅ ❌ ❌ ✅ **Automatic Min/Max Length** **Validation** ✅ ❌ ❌ ❌ **Reverse Masks** ✅ ✅ ❌ ✅ ## **Usage Example** Simplicity is key. Just add the data-mask attribute, and let Masky.js handle the rest—prefixes, suffixes, validations, and even automatic inputmode and minlength adjustments. ```html ``` **Check more details of how to use on the [documentation](https://github.com/eduardovillao/masky-js).** ## **Try it Out** If you’re looking for a fast, flexible, and performance-focused input masking solution, give **Masky.js** a shot! 👉 **GitHub**: [https://github.com/eduardovillao/masky-js](https://github.com/eduardovillao/masky-js) 👉 **npm**: [https://www.npmjs.com/package/masky-js](https://www.npmjs.com/package/masky-js) I’d love to hear your thoughts or suggestions! 😊 --- # Exciting Updates for Form Masks for Elementor Free and PRO Versions! URL: https://eduardovillao.me/exciting-updates-for-form-masks-for-elementor-free-and-pro-versions/ Language: en Published: 2025-01-12 We’re thrilled to announce the latest updates to **Form Masks for Elementor**, both for the Free and PRO versions. With these enhancements, we’ve taken form input control and customization to the next level. Whether you’re a Free version user or a PRO subscriber, there’s something new for you! ## **What’s New in the PRO Version?** ![Form Masks for Elementor Form Controls](/wp/exciting-updates-for-form-masks-for-elementor-free-and-pro-versions/Xnip2025-01-12_22-29-50.png) Our PRO users now have access to a range of powerful new features designed to enhance your forms and improve data accuracy: **✨ Prefix and Suffix Options** Add prefixes (e.g., $, +, or #) or suffixes (e.g., %, lbs, or km) to your masks for better user guidance. These additions make it easier for users to understand the type of data they need to input. **✨ Min and Max Character Validation** Ensure users provide data that meets your exact requirements. With this feature, you can define minimum and maximum character limits to prevent incomplete or invalid submissions. **✨ Inputmode Control** Customize the keyboard type based on the input field’s context. For example: - Open a numeric keypad for phone numbers. - Open a text keyboard for alphanumeric data. ****🚀 New Masking Library: Built by Us, for You**** We’ve replaced the old jQuery dependency with a **lightweight, vanilla JavaScript masking library** developed entirely by our team. **Why This Change?** Our custom library is: - **Super Lightweight**: Minimal impact on your website’s performance. - **Highly Performant**: Optimized to handle complex masking scenarios effortlessly. - **Independently Maintained**: No external dependencies mean we have complete control over updates and improvements. By switching to our own library, we’ve made Form Masks for Elementor faster, smoother, and future-ready. This update ensures that you get the best masking experience without relying on bulky third-party code. **🔧 Code Improvements** Our team has fine-tuned the code for better efficiency and reliability, ensuring a seamless experience for all users. [**Upgrade to the PRO version today!**](https://codecanyon.net/item/form-masks-for-elementor/25872641) ## **Updates for the Free Version** We haven’t forgotten our Free users! The latest update includes: ****🚀 New Masking Library: Built by Us, for You**** We’ve replaced the old jQuery dependency with a **lightweight, vanilla JavaScript masking library** developed entirely by our team. **Why This Change?** Our custom library is: - **Super Lightweight**: Minimal impact on your website’s performance. - **Highly Performant**: Optimized to handle complex masking scenarios effortlessly. - **Independently Maintained**: No external dependencies mean we have complete control over updates and improvements. By switching to our own library, we’ve made For **✅ Support for PRO Controls** Enjoy enhanced compatibility and integration with PRO features, making it easier to upgrade when you’re ready. **🔧 Code Enhancements** We’ve optimized the codebase for improved stability, usability, and performance. [**Download the latest Free version now!**](https://wordpress.org/plugins/form-masks-for-elementor/) ## **Why Upgrade to PRO?** The PRO version takes your forms to the next level with unmatched flexibility and control. Whether you need custom masks, advanced validation, or improved user input guidance, the PRO version has you covered. ## **Your Feedback Matters** We’re constantly improving and adding new features based on your feedback. If you have ideas, suggestions, or questions, feel free to reach out to us at [**plugins@eduardovillao.me**](mailto:plugins@eduardovillao.me). Thank you for being a valued part of the **Form Masks for Elementor** community. We can’t wait to see how you’ll use these new features to build even better forms! --- # Masky.js: Uma Alternativa Leve ao Inputmask, Cleave.js e IMask URL: https://eduardovillao.me/pt-br/masky-js-uma-alternativa-leve-ao-inputmask-cleave-js-e-imask/ Language: pt-br Categories: Quick Tips Published: 2025-01-10 Encontrar a biblioteca certa para mascarar inputs pode ser um desafio. Existem muitas opções disponíveis, cada uma com seus prós e contras. Algumas são repletas de recursos, mas pesadas; outras são leves, mas carecem de funcionalidades importantes, como validação ou otimizações para dispositivos móveis. No mundo atual, onde **performance** e **experiência do usuário** são prioridades, escolher a biblioteca certa é fundamental. Uma solução que minimize o tamanho do bundle e melhore a usabilidade em dispositivos móveis, enquanto oferece validações robustas, pode fazer uma enorme diferença no seu projeto. ## **Bibliotecas Populares e Suas Limitações** Se você já precisou de máscaras para formulários, provavelmente encontrou bibliotecas como **Inputmask**, **Cleave.js** e **IMask**. Embora essas bibliotecas sejam excelentes, elas vêm com algumas desvantagens: **Inputmask** - Poderosa e flexível, mas aumenta significativamente o tamanho do bundle (~20 KB gzipado). - Não possui recursos como o atributo inputmode para uma melhor experiência em dispositivos móveis. **Cleave.js** - Simples e com suporte a máscaras dinâmicas. - No entanto, carece de validação embutida ou configurações automáticas como minlength e maxlength. **IMask** - Oferece ótimos recursos com um tamanho moderado (~5 KB gzipado). - É poderosa, mas pode ser excessiva para casos de uso mais simples. Embora essas ferramentas sejam sólidas, senti que havia espaço para uma solução mais **leve**, **flexível** e focada na **usabilidade em dispositivos móveis**. ## **Apresentando Masky.js** Foi por isso que criei o **Masky.js**: uma alternativa ultra-leve (apenas **1.3 KB gzipado**) que prioriza a performance sem abrir mão da flexibilidade e dos recursos essenciais. **Super Leve**: - Com apenas **1.3 KB gzipado**, é uma das soluções mais compactas do mercado. - Ideal para projetos onde o tamanho do bundle é crítico. **Amigável para Mobile**: - Define automaticamente o atributo inputmode com base na máscara, garantindo uma melhor experiência de digitação em dispositivos móveis. **Totalmente Personalizável**: - Suporte a máscaras personalizadas com **prefixos**, **sufixos** e até máscaras reversas. **Validação Embutida**: - Suporte nativo para validação de **CPF** e **CNPJ** (documentos brasileiros). **Automação**: - Calcula e aplica automaticamente os atributos minlength e maxlength com base na máscara. **Sem Dependências**: - 100% Vanilla JS, fácil de integrar com qualquer ambiente ou framework. ## **Comparação** **Recurso** **Masky.js** **Inputmask** **Cleave.js** **IMask** **Tamanho (Gzipado)** 1.3 KB 20 KB 8 KB 5 KB **Dependências** Nenhuma Nenhuma Nenhuma Nenhuma **Máscaras Personalizáveis** ✅ ✅ ✅ ✅ **Prefixos/Sufixos** ✅ ❌ ✅ ✅ **Validação de CPF/CNPJ** ✅ ❌ ❌ ❌ **inputmode para Mobile** ✅ ❌ ❌ ✅ **Validação de Min/Max** ✅ ❌ ❌ ❌ **Máscaras Reversas** ✅ ✅ ❌ ✅ ## **Como Usar o Masky.js?** A simplicidade é fundamental. Basta adicionar o atributo data-mask, e o Masky.js cuida do resto — prefixos, sufixos, validações e até ajustes automáticos de inputmode e minlength. ```html ``` Confira mais detalhes de uso na [documentação](https://github.com/eduardovillao/masky-js). ## **Experimente o Masky.js** Se você está procurando uma solução de máscaras rápida, flexível e focada na performance, experimente o **Masky.js**! 👉 **GitHub**: [Masky.js](https://github.com/eduardovillao/masky-js) 👉 **npm**: [Masky.js](https://www.npmjs.com/package/masky-js) Adoraria ouvir seus comentários ou sugestões! 😊 --- # Handle Elementor Popup Events Without jQuery URL: https://eduardovillao.me/handle-elementor-popup-events-without-jquery/ Language: en Published: 2025-01-08 If you’ve ever worked with **Elementor** and tried to manipulate its popups programmatically, you’ve probably noticed that the official documentation provides event handling examples only with **jQuery**. However, if you prefer a modern and lightweight solution using **Vanilla JavaScript**, this guide is for you. With the **MutationObserver** API, you can monitor changes to the DOM and detect when an Elementor popup modal is injected into the . This allows you to execute custom actions without relying on additional libraries. ## **Why Use MutationObserver?** The **MutationObserver** API is a native JavaScript feature that lets you observe DOM changes, such as: - Adding or removing elements. - Modifications to attributes of existing elements. In the case of Elementor popups, it’s perfect for detecting when the popup modal is dynamically added to the DOM. ## **Code to Detect Elementor Popups Show** Here’s a complete code snippet that you can use in your project: ```js // Select the element const body = document.body; // Create a MutationObserver const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // Check if new nodes were added if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { // Check if the added node is an Elementor popup modal if (node.classList && node.classList.contains("elementor-popup-modal")) { console.log("Elementor popup detected:", node); // Add your custom logic here } }); } }); }); // Configure the observer to monitor the observer.observe(body, { childList: true }); // Stop observing when no longer needed (optional) // observer.disconnect(); ``` ### **How It Works** - **Monitoring** : - The element is observed because Elementor injects its popups as child nodes of the . - **MutationObserver**: - We use the MutationObserver to watch for changes in the child nodes of by setting { childList: true }. - **Filtering Added Nodes**: - For each added node, we check if it has the class elementor-popup-modal, which identifies Elementor’s popups. - **Custom Actions**: - When the modal is detected, you can execute any logic in the block where the console.log statement is located. ## **Practical Use Case** Suppose you want to apply a custom animation when an Elementor popup appears. You can modify the code like this: ```js if (node.classList && node.classList.contains("elementor-popup-modal")) { console.log("Elementor popup detected:", node); node.style.opacity = 0; // Start invisible setTimeout(() => { node.style.transition = "opacity 0.5s"; node.style.opacity = 1; // Fade-in effect }, 0); } ``` ## **Why Choose Vanilla JS?** - **No Dependencies**: - Reduces the overall project size by eliminating libraries like jQuery. - **Improved Performance**: - Vanilla JavaScript is generally faster since it doesn’t have the overhead of handling selectors and events. - **Modern Compatibility**: - MutationObserver is supported in all modern browsers, including Edge and Safari. ## That’s it! Using **MutationObserver** to detect Elementor popups is an elegant and efficient solution, especially for developers who prefer not to rely on jQuery. With this code, you can fully customize how you interact with Elementor popups, whether it’s adding animations, tracking events, or implementing other custom logic. If you found this tip helpful, share it with other developers and leave your ideas for popup customization in the comments! --- # How to Self-Host WordPress Plugins on Github and Deliver Updates URL: https://eduardovillao.me/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/ Language: en Published: 2024-12-26 Managing plugin updates can be a challenge, especially if you’re not relying on the WordPress Plugin Repository. But what if you could self-host your plugins on GitHub and deliver updates seamlessly, without the need for complex libraries or third-party services? In this guide, I’ll walk you through a simple yet powerful solution that adheres to WordPress’s standard for updates, making the process completely transparent for your users. To make this possible, I’ve developed a custom GitHub Action and a PHP script that work together to handle updates effortlessly. This is the same solution I use for some of my own plugins, and now I’m sharing it with the community so you can benefit from it too. No extra dependencies, no fuss—just GitHub and a bit of PHP magic. Let’s get started! 🚀 ## What is self-host plugins? Before diving in, let’s take a step back: self-hosting plugins means managing your plugin’s storage and updates independently, outside the WordPress.org repository. So you take control of where your WordPress plugins are stored and how updates are delivered, without relying on the official WordPress Plugin Repository. Instead of hosting your plugin on WordPress.org, you use your own infrastructure—such as GitHub, a private server, or any other file host—to manage your plugin’s lifecycle. In essence, self-hosting empowers developers to build, distribute, and update plugins on their own terms, while still providing users with a seamless and familiar update experience. ## What is required to self-host plugins on Github and deliver updates? Now that we understand the basics of self-hosting plugins, let’s discuss what’s needed and how the process works. At a high level, you’ll need: ### 1\. **A server (in our case Github):** **Provide plugin details:** The server hosts metadata about your plugin, including the latest version, download URL, and other required information. **Store distribution files:** This includes the .zip file of your plugin, which will be downloaded during the update process. ### 2\. **An Update Checker Script in Your Plugin** **Fetch data from the server:** The script communicates with your server (GitHub in this case) to retrieve the latest plugin details. **Compare versions:** It checks whether the version installed on the user’s site is outdated by comparing it with the latest version available on the server. **Forward updates:** If a new version is available, the script seamlessly integrates with WordPress’s default update system, making the process transparent for the user. With these two components working together, you can deliver a smooth and automated update experience for your plugins, all while retaining full control over the distribution process. So, based on this structure, let’s talk about the GitHub Action and custom PHP script that I developed to simplify everything. These will be the focus of the next sections, and I’m excited to share how you can implement them for your own projects! ## The server side on Github The goal here isn’t to dive deeply into how GitHub Actions that I developed work or cover every technical detail. For that, I’ll create a separate, more technical post. Instead, let’s take a step back to understand the big picture of the action’s workflow and why it’s so essential to our solution. At its core, the GitHub Action automates the creation of everything needed for the plugin update process. Here’s the high-level flow: ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_19-33-33.png) ### 1\. **Generate JSON Metadata** The action parses your readme.txt and other relevant files to create a JSON file containing metadata about your plugin, such as version, download URL, description and much more. These metadata are essential for the PHP script on plugin side to query the server and make the checks to verify if is updated. The key detail here is that the **download URL is automatically generated to point directly to the** .zip **file in the release created by the action**. This means WordPress and your plugin will fetch updates directly from GitHub, ensuring a seamless and efficient update process. ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_20-14-01-1024x536.png) ### 2\. **Prepare the Distribution Package** The action compiles your plugin files into a .zip package, ready for distribution. During this process, specific rules can be defined to exclude unnecessary folders or files—such as development directories, test cases, or build artifacts—that shouldn’t be included in the final plugin package. This ensures a clean and optimized distribution file, reducing its size and avoiding unnecessary files being downloaded by users. ### 3\. **Release Creation** Once the metadata and distribution package are prepared, the action automates the creation of a new GitHub Release. This release serves as the hosting point for the .zip file. The release is tagged with the corresponding plugin version, ensuring WordPress and the PHP script can correctly fetch the latest version. This streamlined process makes the release instantly accessible, ensuring smooth updates for your users. ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_20-15-14-1024x392.png) This streamlined process ensures that every time you create a new tag/version of your plugin, all required files and metadata are automatically prepared and hosted, ready to deliver updates to your users. **If you’d like to dive into the complete implementation, check the GitHub repository. The README provides detailed examples and instructions to guide you through the implementation process: [https://github.com/eduardovillao/wp-self-host-updater-generator](https://github.com/eduardovillao/wp-self-host-updater-generator).** ## The plugin side – update checker file As with the previous section, I’ll create a dedicated post to dive deeper into the technical aspects of this PHP file. For now, let’s focus on understanding the overall flow and what this script does to ensure plugin updates work seamlessly. The PHP script serves as the “bridge” between the plugin installed on the WordPress site and the server-side metadata hosted on GitHub. Here’s a high-level view of how it operates: ![](/wp/how-to-self-host-wordpress-plugins-on-github-and-deliver-updates/Xnip2024-12-26_15-37-15.png) ### **1\. Add a Simple PHP File to Manage Updates** The update checker script is integrated directly into your plugin. It listens for WordPress’s native update events and hooks into filters like *plugins\_api* and *site\_transient\_update\_plugins* to manage and provide update information dynamically. ### **2\. Request JSON Data from the GitHub Repository** The script queries the GitHub-hosted JSON metadata file for the latest information about your plugin, including the current version, description, download URL, and other important details. This step ensures the site always has access to accurate, up-to-date plugin information. ### **3\. Compare Current Version vs. Latest Version** Once the JSON data is retrieved, the script compares the currently installed version of the plugin with the version available in the metadata. If the versions match, no action is taken. If a newer version exists, the script forwards the update information to WordPress’s built-in update system. ### **4\. Follow the Default WordPress Flow to Upgrade** If a new version is available, WordPress takes over using its default upgrade mechanism. This ensures a seamless and familiar experience for end users, who can update the plugin just like they would with any other WordPress plugin. **If you’d like to find the complete code along with examples of how to implement check here the repository’s README: [https://github.com/eduardovillao/wp-self-host-updater-checker](https://github.com/eduardovillao/wp-self-host-updater-checker)** This simple yet powerful script is what makes self-hosted updates possible. For those who want to dig deeper, the accompanying code and the flowchart above illustrate the inner workings in greater detail. Stay tuned for a more technical post that will break down this script line by line! ## And that’s it! 🎉 If you have any questions, need help with implementation, or have tested this solution and want to share feedback, feel free to drop a comment below. I’d love to hear from you and have a conversation about your experience. Let’s collaborate and make this solution even better together! 🚀 * * * ## FAQ #### 1\. **Will this works with mu-plugins?** Partially! The “server” side on GitHub is fully capable of managing MU-Plugins as well. However, since MU-Plugins don’t follow the same update flow as regular WordPress plugins, the PHP script will require some adjustments to handle them. I’m already working on these changes and will share the solution for MU-Plugins soon. #### 2\. **Will this works with Themes?** Not yet! Some modifications are needed to support themes. For now, this solution works exclusively with plugins. But don’t worry—I’m already working on it, and I’ll share updates on theme support soon. #### 3\. ****Can I validate the user license before delivering updates?**** This flow is designed for “free” plugins, so license validation isn’t currently supported. However, I’m exploring ways to incorporate this feature, and I’ll likely share updates about it in the near future. #### 4\. ****Will this work with private repositories?**** Almost! The action don’t need any change and is working now, but the php script need some changes because to use private repositories, a token is required to authenticate requests for the JSON data. I’ll share more details on how to implement this soon. --- # Como adicionar máscara nos campos do formulário do Elementor – plugin gratuito URL: https://eduardovillao.me/pt-br/mascara-nos-campos-do-formulario-do-elementor/ Language: pt-br Categories: Elementor, Formulário, Plugin gratuito Published: 2024-02-20 Sim, eu tenho certeza que você já ter se perguntado: por que o formulário do Elementor não tem máscaras para os campos? Comigo não foi diferente, então pra ajudar desenvolvi um **plugin gratuito** para te ajudar a resolver esse problema de uma vez por todas! ## Adicionando as máscaras ao formulário Primeiro, você precisa baixar o plugin do diretório oficial do WordPress e instalar/ativar no seu site: [https://wordpress.org/plugins/form-masks-for-elementor/](https://wordpress.org/plugins/form-masks-for-elementor/) Agora é bem simples, abra o Editor do Elementor e adicione o seu widget de formulário. Em campo campo, na aba “avançado” você verá um novo select para você selecionar a máscara que deseja usar. ![Elementor formulário controle de máscara](/wp/mascara-nos-campos-do-formulario-do-elementor/Xnip2023-02-23_23-55-51.jpg) > Importante: por enquanto, as máscaras funcionam apenas nos campo do tipo “text”. ## Ficou com dúvida? Veja mais detalhes no vídeo Fácil né?! Conta ai nos comentários o que achou! --- # Executar ação após o pagamento ser confirmado no WooCommerce URL: https://eduardovillao.me/pt-br/executar-acao-apos-o-pagamento-ser-confirmado-no-woocommerce/ Language: pt-br Categories: WooCommerce Published: 2023-02-22 Ai chega aquele requisito de executar uma ação apenas após a confirmação de pagamento do pedido (através do WooCommerce, é claro), como é que faz? Há algum tempo atrás precisei fazer isso e achei aqui nas minhas anotações esses dias, então resolvi compartilhar! O WooCommerce já tem uma action prontinha pra gente utilizar, e ela recebe o parâmetro da id do pedido que foi atualizado. Com isso, sempre que o seu gateway de pagamento enviar a info que o pagamento está ok esse gancho vai ser executado e você pode pendurar nele a ação que quiser. No exemplo abaixo estou fazendo a atualização de algum campo do usuário, mas você pode fazer o que quiser. ```php function checkPayment( $order_id ) { $order = new WC_Order( $order_id ); /** * Faça o que você quiser aqui! * Exemplo atualizando dados do usuário após a confirmação do pagamento. */ $user_id = $order->get_user_id(); update_user_meta( $user_id, 'field_name', 'field_value' ); } add_action( 'woocommerce_payment_complete', 'checkPayment' ); ``` > Importante: essa action é executada se o método de pagamento executar woocommerce\_payment\_complete() ou $order->payment\_complete(). A maioria faz isso, pois é um comportamento padrão/recomendado ao registrar métodos de pagamento. Mas vale a pena o lembrete. Testa ai e me conta nos comentários! Até a próxima! --- # Como centralizar o botão enviar do Contact Form 7 URL: https://eduardovillao.me/pt-br/como-centralizar-o-botao-enviar-do-contact-form-7/ Language: pt-br Categories: Contact Form 7, Formulário Published: 2023-02-20 O contact form 7 é um dos plugins mais utilizados para formulário no WordPress. Ele tem um visual simples, mas tem milhares de possibilidades de customização. Alguns temas, plugins e até page builders trazem algumas opções de customização de estilo mais avançada para seus formulários. Mas, nem sempre você encontra exatamente o que gostaria.  E isso não significa que você não irá conseguir o resultado que deseja. Pois, embora simples, o contact form 7 é um plugin muito completo. Mas vamos ao que interessa. ## Centralizar o botão de enviar do Contact Form 7 Para fazer isso é muito simples. Você precisará apenas criar o seu formulário de contato da forma que desejas, e acrescentar o seguinte código no seu editor de CSS ou no editor do tema: ```css input.wpcf7-form-control.wpcf7-submit { display: block; margin: 0 auto; } ``` Espero que tenha gostado. Até a próxima. --- # Como centralizar as tabs do Elementor? URL: https://eduardovillao.me/pt-br/como-centralizar-as-tabs-do-elementor/ Language: pt-br Categories: Elementor Published: 2023-02-20 Você já precisou utilizar o widget de tabs (ou Abas) do Elementor, e precisava centralizar as tabs, mas não achou essas opções no widget? Você não é o único! Tenho recebido com certa frequência essa dúvidas, então, sem muita enrolação, vamos pra solução! ### Como centralizar as tabs? É simples, você vai precisar utilizar um código CSS que pode ser inserido no próprio widget do Elementor (se você estiver usando a versão PRO) ou no editor de CSS do seu tema. ```css .elementor-tabs-wrapper { display: flex; justify-content: center; } ``` E esse será o resultado final: ![tabs do Elementor centralizadas](/wp/como-centralizar-as-tabs-do-elementor/centered-tab-1024x196-1.jpg) Espero que tenha ajudado! Até a próxima! --- # Action after confirm payment on WooCommerce URL: https://eduardovillao.me/action-after-confirm-payment-on-woocommerce/ Language: en Categories: Wordpress Published: 2020-02-26 You need start any action only payment is confirmed? If yes, this post will help you. This a simple but very efficient snippet. ## How to create action after payment confirmed on WooCommerce? Snippet to start specific function after payment is confirmed on WooCommerce. In this example, after payment is confirmed set the new value to user. ```php function checkPayment ($order_id) { ///get the order $order = new WC_Order($order_id); $user_id = $order->get_user_id(); ///check for set info update_user_meta( $user_id, 'field_name', 'field_value' ); } add_action( 'woocommerce_payment_complete', 'checkPayment' ); ``` ### Important This Snippet requires the payment method to call woocommerce\_payment\_complete() / $order->payment\_complete(). Most payment methods do this already because is default on woocommerce payment method. Do you like the post? Please, leave your comment! --- # How to add a mask to the Elementor form [FREE] URL: https://eduardovillao.me/elementor-form-mask/ Language: en Categories: Elementor, Wordpress Published: 2020-02-24 Form Masks for Elementor create a custom control in field advanced tab for your customize your fields with masks. This plugin require the Elementor Pro (Form Widget). The masks function with filed types ‘text’ and ‘tel’. ## Add a mask to the Elementor form Just download the plugin in the WordPress directory and install it on your website: [https://wordpress.org/plugins/form-masks-for-elementor/](https://wordpress.org/plugins/form-masks-for-elementor/) See how the plugin works in this video: Do you see how easy it is? Do you like the post? Please, leave your comment! --- # How to center Tabs on Elementor URL: https://eduardovillao.me/center-elementor-tabs/ Language: en Categories: Elementor, Wordpress Published: 2019-11-11 Did you ever need to use the Elementor tabs widget (or Tabs), and needed to center the tabs, but did not find these options in the widget? (unfortunately this option does not even exist in the Widget). Yeah, you’re not the only one. I have received this doubts on social networks and through our consultancy. So, without much fanfare, let’s get the solution! ### How to center the tabs? It’s simple, you’ll need to use CSS code that can be inserted into the Elementor widget itself (if you’re using the PRO version) or the CSS editor for your theme. ```css .elementor-tabs-wrapper { display: flex; justify-content: center; } ``` #### Ready, the result will be this: ![How to center the Tabs on the Elementor](/wp/center-elementor-tabs/centered-tab-1024x196.jpg) I hope that you enjoyed. Do you like the post? Please, leave your comment! --- # How to center the Contact Form 7 submit button URL: https://eduardovillao.me/how-to-center-the-contact-form-7-submit-button/ Language: en Categories: Contact Form 7, Wordpress Published: 2019-11-11 This is a very common question of WordPress users: how to centralize the contact form 7 submit button? It’s easier than you think. I’ll show below. ## How to center the Contact Form 7 submit button To do this is very simple. You just need to create your contact form the way you want it, and add the following code in your CSS editor or theme editor: ```css input.wpcf7-form-control.wpcf7-submit { display: block; margin: 0 auto; } ``` Ready, the result will be this: ![How to center the Contact Form 7 submit button](/wp/how-to-center-the-contact-form-7-submit-button/Bota_o-enviar-centralizado-Contact-Form-7-300x132.jpg) I hope that you enjoyed. Do you like the post? Please, leave your comment! --- # About URL: https://eduardovillao.me/about/ Language: en Published: 2026-02-10 # Nice to meet you, I’m Eduardo Villão! I’m a product-oriented full-stack developer with over a decade of experience building high-impact web applications. I have a strong foundation in WordPress and also work extensively with modern stacks such as React, Next.js, PHP (Symfony, Laravel), and Ruby on Rails (including Hotwire). Beyond the technical side, I focus heavily on the design and architecture of multi-tenant SaaS platforms and on integrating advanced, AI-driven workflows. I originally graduated in Psychology and transitioned into technology in 2014. That background still shapes how I think today: I care deeply about understanding user behavior, reducing friction, and building systems that truly deliver value. Whether I’m designing robust platforms, optimizing experiences with AI, or creating scalable solutions across different stacks, I always start with the problem—not the technology. I spent nine years working as an independent contractor, which strengthened my entrepreneurial mindset. I learned how to attract clients, communicate clearly, deliver consistently, and demonstrate value through metrics and results—not opinions. That mindset guides every technical and product decision I make. Before tech, I competed for 15 years as a professional swimmer at national and international levels. That journey shaped my discipline, resilience, and performance-driven focus—qualities I bring into long-term engineering challenges and product strategy. Today, I specialize in building scalable platforms, integrating and optimizing AI-powered experiences, and helping teams ship better products faster—data-driven, with solid technical execution. My focus is always on cost-benefit, ensuring real impact and sustainable value. If you’re interested in software engineering, AI, product strategy, WordPress, Next.js, Symfony, or building SaaS from scratch, you’ll probably feel right at home here. --- # Cookie Policy URL: https://eduardovillao.me/cookie-policy/ Language: en Published: 2021-09-27 ### About this cookie policy This Cookie Policy explains what cookies are and how we use them, the types of cookies we use i.e, the information we collect using cookies and how that information is used, and how to control the cookie preferences. For further information on how we use, store, and keep your personal data secure, see our Privacy Policy. You can at any time change or withdraw your consent from the Cookie Declaration on our website Learn more about who we are, how you can contact us, and how we process personal data in our Privacy Policy. Your consent applies to the following domains: eduardovillao.me Your current state: No consent given. Manage your consent. ### What are cookies ? Cookies are small text files that are used to store small pieces of information. They are stored on your device when the website is loaded on your browser. These cookies help us make the website function properly, make it more secure, provide better user experience, and understand how the website performs and to analyze what works and where it needs improvement. ### How do we use cookies ? As most of the online services, our website uses first-party and third-party cookies for several purposes. First-party cookies are mostly necessary for the website to function the right way, and they do not collect any of your personally identifiable data. The third-party cookies used on our website are mainly for understanding how the website performs, how you interact with our website, keeping our services secure, providing advertisements that are relevant to you, and all in all providing you with a better and improved user experience and help speed up your future interactions with our website. ### What types of cookies do we use ? Essential: Some cookies are essential for you to be able to experience the full functionality of our site. They allow us to maintain user sessions and prevent any security threats. They do not collect or store any personal information. For example, these cookies allow you to log-in to your account and add products to your basket, and checkout securely. Statistics: These cookies store information like the number of visitors to the website, the number of unique visitors, which pages of the website have been visited, the source of the visit, etc. These data help us understand and analyze how well the website performs and where it needs improvement. Marketing: Our website displays advertisements. These cookies are used to personalize the advertisements that we show to you so that they are meaningful to you. These cookies also help us keep track of the efficiency of these ad campaigns. The information stored in these cookies may also be used by the third-party ad providers to show you ads on other websites on the browser as well. Functional: TThese are the cookies that help certain non-essential functionalities on our website. These functionalities include embedding content like videos or sharing content of the website on social media platforms. Preferences: These cookies help us store your settings and browsing preferences like language preferences so that you have a better and efficient experience on future visits to the website. The below list details the cookies used in our website. Cookie Description cookielawinfo-checkbox-analytics This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". cookielawinfo-checkbox-functional The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". cookielawinfo-checkbox-necessary This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". cookielawinfo-checkbox-others This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. cookielawinfo-checkbox-performance This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". viewed\_cookie\_policy The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. ### How can I control the cookie preferences ? Should you decide to change your preferences later through your browsing session, you can click on the “Privacy & Cookie Policy” tab on your screen. This will display the consent notice again enabling you to change your preferences or withdraw your consent entirely. In addition to this, different browsers provide different methods to block and delete cookies used by websites. You can change the settings of your browser to block/delete the cookies. To find out more about how to manage and delete cookies, visit wikipedia.org, www.allaboutcookies.org. --- # Sobre URL: https://eduardovillao.me/pt-br/sobre/ Language: pt-br Published: 2025-12-11 # Prazer, Eduardo Villão! Sou um desenvolvedor full-stack orientado a produto, com mais de uma década de experiência criando aplicações web de alto impacto. Possuo uma base sólida em WordPress e trabalho também com stacks modernas como React, Next.js, PHP (Symfony, Laravel) e Ruby on Rails (incluindo Hotwire). Além da parte tecnológica, atuo fortemente no design e arquitetura de plataformas SaaS multi-tenant e na integração de fluxos avançados impulsionados por IA. Originalmente sou formado em psicologia, migrei para tecnologia em 2014. Esse background segue moldando minha forma de pensar: valorizo compreender comportamento do usuário, reduzir fricções e construir sistemas que realmente entregam valor. Seja projetando plataformas robustas, otimizando experiências com IA ou criando soluções escaláveis em diferentes stacks. Sempre começo pelo problema, não pela tecnologia. Passei nove anos trabalhando como contratante independente, o que fortaleceu meu mindset empreendedor. Aprendi a atrair clientes, comunicar claramente, entregar com consistência e demonstrar valor através de métricas e resultados, não opiniões. Essa mentalidade orienta cada decisão técnica e de produto que tomo. Antes da tecnologia, competi por 15 anos como nadador profissional em níveis nacional e internacional. Essa trajetória moldou minha disciplina, resiliência e foco em performance, qualidades que carrego para desafios de engenharia e estratégia de produto no longo prazo. Hoje, me especializo em construir plataformas escaláveis, integrar e otimizar experiências baseadas em IA, e ajudar equipes a entregar produtos melhores, mais rápido, orientado a dados, e com execução técnica sólida. Meu foco está sempre no custo-benefício de cada iniciativa, garantindo impacto real e valor sustentável. Se você se interessa por engenharia de software, IA, estratégia de produto, WordPress, Next.js, Symfony ou por construir SaaS do zero, provavelmente vai se sentir em casa aqui. --- # Solutions URL: https://eduardovillao.me/solutions/ (EN) · https://eduardovillao.me/pt-br/solucoes/ (PT) Things I've built to solve real problems. Products, plugins, and open-source tools. Some commercial, some open — all born from real pains observed in practice. ## PRODUCTS - WPFeatureLoop — https://wpfeatureloop.com/: Feedback and roadmap platform for WordPress plugins. Collect votes, prioritize features, and close the loop with users directly from wp-admin. - MatchTube — https://www.matchtube.io/: AI that finds YouTube micro-influencers with high affinity for your product. Automatic channel recommendations from your product or SaaS URL. - Repta — https://repta.com.br/: AI-powered review monitoring for restaurants. Real-time Google and iFood rating alerts on WhatsApp, with AI-suggested replies and sentiment analytics. - Submito — https://www.submito.com.br/: Form builder for Notion, built for Brazil. Validated CPF/CNPJ fields and automatic address lookup from CEP — clean data straight into your Notion database. - FormRoute — https://formroute.dev/: Form backend with a single endpoint. Validates fields, filters spam at the edge, and routes submissions to email, Slack, Sheets, or webhooks — no backend to build. ## WORDPRESS PLUGINS - MyD Delivery — https://wordpress.org/plugins/myd-delivery/: Turns your site into a complete ordering and delivery system. - MyD Delivery Widgets — https://wordpress.org/plugins/myd-delivery-widgets/: Widgets to display your menu, order buttons, and store info. - Order on Mobile for Woo — https://wordpress.org/plugins/woo-order-on-whatsapp/: Sends WooCommerce orders to WhatsApp. Turns checkout into a conversation. - Shipping per Neighborhood — https://wordpress.org/plugins/shipping-per-neighborhood-for-woocommerce/: Delivery rules by neighborhood — pricing and availability per area. - Add Class to Elementor Image — https://wordpress.org/plugins/add-class-to-elementor-image/: Custom CSS classes on Elementor's Image widget, no wrappers. - Form Masks for Elementor — https://wordpress.org/plugins/form-masks-for-elementor/: Input masks for Elementor forms — better validation and UX. - Extensions for Elementor Form — https://wordpress.org/plugins/extensions-for-elementor-form/: Advanced validations, fields, and behaviors for Elementor forms. ## OPEN-SOURCE TOOLS - wp-since — https://github.com/eduardovillao/wp-since: Checks plugin compatibility with minimum WordPress core versions — functions, classes, and hooks. - masky.js — https://www.npmjs.com/package/masky-js: Ultra-lightweight input masks. Zero dependencies, framework-agnostic, performance-first. - wp-self-host-updater-checker — https://github.com/eduardovillao/wp-self-host-updater-checker: Lets plugins check for updates from self-hosted repositories, outside WordPress.org. - wp-self-host-updater-generator — https://github.com/eduardovillao/wp-self-host-updater-generator: Generates update manifests for self-hosted plugins — versioning via GitHub or your own servers.