Frontend runtime API URL — Implementation Plan
TLDR: Fazer o browser resolver a API base URL em runtime (a partir do env do pod) para que as chamadas client-side batam no backend diretamente em vez de 404, e tornar permanente a correção do buffer do ingress (502).
Branch:
fix/frontend-runtime-api-url
Arquitetura: Introduzir um módulo Env pequeno que encapsula o env() do next-runtime-env e a convenção NEXT_PUBLIC_ (Env.get("API_URL")). O pod deriva NEXT_PUBLIC_API_URL a partir do secret API_URL existente no boot (run/runtime); o PublicEnvScript o serve ao client em runtime; todas as leituras de process.env.API_URL passam a Env.get("API_URL"). Remove o inlining de build time do bloco env. Adiciona proxy-buffer-size ao ingress do frontend.
Stack: Next.js 15 (pages-router), next-runtime-env, axios, react-query, Jest, Kustomize.
Restrições globais
- Diretório do módulo:
modules/frontend. Testes: Jest (run/test/npm test), jsdom,@/→src/. - A variável de runtime é
NEXT_PUBLIC_API_URL; consumidores usam sóEnv.get("API_URL"). - Nada relacionado a API-URL no bloco
envdonext.config.js(inlinaria em build). - Imagem permanece agnóstica de ambiente: sem build-arg, sem imagem por ambiente; URL vem do pod em runtime.
- Commits: uma linha, ≤60 chars, sem menção a IA.
- Infra: a anotação do ingress vive no
.infra/k8sdo próprio projeto (per wehive:infra).
Mapa de arquivos
- Criar
modules/frontend/src/infra/env/index.ts— o wrapperEnv - Criar
modules/frontend/src/infra/env/index.test.ts— teste unitário - Modificar
modules/frontend/package.json— adicionarnext-runtime-env - Modificar
modules/frontend/pages/_document.tsx—<PublicEnvScript /> - Modificar
modules/frontend/run/runtime— derivarNEXT_PUBLIC_API_URLdeAPI_URL - Modificar
modules/frontend/src/services/fetch/fetch.ts—baseURL = Env.get("API_URL") - Modificar
modules/frontend/src/infra/NextAPIAuth/index.js—Env.get("API_URL") - Modificar
modules/frontend/src/infra/routesGuard/validators/restrictedRoutesValidator/restrictedRoutesForRolesValidator.tsx—Env.get("API_URL") - Modificar
modules/frontend/next.config.js— remover API_URL/NEXT_PUBLIC_API_URL do blocoenv - Modificar
modules/frontend/.infra/k8s/...ingress do frontend — anotaçãoproxy-buffer-size
Task 1: módulo Env (encapsula next-runtime-env)
Files:
- Create: src/infra/env/index.ts
- Test: src/infra/env/index.test.ts
- Modify: package.json (add next-runtime-env)
Interfaces:
- Produz: Env.get(key: string): string | undefined, Env.getOrThrow(key: string): string. Consumido por fetch.ts, NextAPIAuth, route guard.
- [ ] Passo 1: Adicionar a dependência
bash
cd modules/frontend && npm install next-runtime-env
- [ ] Passo 2: Escrever o teste que falha
```ts // src/infra/env/index.test.ts import { Env } from “./index”;
jest.mock(“next-runtime-env”, () => ({ env: (key: string) => ({ NEXT_PUBLIC_API_URL: “https://api.example.com/api/v1” } as Record<string, string>)[key], }));
describe(“Env”, () => { it(“resolves a key via the NEXT_PUBLIC_ prefix”, () => { // arrange / act const value = Env.get(“API_URL”); // assert expect(value).toBe(“https://api.example.com/api/v1”); });
it(“getOrThrow raises when the key is missing”, () => { // arrange / act / assert expect(() => Env.getOrThrow(“MISSING”)).toThrow(/MISSING/); }); }); ```
- [ ] Passo 3: Rodar para confirmar a falha
bash
cd modules/frontend && npm test -- src/infra/env
Esperado: FAIL — Cannot find module './index'.
- [ ] Passo 4: Escrever a implementação mínima
```ts // src/infra/env/index.ts import { env } from “next-runtime-env”;
const PUBLIC_PREFIX = “NEXT_PUBLIC_”;
export const Env = {
get(key: string): string | undefined {
return env(${PUBLIC_PREFIX}${key}) ?? env(key);
},
getOrThrow(key: string): string {
const value = Env.get(key);
if (!value) throw new Error(Missing env: ${key});
return value;
},
};
```
- [ ] Passo 5: Rodar para confirmar que passa
bash
cd modules/frontend && npm test -- src/infra/env
Esperado: PASS (2/2).
- [ ] Passo 6: Commit
bash
git add modules/frontend/src/infra/env modules/frontend/package.json modules/frontend/package-lock.json
git commit -m "feat: env wrapper over next-runtime-env"
Task 2: servir o env de runtime ao client (_document + run/runtime)
Files:
- Modify: pages/_document.tsx (add <PublicEnvScript />)
- Modify: run/runtime (derive NEXT_PUBLIC_API_URL from API_URL)
Interfaces:
- Consome: next-runtime-env (dependência da Task 1).
- Produz: NEXT_PUBLIC_API_URL disponível para Env.get no client em runtime.
- [ ] Passo 1: Adicionar
PublicEnvScriptao_document.tsx
No <Head>, antes de <DocumentHeadTags {...props} />:
```tsx
import { PublicEnvScript } from “next-runtime-env”;
// …
```
- [ ] Passo 2: Derivar a variável pública no boot em
run/runtime
```bash #!/usr/bin/env bash set -e
export NEXT_PUBLIC_API_URL=”${API_URL}”
exec npm run start ```
- [ ] Passo 3: Verificar (build + sem inline)
bash
cd modules/frontend && npm run build 2>&1 | tail -5
Esperado: build passa. (A entrega em runtime é verificada ponta a ponta em staging conforme o spec; não há teste unitário para a fiação _document/run.)
- [ ] Passo 4: Commit
bash
git add modules/frontend/pages/_document.tsx modules/frontend/run/runtime
git commit -m "feat: serve runtime env via PublicEnvScript"
Task 3: fetch.ts usa Env.get para o baseURL
Files:
- Modify: src/services/fetch/fetch.ts
- Test: src/services/fetch/fetch.test.ts
Interfaces:
- Consome: Env.get("API_URL") (Task 1).
- Produz: fetchInstance com baseURL resolvida em runtime; fetcher/fetchFn com assinatura inalterada.
- [ ] Passo 1: Escrever o teste que falha
```ts // src/services/fetch/fetch.test.ts jest.mock(“@/infra/env”, () => ({ Env: { get: () => “https://api.example.com/api/v1” }, }));
import { fetchInstance } from “./fetch”;
describe(“fetchInstance baseURL”, () => { it(“resolves baseURL from Env at runtime”, () => { // arrange / act const base = fetchInstance.defaults.baseURL; // assert expect(base).toBe(“https://api.example.com/api/v1”); }); }); ```
- [ ] Passo 2: Rodar para confirmar a falha
bash
cd modules/frontend && npm test -- src/services/fetch
Esperado: FAIL — baseURL é process.env.API_URL (undefined no teste), não o valor mockado.
- [ ] Passo 3: Escrever a implementação mínima
Substituir as linhas 6 e 11 em src/services/fetch/fetch.ts:
```ts
import { Env } from “@/infra/env”;
// …
export const API_URL = Env.get(“API_URL”); // keep the export name; value now runtime
export const fetchInstance: AxiosInstance = axios.create({
baseURL: Env.get(“API_URL”),
timeout: 10000,
});
```
(Manter o named export API_URL para não quebrar imports existentes em outros lugares; seu valor agora é resolvido em runtime.)
- [ ] Passo 4: Rodar para confirmar que passa
bash
cd modules/frontend && npm test -- src/services/fetch
Esperado: PASS.
- [ ] Passo 5: Commit
bash
git add modules/frontend/src/services/fetch/fetch.ts modules/frontend/src/services/fetch/fetch.test.ts
git commit -m "fix: fetch baseURL from runtime env"
Task 4: substituir as leituras restantes de process.env.API_URL
Files:
- Modify: src/infra/NextAPIAuth/index.js
- Modify: src/infra/routesGuard/validators/restrictedRoutesValidator/restrictedRoutesForRolesValidator.tsx
Interfaces:
- Consome: Env.get("API_URL").
- [ ] Passo 1: NextAPIAuth
Substituir a linha 7 const API_URL = process.env.API_URL; por:
js
import { Env } from "@/infra/env";
const API_URL = Env.get("API_URL");
Manter funcionando as concatenações existentes (API_URL + "/terapeuta/sign_in", ${API_URL}phone_...) — Env.get retorna o mesmo formato de string que o secret fornece (trailing slash preservado), então o comportamento não muda. NÃO alterar o estilo de join; só a origem do valor.
- [ ] Passo 2: Route guard
Substituir a linha 43 const apiUrl = process.env.API_URL || ""; por:
tsx
import { Env } from "@/infra/env";
const apiUrl = Env.get("API_URL") || "";
- [ ] Passo 3: Verificar (lint + testes existentes continuam passando)
bash
cd modules/frontend && npm run lint 2>&1 | tail -5 && npm test 2>&1 | tail -8
Esperado: nenhum erro de lint novo; suite de testes verde.
- [ ] Passo 4: Commit
bash
git add modules/frontend/src/infra/NextAPIAuth/index.js modules/frontend/src/infra/routesGuard
git commit -m "refactor: read api url via env wrapper"
Task 5: remover API_URL do bloco env do next.config
Files:
- Modify: next.config.js
- [ ] Passo 1: Editar o bloco
env
Remover API_URL e NEXT_PUBLIC_API_URL, mantendo o resto:
js
env: {
ENV_MODE: process.env.NODE_ENV,
},
- [ ] Passo 2: Verificar — build passa e API URL não é inlinada
bash
cd modules/frontend && npm run build 2>&1 | tail -5
grep -rl "beta.staging.api.trgclub.com\|staging-api.trg.club" .next/static 2>/dev/null && echo "FOUND (bad — inlined)" || echo "not inlined (good)"
Esperado: build passa; API URL NÃO encontrada em .next/static (prova que não há inlining em build time).
- [ ] Passo 3: Commit
bash
git add modules/frontend/next.config.js
git commit -m "refactor: stop inlining api url at build"
Task 6: tornar permanente o proxy-buffer-size no ingress do frontend
Files:
- Modify: modules/frontend/.infra/k8s/<base or overlay>/...ingress...
Interfaces: - Corrige o 502 (cookie grande do next-auth vs. buffer padrão do nginx), tornando permanente a anotação validada ao vivo em staging via kubectl.
- [ ] Passo 1: Localizar o manifest do ingress
bash
cd modules/frontend && grep -rln "kind: Ingress\|nginx.ingress" .infra/k8s 2>/dev/null
-
[ ] Passo 2: Adicionar as anotações (no mesmo bloco
metadata.annotationsondessl-redirect/ host estão — base spec ou o patch do overlay, seguindo onde o host é definido):yaml nginx.ingress.kubernetes.io/proxy-buffer-size: "16k" nginx.ingress.kubernetes.io/proxy-buffers-number: "4" -
[ ] Passo 3: Verificar que o kustomize renderiza a anotação
bash
cd modules/frontend && kubectl kustomize .infra/k8s/overlays/staging 2>/dev/null | grep -A1 "proxy-buffer-size"
Esperado: a anotação aparece no Ingress renderizado.
- [ ] Passo 4: Commit
bash
git add modules/frontend/.infra/k8s
git commit -m "fix: raise ingress proxy buffer for auth cookie"
Task 7: Docs
Files:
- Create/Modify: .project/docs/reference/frontend/frontend_runtime_api_url.md
- [ ] Passo 1: Escrever a nota de topologia
Documentar: a API base URL é resolvida em runtime via o wrapper Env sobre o
next-runtime-env (NEXT_PUBLIC_API_URL derivada de API_URL no boot em
run/runtime, servida pelo PublicEnvScript); nunca inlinar API URLs no bundle
do client no k8s (modelo build-once/promote); browser chama o domínio da API
diretamente (CORS aberto); ingress carrega proxy-buffer-size para o cookie do
next-auth.
- [ ] Passo 2: Commit
bash
git add .project/docs/reference/frontend
git commit -m "docs: frontend runtime env topology"
Autorrevisão
- Cobertura do spec: wrapper
Env(Task 1) ✓; dependêncianext-runtime-env+PublicEnvScript+ derivação emrun/runtime(Tasks 1–2) ✓; todas as leituras deprocess.env.API_URL→Env.get(Tasks 3–4) ✓; remoção do inlining do blocoenv(Task 5) ✓; buffer do ingress permanente (Task 6) ✓; docs (Task 7) ✓; 502 e 404 endereçados. - Placeholders: o path do manifest do ingress é descoberto no Passo 1 da Task 6 (grep), não fica em branco; sem TBD/TODO.
- Consistência de tipos:
Env.get(key: string): string | undefinedé usado identicamente nas Tasks 3–4; o named exportAPI_URLem fetch.ts é preservado para não quebrar imports externos. - Fora de escopo respeitado: sem trabalho de backend/Aurora/conta, sem prod, sem build-arg, sem proxy — de acordo com o spec.