Lab parte 1
Construindo este site em Elixir, parte 1
4 min de leitura
Por que Elixir, por que Docker e como os posts acabam compilados dentro do app. As primeiras anotações da construção do sh4.tech.
- Tema
- Elixir · Phoenix
- Stack
- Elixir 1.18, OTP 28, Phoenix 1.8, NimblePublisher, Docker
- Também em
- English
- 1.18Elixir
- 1.8Phoenix
- 0bancos de dados
- 2idiomas
Por que Elixir
Eu montaria este site numa tarde com as ferramentas que uso todo dia. Escolhi Elixir mesmo assim, por três motivos. Primeiro, quero aprender, e aprendo melhor quando tem algo de verdade para entregar no fim. Segundo, a BEAM. Por dez anos cuidei de sistemas em que milhares de dispositivos falam com os servidores ao mesmo tempo, e o Elixir foi feito para esse tipo de carga: muitos processos pequenos, cada um barato, supervisionado, reiniciado quando cai. Um blog não precisa de nada disso. Mas é um bom lugar para aprender o modelo antes de precisar dele num trabalho de verdade.
Terceiro, o próprio site é um case. Quando estiver no ar, a página que você está lendo sai de um app Phoenix que eu escrevi enquanto aprendia a linguagem.
Docker, porque o apt é velho
O primeiro problema apareceu antes de qualquer código. apt install elixir no Ubuntu traz o Elixir 1.14, e a versão do Phoenix que eu queria pede uma mais nova. Não quis brigar com gerenciador de versão na minha máquina, então tudo roda no Docker: a imagem oficial elixir:1.18 com OTP 28, a pasta do projeto montada como volume, porta 4000 aberta. docker compose up e o site está lá. Nada instalado na máquina, fora o próprio Docker.
Todo comando do mix passa pelo container:
docker compose exec -T web mix test
docker compose exec -T web mix compile --warnings-as-errorsPhoenix 1.8, sem Ecto
Gerei o projeto sem Ecto, então não tem banco de dados. Os posts são arquivos Markdown no repositório, uma pasta por idioma: priv/posts/en/ e priv/posts/pt/. Sem login, sem comentários, sem painel de admin. Quando quero mudar um post, edito o arquivo e faço commit, igual a qualquer outra mudança de código.
Os posts são compilados dentro do app
É a parte de que eu mais gosto. O NimblePublisher lê todos os arquivos Markdown na compilação, converte para HTML e guarda a lista dentro do módulo compilado. Em produção não tem leitura de disco nem consulta para servir uma página. É uma função devolvendo dados que já estão na memória.
Para cada arquivo ele chama uma função build/3 que eu escrevo. O caminho do arquivo traz o idioma e a data, e o frontmatter fica curto:
priv/posts/pt/2026-10-22-codecs-clickhouse-por-coluna.md
^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
idioma data slugA caixa tracejada da direita ainda não existe. Falo dela no fim.
Dois idiomas na URL
Toda página existe em /en/... e /pt/.... Quem valida o idioma é um plug: uma função pequena que recebe a conexão e devolve a conexão, talvez modificada. Em Elixir isso são duas cláusulas da mesma função, e o runtime escolhe a que casa.
def call(%Plug.Conn{path_params: %{"locale" => locale}} = conn, _opts)
when locale in @locales do
Gettext.put_locale(Sh4Web.Gettext, locale)
conn
|> assign(:locale, locale)
|> assign(:alternates, Map.new(@locales, &{&1, swap_locale(conn.request_path, &1)}))
end
def call(conn, _opts) do
raise Phoenix.Router.NoRouteError, conn: conn, router: Sh4Web.Router
end
Se o primeiro pedaço do caminho é en ou pt, roda a primeira cláusula. Qualquer outra coisa cai na segunda e vira 404, igual a uma rota que não existe. Rótulos curtos como "Artigos" passam pelo Gettext. Os textos longos da home são um mapa por idioma num módulo só, porque é mais fácil revisar um parágrafo inteiro em português ao lado do inglês do que caçar pedaços num arquivo .po.
Design: três propostas às cegas, depois a de verdade
Pedi três propostas de design sem o briefing completo: uma de paddock, um caderno de campo e um estúdio. Às cegas de propósito, para ver o que sai quando ninguém ainda sabe o que eu quero. Depois veio uma quarta versão com o posicionamento real, e é essa que está no app hoje.
Duas coisas da rodada às cegas voltaram depois. A régua logarítmica da home vem da proposta do estúdio. E este Lab, com o papel quadriculado e as notas na margem, vem do caderno.
Próximos passos
- Busca e filtro por tag com LiveView, sem recarregar a página. É aí que processos e estado começam a importar.
- Deploy: uma release do Elixir num container no meu próprio servidor, atrás do nginx que já roda lá, com Let's Encrypt. Primeiro num subdomínio de teste, depois o DNS.
A parte 2 vai ser sobre LiveView, provavelmente começando pela caixa de busca.