Do zero ao ar num domingo
Eu queria um lugar simples para publicar texto técnico em português e em inglês. Não um perfil e não uma vitrine. Um site em que o conteúdo é um arquivo Markdown, o build é estático, e publicar é um commit.
Este aqui saiu do zero e foi para o ar num domingo. O domínio já existia. O resto, não.
O que eu queria
Duas línguas desde o primeiro dia. Português como padrão, inglês no mesmo texto quando fizer sentido. Sem CMS, sem banco, sem bundle de JavaScript no navegador. Se eu abrir o repositório daqui a um ano, tem que dar para achar onde entra um post novo sem caçar configuração.
A home não precisava me apresentar. Quem quiser o perfil acha o GitHub. Aqui é para post.
A stack, do jeito que está no repositório
O site é Astro 7.4.4, com output: "static". TypeScript 7.0.2. O @astrojs/mdx 8.1.2 está instalado para o dia em que um texto precisar de MDX. Este aqui é Markdown puro. RSS é @astrojs/rss 4.0.19, um feed por língua. Sitemap é @astrojs/sitemap 3.7.4. O destaque de código é o Shiki, no tema vitesse-dark. O ícone do GitHub vem do simple-icons 16.36.0. As fontes são locais, via Fontsource: Newsreader, Source Sans 3 e IBM Plex Mono. O Node pedido no package.json é 22.23.4 ou mais novo.
O i18n é o do próprio Astro. Português é o defaultLocale, os dois idiomas levam prefixo, e a raiz redireciona para /pt/. A parte de i18n do astro.config.ts:
export default defineConfig({
site: "https://douglasrubim.com",
output: "static",
trailingSlash: "always",
i18n: {
defaultLocale: "pt",
locales: ["pt", "en"],
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
},
});
redirectToDefaultLocale fica desligado de propósito. O redirect de / é um 301 em src/pages/index.astro e a mesma regra no vercel.json. Site estático não roda middleware. Se o redirect só existisse no middleware, a Vercel serviria a página raiz parada.
O blog é uma content collection. Cada post declara title, description, date, tags, lang e, quando existe tradução, o mesmo translationKey nos dois arquivos. O lang tem que ser igual à pasta (pt ou en). Se não for, o build quebra. draft: true aparece no npm run dev e fica de fora do build de produção, do índice, das tags, do RSS e do sitemap.
O npm run build não emite arquivo .js. O menu do celular é um <details>, sem script. O único script da página é o do tema, inline e curto, para o claro e o escuro não piscarem na carga. A scanline e o glitch do título, que só acontece no hover, param quando o sistema pede prefers-reduced-motion.
Um agente, e eu devolvendo o que estava errado
Eu não sentei para digitar o site linha a linha. Pedi para um agente de código na nuvem do Cursor construir, conduzido pelo meu assistente. Eu olhava o que saía e devolvia o que não servia.
A primeira versão compilava. Visualmente era uma coluna de texto. Eu achei péssima: simples demais, cara de template. Disse isso. Veio um redesign, com hero grande, cards e ícones.
Os ícones estavam inventados. Dava para ver na hora. Marca desenhada na pressa não parece marca, mesmo com a cor “certa”. A correção foi usar o simple-icons nos logos conhecidos. Onde eu já tinha um arquivo oficial de ícone, o agente baixou esse arquivo para o repositório, em vez de improvisar um desenho e em vez de apontar para uma URL de fora.
Depois eu pedi um cyberpunk que ainda desse para ler. Fundo navy quase preto, ciano e magenta, grade fina, scanline baixa, cantos em colchete nos cards. O glitch fica só no hover do título e respeita redução de movimento. O modo claro não é um invertido preguiçoso: é a mesma ideia de dia, com o neon mais escuro, senão o texto não segura contraste.
A barra de cima no celular estava gigante. Quebrava em várias linhas, com pílula demais. Pedi uma linha só, no máximo uns 56px: nome de um lado, idioma e tema do outro, links num menu. O menu é <details> e <summary>. Sem biblioteca e sem estado em JavaScript. No desktop a barra ficou como estava.
Por último, a home. Ela tinha virado um “sobre mim”, repetindo o que já está no GitHub. Tirei o nome enorme e as seções de apresentação. Ficou uma frase e a lista de posts. Se passar de oito, aparece o link para o índice.
O que prestou nessa troca:
- Feedback específico. “Tá feio” não orienta. “A topbar está gigante, no mobile tem que ser uma linha” orienta.
- Screenshot em cada passo. Texto descrevendo layout mente com facilidade. A imagem não.
- Logo oficial ou nenhum. Ícone inventado parece amador no mesmo dia.
O agente acelera o caminho entre uma decisão e um build. Quem olha a tela e diz o que fica sou eu.
Vercel, e depois a Cloudflare
O deploy é a Vercel, no preset do Astro. O comando é npm run build, a pasta de saída é dist, e não tem variável de ambiente. Push na main publica. A primeira versão foi assim para um domínio da própria Vercel, antes do domínio definitivo apontar para cá.
douglasrubim.com já estava na Cloudflare. Uma Redirect Rule mandava quem chegava para o meu GitHub. Eu não apaguei essa regra. Desliguei. Se eu precisar lembrar o que ela fazia, ela continua lá, inativa.
No DNS, os dois registros ficaram em DNS only, a nuvem cinza. Com o proxy laranja a Cloudflare termina o TLS na frente, e a Vercel não emite o certificado do domínio do jeito direto. Na nuvem cinza, a Vercel enxerga o domínio e cuida do HTTPS.
Type Name Content Proxy
A @ 76.76.21.21 DNS only
CNAME www cname.vercel-dns.com DNS only
O www redireciona para o apex. Com ou sem www, a barra de endereço termina em https://douglasrubim.com.
Como entra o próximo texto
Um arquivo Markdown na pasta da língua. Este post é o par src/content/blog/pt/do-zero-ao-ar-num-domingo.md e src/content/blog/en/from-zero-to-live-on-a-sunday.md. Os dois carregam translationKey: sunday-to-live. O slug de cada um pode ser diferente. A página mostra o link “Ler em inglês”, e o hreflang aponta para a URL de verdade, não para um caminho copiado.
O frontmatter que o schema aceita:
---
title: "Título"
description: "Uma ou duas frases. Vão para o índice, a meta e o RSS."
date: 2026-11-01
tags:
- astro
lang: pt
translationKey: sunday-to-live
draft: false
---
A data é YYYY-MM-DD. Cada tag ganha página em /pt/blog/tags/<slug>/ (e o equivalente em /en/). O slug da tag é o texto em minúsculas, sem acento.
npm run dev mostra o arquivo na hora. Produção é commit na main. O feed de cada língua fica em /pt/rss.xml e /en/rss.xml. O sitemap sai em /sitemap-index.xml.
Era isso que eu queria de um domingo: um repositório pequeno, um agente para encurtar o caminho, e um domínio que abre o site em vez de desviar para outro lugar.