From 068e8a58e5ec86f585ff4190c4d6b45280c1617c Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sun, 19 Apr 2026 18:32:54 +0200 Subject: [PATCH] feat(docs): add architecture principles for using API routes over Server Actions in Next.js --- TEKNISK_BESKRIVNING.md | 48 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/TEKNISK_BESKRIVNING.md b/TEKNISK_BESKRIVNING.md index 08de4ee2..a25dcd94 100644 --- a/TEKNISK_BESKRIVNING.md +++ b/TEKNISK_BESKRIVNING.md @@ -121,9 +121,55 @@ Next.js API routes som kräver server-side auth och ska gå via frontend måste | `/api/products*` | explicit regel | `recipe-api:8080` ⚠️ | | `/api/inventory*` | explicit regel | `recipe-api:8080` ⚠️ | +--- + +## Arkitekturprincip: API routes framför Server Actions + +> **Regel: Använd Next.js API routes (`/app/api/...`) för all mutation från klientkomponenter. Använd INTE Server Actions för detta.** + +### Bakgrund + +Next.js Server Actions returnerar alltid ett **RSC-payload** (React Server Component flight-format) som svar — även om funktionen bara returnerar ett vanligt JSON-objekt. När en klientkomponent anropar en Server Action via `startTransition` försöker React tolka svaret som ett siduppdateringspaket. Detta orsakar kraschen **"can't reload page"** / `TypeError: r is not iterable` i React 19 om sidans RSC-träd inte kan återskapas korrekt (t.ex. p.g.a. Caddy-routing, auth-state eller timing). + +### Rätt mönster: Next.js API route + +``` +Klientkomponent → fetch('/api/admin/...') → Next.js API route → Backend API +``` + +- API routen körs server-side och har tillgång till sessionen via `auth()` → kan lägga till auth-headers +- Returnerar ren JSON — inga RSC-payload-problem +- Caddy-safe: använd `/api/admin/` som prefix (faller igenom till `recipe-frontend:3000`) +- Klientkomponenten hanterar UI-state lokalt efter svar (uppdatera/ta bort ur lokal state) + +**Exempel** (se [app/api/admin/product/[id]/route.ts](frontend/app/api/admin/product/%5Bid%5D/route.ts)): +```ts +// API route (server-side, har session) +export async function PATCH(req, { params }) { + const authHeaders = await getAuthHeaders(); // använder auth() + const res = await fetch(`${API_BASE}/api/products/${id}`, { method: 'PATCH', headers: authHeaders, ... }); + return Response.json(await res.json()); +} + +// Klientkomponent +const res = await fetch(`/api/admin/product/${id}`, { method: 'PATCH', body: JSON.stringify(data) }); +const updated = await res.json(); +setProducts(prev => prev.map(p => p.id === updated.id ? updated : p)); // lokal state-uppdatering +``` + +### När är Server Actions OK? + +Server Actions kan fortfarande användas för operationer som **inte anropas från klientkomponenter med `startTransition`**, t.ex.: +- Form submissions i rena Server Components (inget `useTransition`) +- Admin-operationer som ändå triggar en helsidsladdning efteråt + +### Befintliga undantag att känna till + +Dessa Server Actions finns kvar men bör migreras om de orsakar problem: +- `bulkSetCategory` — anropas från `AdminProductList` (klientkomponent) +- `suggestProductCategory` / `suggestBulkCategories` — AI-kategorisering, anropas från klient -## Frontend - **Framework:** Next.js 16.2 (App Router, server + client components) - **Språk:** TypeScript 5.4.5