Hello World
Como construí este blog do zero com Astro, TypeScript e Tailwind CSS — arquitetura, decisões técnicas e o fluxo de desenvolvimento.
Hello World
Todo blog de dev começa com um “Hello World”. Este não seria diferente.
Este é o primeiro post oficial, e nada mais justo do que escrever sobre como o próprio blog foi construído: as decisões técnicas, a arquitetura, o que funcionou e o que eu teria feito diferente.
Por que um blog?
Há um tempo venho sentindo que não documentava o suficiente o que aprendo. Livros, projetos, experimentos… tudo ficava na minha cabeça ou em arquivos avulsos. Um blog resolve isso. Não porque alguém necessariamente vai ler, mas porque escrever sobre algo é a melhor forma de descobrir se você realmente entendeu.
A stack
A escolha foi clara desde o início: queria algo estático, simples de manter e sem banco de dados. Nada de CMS, nada de painel administrativo. Só arquivos Markdown no repositório.
Astro foi a escolha natural para o framework. Ele foi feito exatamente para esse caso de uso: sites com pouco ou nenhum JavaScript no cliente, geração estática e suporte nativo a Markdown com Content Collections.
TypeScript para tudo. Sem any, sem atalhos. O schema do frontmatter é validado com Zod em tempo de build. Se um campo obrigatório estiver faltando, o build quebra. Isso evita posts malformados chegarem em produção.
Tailwind CSS para estilização. Sem CSS modules, sem styled-components. Só utilitários. O resultado é um bundle pequeno e componentes fáceis de escanear.
Arquitetura
A estrutura do projeto ficou assim:
tree -I 'node_modules|.astro'
.
├── astro.config.mjs
├── content
│ └── posts
│ └── hello-world.md
├── package.json
├── package-lock.json
├── public
├── README.md
├── src
│ ├── components
│ │ ├── Footer.astro
│ │ ├── Header.astro
│ │ ├── MarkdownContent.astro
│ │ ├── PostCard.astro
│ │ └── ThemeToggle.astro
│ ├── content.config.ts
│ ├── layouts
│ │ ├── BaseLayout.astro
│ │ └── PostLayout.astro
│ ├── lib
│ │ ├── markdown.ts
│ │ ├── posts.ts
│ │ └── seo.ts
│ ├── pages
│ │ ├── 404.astro
│ │ ├── index.astro
│ │ └── posts
│ │ └── [slug].astro
│ ├── styles
│ │ └── global.css
│ └── types
│ └── post.ts
├── tailwind.config.mjs
├── tsconfig.json
└── vercel.json
12 directories, 24 files
Os posts vivem fora do src/, dentro de content/posts/. O Astro lê essa pasta automaticamente via Content Collections. Nenhum import manual, nenhuma rota registrada na mão. Crio um arquivo .md, salvo, e ele aparece.
A lógica de leitura dos posts está isolada em src/lib/posts.ts. Essa função filtra posts não publicados, ordena por data e é reutilizada em todas as páginas que precisam listar posts.
Roteamento
Duas rotas principais:
/lista todos os posts publicados/posts/[slug]renderiza o post completo
O slug vem diretamente do nome do arquivo. hello-world.md vira /posts/hello-world. Sem configuração extra.
Dark mode
O dark mode é controlado por classe CSS no elemento html, com preferência salva no localStorage. Um script inline no <head> aplica o tema antes do primeiro render para evitar flash de tema errado.
Deploy
O blog é hospedado na Vercel. O deploy é automatizado via GitHub Actions: qualquer push para main que altere um arquivo dentro de content/posts/ dispara um webhook que aciona a Vercel para reconstruir o site.
Escrevo o post, faço o commit, faço o push. Em alguns segundos está no ar.
O que ficou de fora
Intencionalmente não implementei:
- Paginação - por enquanto não preciso
- Busca - idem
- RSS - está nos planos
- Syntax highlighting - vem em breve
A ideia foi lançar simples e ir adicionando conforme a necessidade real aparecer. Overengineering em blog pessoal é uma armadilha fácil.
Considerações finais
O projeto inteiro tem menos de 25 arquivos. O build leva poucos segundos. Adicionar um post novo leva menos de um minuto.
É exatamente o que eu queria.
Se tiver curiosidade sobre algum detalhe específico da implementação, os próximos posts vão aprofundar cada parte: Content Collections, o pipeline de deploy, dark mode sem flash, e mais.
Até o próximo.