Lib de App Router adotada no Pages Router falhou em silêncio
TLDR: adotamos
next-runtime-env@3.xpara resolver env em runtime. A 3.x é a linha de App Router; o app é Pages Router. Nada foi emitido no HTML,Envdevolveuundefinedsem erro, e testes, lint e build passaram verdes — o fix foi mergeado sem funcionar.
O que aconteceu
O login e o painel no k8s davam 404 porque a API URL era inlinada em build time e a imagem
é buildada sem ela. A correção adotou next-runtime-env e foi mergeada com suíte verde,
lint limpo e build passando. Não funcionava.
Por que passou despercebido
Três camadas de silêncio empilhadas:
- A lib estava fora da matriz de suporte. O README dela é explícito:
1.xé Pages Router (Next 12/13),3.xé App Router (Next 14). O spec original afirmou que “suporta Next 15 + pages-router” — afirmação nunca verificada, e falsa. - Duas cópias do Next. A lib declara
next: ^14emdependencies(não só empeerDependencies), então o npm aninhou umnext@14.2.35dentro dela — 215 MB. Onext/scriptque ela importa era de outra instância do Next, ebeforeInteractivedepende do componente se registrar no runtime que renderiza o_document. O registro nunca chegou. -
A detecção de browser dependia do valor que ela não conseguia injetar.
js function isBrowser() { return Boolean(typeof window !== 'undefined' && window['__ENV']); }Sem
__ENV,isBrowser()devolvefalsedentro do browser, eenv()cai no ramo de servidor lendoprocess.env— que no bundle client viraundefined. Sem exceção, sem log.
Por que a suíte não pegou
Os testes exercitavam a classe Env com a lib mockada. O build só provava que a URL
não estava mais inlinada — ausência de evidência tratada como evidência de sucesso. Ninguém
subiu o servidor e olhou se o browser recebia o valor. O bug só apareceu quando o usuário
pediu para rodar e ver funcionando.
O que fazer diferente
- Verificar a matriz de compatibilidade da dependência antes de adotá-la, não depois.
Uma consulta ao README e ao
peerDependenciesteria matado a escolha em dois minutos. - Desconfiar de
dependenciesonde deveria haverpeerDependenciesem libs de framework: é o sinal de que uma segunda cópia do framework vai entrar nonode_modules. - Um mecanismo que atravessa server→browser precisa de um teste que atravesse
server→browser. Teste unitário com a lib mockada não prova integração. Hoje existe
src/infra/env/runtimeEnvScript.test.ts, que assere sobre o HTML realmente emitido. - Preferir falha ruidosa a fallback silencioso. Todo o custo veio de
undefinedser um valor aceitável onde deveria ser um erro.
Achado colateral: o contrato de URL ambíguo
Na mesma investigação apareceu que o código mistura ${API_URL}path e ${API_URL}/path —
o mesmo arquivo usa as duas formas. Sobreviveu porque .env.example tinha barra final e o
secret do k8s não. Consequência: phone auth e cadastro inicial já estavam quebrados em
staging, por um motivo diferente do 404. Contrato de formato de URL precisa ser único e
aplicado por uma função, nunca por convenção.