---
title: "Meu starter de Astro ganhou um update grande: o que mudou e por que ele existe"
description: "O esqueleto que uso pra começar todo site em Astro estava parado desde fevereiro. Subi ele pro Astro 7 e trouxe pra dentro tudo que já tinha virado padrão nos projetos em produção: fontes self-hosted, cabeçalhos de segurança, captura de UTM, SEO absoluto. Repositório aberto no GitHub."
published: "2026-08-21"
tags: ["astro", "github", "frontend", "produtividade"]
source: "https://marciotoledo.com/blog/astro-starter-toledo-interactive-update"
---

# Meu starter de Astro ganhou um update grande: o que mudou e por que ele existe

Toda vez que eu começava um site novo em Astro, perdia as mesmas quatro horas. Configurar o Prettier, montar o componente de SEO, lembrar que a URL da imagem de Open Graph precisa ser absoluta senão o WhatsApp não mostra nada, copiar o `_headers` do projeto anterior, descobrir de novo que o pnpm 10 bloqueia o script de instalação do `sharp`. Nada disso é difícil. É só repetitivo o suficiente pra irritar, e sutil o suficiente pra eu errar um detalhe e só perceber depois do deploy.

Foi por isso que criei o [astro-starter](https://github.com/toledox82/astro-starter). Não é o starter oficial do Astro nem tenta ser: é o esqueleto do que sobrou depois de alguns sites em produção, com as decisões que eu já tinha tomado três ou quatro vezes deixando de ser decisão e virando ponto de partida.

Só que ele estava parado desde fevereiro. Enquanto isso, cinco projetos que desenvolvi (este site aqui, [AS Locação](https://aslocacao.com.br), [OperAqui](https://operaqui.com), [Missão China Health](https://missaochinahealth.com.br) e o [Toin.app](https://toin.app)) foram andando e acumulando melhorias que nunca voltavam pro template. Chegou num ponto em que o starter estava atrás dos projetos que ele deveria iniciar, o que é meio o oposto do objetivo. Semana passada sentei e fiz o caminho de volta.

## O que é um starter, e o que ele não é

Um starter é um repositório que você clona pra não começar do zero. A diferença entre ele e um boilerplate genérico é o escopo: boilerplate tenta servir todo mundo e por isso não decide nada, enquanto um starter pessoal decide tudo, e é exatamente isso que economiza tempo.

Este aqui não é um tema, não tem componente de UI pronto e não tem página de exemplo pra você apagar. Ele é infraestrutura: SEO, fontes, cabeçalhos, cache, formatação, analytics e deploy já resolvidos, com o conteúdo em branco esperando o projeto. A stack é Astro 7, Tailwind CSS 4, Alpine.js 3 e Lenis, com saída estática e deploy em Cloudflare Workers.

## Onde um starter desatualizado estraga a festa

O problema de um template parado não é ficar velho: é ficar **enganosamente pronto**. Você clona confiando que está começando com a base atual, e na verdade começa seis meses atrás. Pior: as correções que você fez nos projetos reais nesse meio-tempo não estão ali, então você repete os mesmos bugs que já tinha resolvido.

No meu caso, o starter tinha um `tailwind.config.mjs` que o Tailwind 4 não usa mais (a configuração migrou pro CSS, via `@theme`), carregava fonte por `<link>` pro `fonts.googleapis.com`, e o `Seo.astro` montava a URL da imagem de Open Graph em caminho relativo. Esse último é o tipo de coisa que passa despercebida: o site funciona, o Lighthouse não reclama, e só quem manda o link no WhatsApp descobre que o card vem sem imagem, porque o WhatsApp e o Facebook não resolvem caminho relativo.

## O que mudou nesse update

| Área | Antes | Agora |
| :--- | :--- | :--- |
| Astro | 5.16 | 7.2.4 |
| Tailwind | 4 com `tailwind.config.mjs` | 4.3.3, configurado por CSS no `@theme` |
| Alpine | `@astrojs/alpinejs` 0.4 | 1.0 |
| Fontes | `<link>` pro Google Fonts | `astro:fonts`, self-hosted no build |
| Segurança | nada | `_headers` com HSTS, nosniff, X-Frame-Options |
| SEO | canonical e OG relativas | absolutas, tipadas |
| Analytics | GA4 solto | `Analytics.astro` com captura de UTM |
| Node | não declarado | `engines >=22.12` e `.nvmrc` |

Abaixo, o que está por trás de cada linha que merece explicação.

### Fontes self-hosted via `astro:fonts`

Essa é a que mais muda o resultado percebido. Em vez do `<link>` pro `fonts.googleapis.com`, o Astro baixa os arquivos no build, gera o `@font-face` com métricas de fallback e emite o `preload` sozinho.

Duas vantagens concretas: um round-trip a menos em outro domínio antes de conseguir pintar o texto, e o fim do pulo de layout na troca da fonte de sistema pela real, que as métricas de fallback resolvem. Já tinha escrito sobre [self-host de fontes com Fontsource](/blog/google-fonts-self-host-fontsource) quando fiz isso neste site; a diferença é que agora o `astro:fonts` faz o trabalho nativamente, sem dependência extra.

Um detalhe que custa banda se passar batido: declarar sempre `styles: ['normal']`. Sem isso, fontes como o Inter trazem as variantes itálicas com o mesmo `unicode-range`, e o navegador baixa as duas famílias.

### `public/_headers` com o raciocínio junto

Esse veio quase inteiro do toin-website. Traz HSTS, `Referrer-Policy`, `X-Content-Type-Options`, `X-Frame-Options`, `Permissions-Policy` e `immutable` no `/_astro/*`.

O que eu acho que faz diferença aqui não são os cabeçalhos em si, que são padrão de mercado: é que cada bloco explica **por que** está ali, incluindo a ausência deliberada de CSP, que é uma decisão consciente e não um esquecimento. E documenta as duas armadilhas do formato: a indentação dos cabeçalhos é obrigatória (sem ela a linha vira um padrão de rota e o header some sem erro nenhum), e `/*` não cobre o que já está em cache.

No caso do HSTS tem um aviso explícito sobre o token `preload`, que só torna o domínio elegível à lista embutida nos navegadores. Uma vez aceito, sair leva meses, e nesse meio-tempo qualquer subdomínio que só saiba HTTP fica inacessível.

### Captura de origem no `Analytics.astro`

Esse resolve um problema que aparece em todo projeto de cliente. O componente captura UTMs e click-ids na primeira página da visita, guarda na sessão e injeta como campos ocultos em todo `<form>` da página. O lead chega no destino sabendo de onde veio.

A captura precisa acontecer na entrada porque a pessoa quase sempre navega antes de preencher o formulário: sem o `sessionStorage`, os parâmetros se perderiam no primeiro clique. O GA4 só sobe se `analytics.ga4` estiver preenchido no `site.json`, então o starter não fica disparando script de rastreamento em projeto que ainda nem tem conta.

### SEO absoluto e `robots.txt` gerado no build

O `Seo.astro` foi tipado e agora resolve `og:image` e canonical em absoluto. O `robots.txt` passou a ser gerado no build a partir do `site` do config, em vez de ser um arquivo estático em `public/`, que num template aponta pro domínio errado no primeiro clone e ninguém percebe.

O sitemap ganhou um filtro `EXCLUDED`: página marcada com `noindex` fica fora do sitemap, porque sinalizar as duas coisas ao mesmo tempo é contraditório pros buscadores.

### O resto

Coisas menores que somam: `.gitattributes` normalizando para LF (senão o Windows e o Prettier brigam a cada `pnpm format`), `.nvmrc`, `pnpm-workspace.yaml` liberando os scripts de instalação do `sharp`, `esbuild` e `workerd` que o pnpm 10+ bloqueia por padrão, página `404.astro`, `env.d.ts`, um `anchor-scroll.ts` que desconta o header fixo, e `wrangler.jsonc` descrevendo o Worker de Static Assets.

O README foi reescrito inteiro em português, com checklist de projeto novo e uma seção de decisões não-óbvias. O `CLAUDE.md` descreve o repositório como esqueleto, pra quem usa Claude Code não sair inventando estrutura que já existe.

## Decisões que documentei de propósito

Tem um bloco no README que eu gosto mais do que o código: as decisões que parecem erro pra quem chega. Um starter sem elas vira um lugar onde as pessoas "consertam" coisas que estavam certas.

- **Não existe `tailwind.config.mjs`, e não deve existir.** O Tailwind 4 configura por CSS. Os tokens vivem no `@theme` do `global.css`.
- **`trailingSlash: 'always'`.** O que importa é escolher um e manter. Já escrevi sobre [o 308 do Cloudflare e as combinações de trailing slash no Astro](/blog/trailing-slash-astro-cloudflare-pages); o starter usa `always` porque é o default seguro, mas a escolha está documentada como escolha.
- **`scroll-behavior: smooth` está fora do CSS de propósito.** O Lenis já anima a rolagem e o `anchor-scroll.ts` desconta o header. Ligar os dois faz a rolagem brigar consigo mesma.
- **Comentários longos no markup usam `{/* */}`**, nunca ``. Os primeiros o Astro remove no build, os segundos vão parar no HTML do visitante.

> **TIP**
>
> Se você está começando um site em Astro agora, a parte que eu faria primeiro é o `_headers` e o SEO absoluto. São as duas que não dão erro visível em dev e só aparecem depois que o site está no ar, quando corrigir custa um deploy e uma explicação pro cliente.

## Como usar

```bash
git clone https://github.com/toledox82/astro-starter.git meu-projeto
cd meu-projeto
pnpm install
pnpm dev
```

Depois disso, o checklist do README tem oito itens: nome no `package.json` e no `wrangler.jsonc`, `SITE` no `astro.config.mjs`, `src/data/site.json`, paleta no `global.css`, as fontes, os favicons, a imagem de OG e o `compatibility_date`.

Requer Node 22.12 ou superior e pnpm. O pnpm não é preferência minha aqui, é requisito: o `pnpm-workspace.yaml` é o que libera os scripts de instalação bloqueados por padrão.

## Resumo

**O que fazer:** clonar, rodar o checklist de oito itens do README, e confiar que SEO, fontes, cabeçalhos e formatação já estão resolvidos.

**O que não fazer:** recriar o `tailwind.config.mjs`, ligar `scroll-behavior: smooth`, marcar uma página como `noindex` sem acrescentar o caminho ao `EXCLUDED`, ou trocar as URLs absolutas do Open Graph por relativas.

**O que mudou nesse update:** Astro 5.16 pro 7.2.4, fontes self-hosted, cabeçalhos de segurança documentados, captura de UTM, SEO absoluto e uma camada de infraestrutura que veio dos projetos em produção de volta pro template.

> **NOTE**
>
> Esse starter é opinativo por construção. As decisões dele são as minhas, calibradas pros projetos que eu toco: site institucional, landing page, site de conteúdo, tudo estático e na Cloudflare. Se o seu contexto for outro (SSR pesado, app com autenticação, e-commerce), várias escolhas aqui não vão servir, e tudo bem.

## Testa e me conta

O repositório está aberto: **[github.com/toledox82/astro-starter](https://github.com/toledox82/astro-starter)**

Se você trabalha com Astro, clona e usa no próximo projeto. E se topar contribuir, é bem-vindo de verdade:

- **Achou um bug ou um detalhe desatualizado?** [Abre uma issue](https://github.com/toledox82/astro-starter/issues). O update deste mês nasceu de coisas que eu mesmo fui anotando ao longo dos projetos, então a lista de melhorias sempre tem espaço.
- **Discorda de alguma decisão?** Melhor ainda. As decisões do README são as que fizeram sentido pra mim; se você tem um argumento melhor pra alguma delas, quero ouvir.
- **Tem algo que sempre repete nos seus projetos e não está ali?** Manda. É exatamente assim que esse repositório cresce.
- **Se ele te poupar uma tarde, deixa uma estrela no GitHub.** ⭐ É o que me diz que vale a pena continuar mantendo o repositório público e atualizado.

E se o starter ou este post te ajudaram de alguma forma, seja economizando algumas horas de setup ou passando um detalhe que você não conhecia, você pode retribuir [me pagando um café ☕](https://donate.stripe.com/9AQ7sCbcPg9963e5kl). Não é obrigatório e nada aqui fica atrás de paywall: manter o repositório aberto e escrever sobre ele é uma escolha minha. Mas ajuda, e é o que sustenta o tempo que vai nisso. No rodapé do site tem outras formas de colaborar sem gastar nada, como usar meus [links de indicação](/links) ao abrir conta em serviços que você já ia contratar de qualquer jeito.
