Pular para o conteúdo
Solerte

Ficha técnica

A oficina, com a porta aberta.

A Solerte é nova. Não temos logotipo de cliente para exibir nem case para contar, e inventar os dois seria o pior começo possível. O que temos é este site — construído com o mesmo rigor que aplicamos em software pelo qual alguém paga.

Esta página é a ficha técnica dele. Cada linha é um número que nós medimos, com o comando ao lado para você medir de novo. Onde uma escolha teve custo, o custo está escrito. Medições de 2 de setembro de 2026, contra o build de produção.

O que chega ao navegador

O que o navegador de quem visita realmente precisa baixar, medido arquivo por arquivo contra o build de produção.

Primeira visita à home

312 KiBtransferidos no total, em 31 requisições

É o número que o navegador realmente baixou, medido pelo Lighthouse: HTML, CSS, JavaScript e fontes somados. As 31 requisições foram todas para o mesmo domínio — nenhuma para servidor de terceiro.

Abrindo: 574,2 KB de JavaScript bruto que viram 178,1 KB em gzip e 154,2 KB em Brotli, que é o que a borda serve; 5,9 KB de CSS, folha única para o site inteiro; 83,0 KB em 2 arquivos de fonte, servidos do próprio domínio.

O que isso custou. A maior parte desse peso é o React 19 e o Next 16 — o piso da ferramenta, não código nosso, e não sai sem trocar de ferramenta. O que é escolha nossa é o que não está ali: nenhuma biblioteca de componentes, nenhum carrossel, nenhum gerenciador de etiquetas e nenhum script de terceiro nesta página. O site inteiro tem um único terceiro — o antispam —, e ele só carrega nas duas páginas com formulário. Se um dia entrar outro, ele aparece aqui como aumento.
npx next build && npx next start -p 3100
node scripts/medir.mjs --url http://localhost:3100

Rotas que não baixam nada além do núcleo

7 de 10medidas; o núcleo são 9 arquivos, 152,2 KB em Brotli

Nove arquivos de JavaScript são comuns a todas as rotas medidas. Baixados uma vez, valem para o site inteiro — inclusive para esta página, que não soma nem um arquivo a eles. Na prática: depois da primeira página, navegar pelo site não custa JavaScript novo.

Três rotas somam algo, e cada uma tem motivo escrito: /contato e /avaliacoes somam 1 arquivo de cerca de 5 KB comprimidos, que é o formulário; a home soma 1 arquivo de 2,1 KB. Código interativo só é entregue a quem abre a página que tem interação.

node scripts/medir.mjs --url http://localhost:3100

Arquivos de imagem no projeto

9imagens raster: capturas de tela dos produtos e ícones da marca; nenhuma foto de banco

O resto é vetor: a marca e o ícone, desenhados à mão em SVG, e as telas de demo dos cases, que são HTML e CSS — não imagem. Os ícones PNG saem do próprio SVG por scripts/gerar-icones.mjs, e as capturas entram por scripts/importar-capturas.mjs, a partir dos repositórios dos produtos.

O efeito é medido: deslocamento de layout igual a zero no Lighthouse — nada chega atrasado para empurrar o texto que a pessoa já estava lendo. Coluna de largura e altura que existe só para segurar o layout parado.

O que isso custou. Nada de foto de equipe nem de banco de imagens: uma foto comprada só diz que compramos uma foto. O que existe são as capturas de tela dos produtos, que mostram dados fictícios e são rotuladas como demonstração em cada página, e os ícones da marca. Cada imagem declara largura e altura, então o espaço é reservado antes de o arquivo chegar.
node scripts/medir.mjs --sem-rede

Páginas prontas antes da visita

27rotas geradas no build — e o número sobe quando você publica um projeto

O aplicativo declara 27 pontos de entrada e o build gera 27 páginas. Todas as públicas são HTML pronto, servido da borda: as páginas do site, os ícones, o sitemap e o robots.

Este é o único número desta página que se move sozinho, e vale dizer por quê em vez de deixar a armadilha montada: cada projeto publicado no painel vira uma página gerada a mais. Era 26 até 8 de setembro de 2026, quando um nono projeto entrou no ar. O número certo é sempre a saída do comando acima — não o que está escrito aqui.

Sete executam algo por requisição, e é exatamente onde tem que ser: as três rotas que recebem formulário e as quatro telas do painel administrativo, que têm sessão. Página com login não pode ser cacheada — servir a resposta de uma sessão para outra pessoa é o defeito que ninguém percebe até acontecer.

O que isso significa quando algo dá errado: se o banco cair, o site continua no ar e indexável, porque o portfólio tem uma cópia versionada como reserva. Só a escrita para — e ela recusa em vez de fingir que recebeu.

node scripts/medir.mjs --sem-rede

O que o site recusa

Medido na resposta HTTP e no SQL versionado — não lido do arquivo de configuração, que é onde a intenção mora, não o resultado.

Cabeçalhos de segurança servidos

7 de 7conferidos na resposta real

Política de segurança de conteúdo com 12 diretivas, HSTS de dois anos, X-Frame-Options: DENY, nosniff, política de referência, isolamento de janela e política de permissões negando câmera, microfone, geolocalização e mais cinco. O X-Powered-By foi removido: o site não anuncia com o que foi feito.

A política autoriza exatamente dois domínios de terceiro, e vale dizer quais: o Turnstile da Cloudflare, que é o antispam e só carrega nas duas páginas com formulário, e o Supabase, que é o banco. As fontes não entram na lista porque são servidas do próprio domínio — um terceiro a menos no caminho crítico e uma transferência de IP a menos para declarar na política de privacidade.

O que isso custou. A política de segurança de conteúdo não usa nonce, e por isso script-src carrega 'unsafe-inline'. É dívida consciente e datada, registrada no ADR 0005: gerar nonce por requisição exigiria middleware e tornaria todas as rotas dinâmicas, jogando fora o HTML estático de que a indexação depende. A decisão expira sozinha no dia em que entrar login, sessão, conteúdo de terceiro renderizado como HTML ou script de terceiro — e os quatro gatilhos estão escritos, não subentendidos.
curl -sI http://localhost:3100/

Tabelas que o público consegue ler

3 de 7e zero policy nas que guardam mensagem

As sete tabelas têm segurança em nível de linha habilitada e forçada — forçada vale inclusive para o dono da tabela. Sobre elas existem seis policies, e a conta importa mais que o total: três deixam o visitante ler só o que já está publicado (projeto no ar, imagem de projeto no ar, depoimento aprovado), duas servem ao painel e exigem estar na tabela de administradores, e uma é do dono.

O número que vale é o outro: duas tabelas não têm policy nenhuma — a de mensagens de contato e a do contador antispam. Sem policy, com RLS forçada, a resposta do Postgres é vazio para todo mundo que não seja a chave de servidor. Não é uma regra que alguém escreveu e pode apagar: é a ausência de regra, que nega por definição.

Dito na prática: a chave que roda no navegador de quem visita não lê nenhuma mensagem de contato. Não porque o código esquece de pedir — porque o banco recusa. Ela vaza por desenho, e o que ela abre é exatamente o que já está publicado na tela.

select tablename, policyname, cmd, roles from pg_policies where schemaname = 'public';

Segredos encontrados no que vai ao navegador

0de 5 valores só-de-servidor, procurados letra por letra

A medição varre 37 arquivos — todo o estático servido mais todo o HTML pré-renderizado — procurando o valor literal de cada variável de ambiente. Chave de serviço do banco, sal do hash de IP, chave da API de e-mail, segredo do antispam e endereço de destino: nenhum aparece. Também não aparece o nome de nenhuma delas, que é como o vazamento costuma começar.

Foi medido com valores-sentinela, não com as chaves de produção — de propósito. Auditoria que exige o segredo para rodar é auditoria que ninguém de fora consegue repetir; os valores estão escritos em scripts/medir.mjs para você reproduzir. As duas variáveis públicas aparecem, porque é para isso que servem.

node scripts/medir.mjs --sem-rede

Casos de teste hostis

351casos escritos — 164 de unidade e 187 com navegador

Os casos que valem citar não são os felizes. São: quebra de linha CRLF no campo de nome, que é injeção de cabeçalho de e-mail; campo-armadilha mordido, que responde sucesso falso sem denunciar qual campo era; corpo de requisição acima do teto, conferido antes do JSON.parse, porque analisar 9 KB para depois recusar já é o ataque funcionando; e um status mandado pelo cliente, que o esquema simplesmente não tem.

O que isso custou. Alguns dos testes com navegador se pulam sozinhos e imprimem o motivo ao fim de cada execução — só valem contra um build de produção, ou contra um painel com credencial. Teste pulado em silêncio vira teste esquecido, então eles falam. O número acima é contado do repositório a cada build. E o que ainda não temos é medição de campo — só laboratório —, porque não há visitante.
npm test && npm run e2e

O que já reprovou aqui dentro

Duas coisas que quase nunca são medidas porque quase nunca são exigidas por alguém — até serem.

Checagens de contraste no CI

27pares de cor, no tema escuro — o único que o site tem

O script lê os tokens do próprio arquivo de estilo — não uma cópia em documento — e falha com código de saída 1 se um par cair abaixo do alvo da WCAG. Roda em npm run verify, portanto no CI, portanto antes do merge.

Ele já reprovou e forçou correção. Uma cor de texto secundário estava testada só contra o fundo da página, onde passava; duas páginas a pintavam sobre uma superfície elevada, onde ela dava 4,26:1 e reprovava. Quem achou foi o axe no navegador, não o script. A regra que ficou: toda cor de texto é testada contra toda superfície onde o site a desenha — e os dois pares que faltavam entraram na lista. Vale mais dizer isso do que dizer que seguimos as diretrizes.

npm run contraste

Chaves de ambiente necessárias para o site subir

0build completo com o arquivo de ambiente ausente

A bateria de medições desta página foi feita num build sem nenhuma variável de ambiente. As 26 rotas foram geradas, o servidor subiu e o site funciona — e o formulário de contato recusa o envio e diz que recusou, com o e-mail à vista para a pessoa não ficar sem saída. O que ele nunca faz é responder “mensagem recebida” para uma mensagem que não foi a lugar nenhum.

É o oposto do padrão. Validação de ambiente feita no carregamento do módulo é o motivo número um de build que passa na máquina de quem escreveu e explode na hospedagem. Aqui nada lança exceção ao ler ambiente: quem consome pergunta se está configurado e escolhe o adaptador real ou o que recusa a escrita.

mv .env.local .env.local.desligado && npx next build

Os quatro placares, e o que eles escondem

Lighthouse 12.8.2, perfil móvel com estrangulamento simulado, seis rodadas contra o build de produção rodando localmente. É laboratório, e a diferença importa.

Lighthouse — acessibilidade e SEO

100 e 100sem nenhuma auditoria reprovada

Nota máxima nas duas categorias, com zero auditoria falhando, e o mesmo resultado em seis rodadas seguidas. Não é o teto de acessibilidade — ferramenta automática pega uma fração do que existe, e o resto é teclado, leitor de tela e revisão humana — mas é o piso, e o piso está limpo.

Práticas recomendadas: 96 nas seis. Desempenho ficou entre 93 e 96 em cinco delas e caiu para 86 na sexta, com a máquina de medição ocupada — as seis estão na tabela de docs/medicoes.md, inclusive a pior. Publicamos a faixa e o tropeço de propósito: número de laboratório oscila com a carga de quem mede, e escolher a rodada mais bonita é a forma mais comum de mentir com dado verdadeiro.

Esta página que você está lendo foi medida junto: 100 em acessibilidade, 100 em SEO, 96 em práticas recomendadas e desempenho entre 92 e 95. Ela carrega mais texto que a home e nem um arquivo de JavaScript a mais.

npx lighthouse http://localhost:3100/ --only-categories=performance,accessibility,best-practices,seo

Métricas de campo, no laboratório

0de deslocamento de layout

Deslocamento cumulativo de layout em 0, igual nas seis rodadas — nada pula na tela depois de aparecer, e é consequência direta de não haver imagem chegando atrasada. Primeira pintura de conteúdo em 0,8 s, também estável. O tempo total de bloqueio ficou entre 80 e 190 ms, e é o número mais barulhento da lista.

O que não temos: dado de campo, de gente real, do Relatório de Experiência do Usuário Chrome. Não temos porque não temos tráfego suficiente para gerar. Preferimos escrever isso a apresentar número de laboratório como se fosse medição de uso real.

O que isso custou. O maior conteúdo visível levou de 2,7 s a 2,9 s na home e até 3,2 s aqui, e este é o nosso número mais fraco — a única métrica de desempenho que reprova. A causa está medida e não é o download: 83% desse tempo é atraso de renderização, não transferência. É a próxima coisa a atacar, e não vamos chamar de ótimo enquanto estiver assim. Os dois pontos perdidos em práticas recomendadas são um 404 em /_vercel/insights/script.js, recurso que só existe quando o site está hospedado na Vercel — medindo localmente, ele falta.
npx lighthouse http://localhost:3100/ --output=json

O que fica escrito

O que sobra depois que o time vai embora. É a parte que não aparece na tela e é a que decide se alguém consegue manter isto daqui a dois anos.

Decisões de arquitetura registradas

10ADRs, com data, opções descartadas e custo declarado

Cada ADR diz o que foi decidido, o que foi recusado junto, o que a decisão custa e — nos casos em que é dívida — qual evento a faz expirar. O ADR 0005, o da política sem nonce, tem quatro gatilhos verificáveis num diff.

Ao lado: 15 migrations de banco versionadas, todas as 15 com o bloco de reversão escrito. Migration sem reversão escrita é decisão que só anda para frente.

ls docs/adr/

Nada é pronto sem o comando que prova

4portões encadeados antes de qualquer merge

Tipagem estrita, lint, contraste e testes, nessa ordem, num comando só. O build de produção falha com erro de tipo — publicar com erro conhecido só adia o problema para quando ele custar mais.

É a mesma regra que aplicamos em projeto de cliente: o critério de entrega é o comando que sobe o sistema e a tela que abre, não o arquivo escrito.

npm run verify

Como reconferir tudo isto

Todo número desta página sai desta sequência. Se o seu resultado for diferente do nosso, o errado é o nosso — escreva para a gente e corrigimos a página.

# 1. build de produção, numa porta que não seja a do servidor de desenvolvimento
npm ci
npx next build
npx next start -p 3100

# 2. a bateria inteira: peso, rotas, cabeçalhos, banco, testes, segredos
node scripts/medir.mjs --url http://localhost:3100

# 3. os portões que rodam no CI antes de qualquer merge
npm run verify

# 4. os quatro placares do Lighthouse
npx lighthouse http://localhost:3100/ \
  --only-categories=performance,accessibility,best-practices,seo

A saída bruta da bateria de 2 de setembro de 2026, com data e ambiente, está versionada em docs/medicoes.md. É de lá que os números desta página vieram, e é lá que eles são conferidos quando mudam.

O que este site não faz

Não tem blog. Não tem área logada. Não tem gerenciador de conteúdo, e o texto de cada página mora num arquivo versionado, revisado como código. Não tem busca interna, não tem chat, não tem newsletter e não tem banner de cookies — este último porque não há cookie a consentir.

A razão é a mesma nos seis casos, e não é minimalismo: nada disso foi construído porque nada disso era necessário para o único objetivo desta página existir, que é alguém conseguir falar com a gente. Cada uma dessas peças traria código para manter, superfície para atacar e peso para carregar — e traria a conta mensal junto. No dia em que uma delas passar a ser necessária, ela entra, com o custo declarado antes de começar.

É exatamente o critério que usamos no software de quem nos contrata. A diferença é que aqui você pode conferir.