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 env do next.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/k8s do próprio projeto (per wehive:infra).

Mapa de arquivos

  • Criar modules/frontend/src/infra/env/index.ts — o wrapper Env
  • Criar modules/frontend/src/infra/env/index.test.ts — teste unitário
  • Modificar modules/frontend/package.json — adicionar next-runtime-env
  • Modificar modules/frontend/pages/_document.tsx — <PublicEnvScript />
  • Modificar modules/frontend/run/runtime — derivar NEXT_PUBLIC_API_URL de API_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 bloco env
  • Modificar modules/frontend/.infra/k8s/... ingress do frontend — anotação proxy-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 PublicEnvScript ao _document.tsx

No <Head>, antes de <DocumentHeadTags {...props} />: ```tsx import { PublicEnvScript } from “next-runtime-env”; // …

<DocumentHeadTags {...props} />

```

  • [ ] 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.annotations onde ssl-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ência next-runtime-env + PublicEnvScript + derivação em run/runtime (Tasks 1–2) ✓; todas as leituras de process.env.API_URL → Env.get (Tasks 3–4) ✓; remoção do inlining do bloco env (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 export API_URL em 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.