Aprender
Desenvolvimento e produção
O que trilha dev faz por baixo, como publicar um binário e como configurar por variáveis de ambiente.
trilha dev#
O comando escuta em :3000 e roda o seu app em uma porta interna, encaminhando as requisições. A cada arquivo salvo:
- regenera
trilha_gen.gose a árvore deapp/mudou; - recompila com
go build; - sobe o processo novo, espera ele responder e só então derruba o antigo;
- avisa o navegador por um evento (SSE), que recarrega.
Mudanças só em public/ pulam os passos 1 a 3. Um erro de compilação vira uma página com a saída do go build; corrija e ela some. O processo do app roda com TRILHA_ENV=dev, o que liga stack traces nas páginas de erro e desliga o cache de estáticos.
O inspetor de rotas#
Enquanto o trilha dev roda, http://localhost:3000/_trilha/routes responde com o mapa do app: cada rota na ordem em que o roteador decide, com o tipo, os métodos, a pasta de origem, os layouts que a embrulham (de fora para dentro) e os middlewares que rodam antes dela — as duas coisas que o trilha routes não mostra, porque são cadeias, não linhas.
A caixa no topo responde à pergunta que costuma levar alguém até lá: digite /blog/ola e a página diz qual padrão atende e quanto vale cada parâmetro. A resposta sai de um http.ServeMux montado com os seus padrões, então é o roteador decidindo, não uma segunda implementação das regras de precedência.
Quem serve a página é o supervisor do dev, não o seu app: ela não está no binário que o trilha build produz, e a mesma URL em produção é um 404 como outro qualquer. Não há o que desligar antes de publicar.
trilha build#
trilha build # → bin/agenda
TRILHA_ENV=prod PORT=8080 ./bin/agendaO binário é estático (CGO_ENABLED=0), tem public/ embutido e não precisa da CLI nem de nenhum arquivo ao lado. Um Dockerfile cabe em quatro linhas:
FROM golang:1.25 AS build
WORKDIR /src
COPY . .
RUN go run github.com/emersonjoe/trilha/cmd/trilha@latest build -o /app
FROM gcr.io/distroless/static
COPY --from=build /app /app
ENV PORT=8080
CMD ["/app"]Variáveis de ambiente#
| Variável | Efeito |
|---|---|
PORT ou ADDR | porta ou endereço de escuta (padrão :3000) |
TRILHA_ENV | dev ou prod (padrão prod) |
TRILHA_BASE_PATH | prefixo de URL quando o app vive em um subcaminho; use c.Base() nos links |
TRILHA_EXPORT | pasta de saída: em vez de servir, exporta o site estático e sai |
TRILHA_DEV_RELOAD | off desliga a injeção do script de recarga em dev (testes de snapshot, comparação de HTML); stack traces e no-cache continuam |
Outras configurações (limite de corpo, logger, CSRF em APIs) ficam em trilha.Config, que o arquivo gerado monta com trilha.ConfigFromEnv().
Inicialização com setup.go#
Abrir um banco, carregar um cache, validar variáveis: tudo isso vai em app/setup.go:
package app
import "github.com/emersonjoe/trilha"
func Setup(a *trilha.App) error {
db, err := sql.Open("pgx", os.Getenv("DATABASE_URL"))
if err != nil {
return err // aborta a subida com a mensagem no terminal
}
trilha.Provide(a, db)
return nil
}A página lê de volta pelo mesmo tipo: db := trilha.Use[*sql.DB](c). Não guarde o pool em variável de pacote — funciona até existir um segundo app no mesmo processo (um hospedeiro que monta dois, um teste que constrói outro), e aí os dois passam a dividir o mesmo. Values() continua ali para cola por nome. Veja Dependências.
trilha export#
Se todas as páginas são estáticas (um blog, uma documentação), exporte HTML e publique em qualquer hospedagem:
trilha export -o out --base /agendaPáginas com parâmetro entram quando Setup as declara com a.AddExportPath("/eventos/x"). Páginas que respondem um redirecionamento para o próprio site viram um pequeno HTML apontando para o destino. O site que você está lendo foi gerado assim.
Um caminho exportado cujo último segmento tem ponto é gravado como esse arquivo, e não como pasta com um index.html dentro. É assim que uma rota produz out/llms.txt ou out/feed.xml:
func Setup(a *trilha.App) error {
a.AddExportPath("/llms.txt", "/feed.xml")
return nil
}É a mesma regra que a varredura usa para pasta com ponto no nome (app/llms.txt/route.go responde /llms.txt), então a rota e o arquivo exportado combinam sem inventar uma segunda convenção.
Assets e cache#
Publicar HTML novo com CSS velho é o bug que ninguém consegue reproduzir dez minutos depois. A causa é sempre a mesma: o endereço do arquivo não mudou quando o conteúdo mudou, e alguma camada de cache — o navegador, um CDN, o GitHub Pages — ainda tem a versão antiga.
O Asset põe o hash do conteúdo na URL:
h.Link(h.Rel("stylesheet"), h.Href(c.Asset("/style.css"))) // /style.css?v=8f3a1c92Com isso, um cache longo passa a ser seguro:
cfg.StaticCacheControl = "public, max-age=31536000, immutable"Quem pede a URL versionada certa recebe o cache de um ano; quem pede /style.css sem versão cai na regra normal. Em dev nada é imutável e o hash acompanha o arquivo, então salvar o CSS e atualizar a página basta. O trilha export não precisa de nenhuma opção: o HTML exportado sai com as mesmas URLs, porque é o mesmo layout que o gera.
trilha audit avisa quando encontra immutable num projeto que não usa Asset — é a combinação que congela um arquivo por um ano no endereço errado.
Segurança por padrão#
Cabeçalhos X-Content-Type-Options: nosniff, X-Frame-Options: DENY e Referrer-Policy em toda resposta; corpo limitado; CSRF em formulários; estáticos sem path traversal; logs com método, caminho, status e duração, nunca com corpo ou cookies. Erros em produção mostram uma página opaca e vão para o log com o request_id que aparece no cabeçalho X-Request-ID.
Desafio#
Publique a agenda em um servidor com systemd e faça o serviço reiniciar sozinho se cair.
Mostrar solução
[Unit]
Description=agenda
After=network.target
[Service]
ExecStart=/opt/agenda/bin/agenda
Environment=PORT=8080 TRILHA_ENV=prod
Restart=always
User=agenda
[Install]
WantedBy=multi-user.target