

20 September 2026

Plenty of full-stack projects go all-in on one framework — Next.js API routes for everything, or a Django app serving templates. This site deliberately doesn't: a Next.js frontend and a FastAPI backend as two independent applications, talking over a REST API, deployed separately. Here's the actual reasoning, and how the two sides really talk to each other in a deployed app rather than a toy example.
Next.js API routes are genuinely fine for simple CRUD. Where they get uncomfortable is anything Python does meaningfully better — and a good chunk of what a backend for a data/ML-adjacent product needs (data processing, ML model serving, anything touching pandas or scikit-learn) is squarely in Python's territory. Rather than forcing that into a TypeScript API route or maintaining two backend languages, FastAPI owns all of it, and Next.js stays focused on what it's genuinely best at: server-rendered React with excellent caching and routing.
This isn't a single generic "fetch from the API" pattern — there are two distinct paths, because public reads and admin writes have completely different trust requirements:
next: { revalidate: 60 }), so public pages get fast, cached responses without hitting the backend on every request.cache: "no-store"), because a write should never be stale and the credential authorizing it should never reach client-side JavaScript.That split isn't incidental — it's the difference between a public page and an action that mutates real data, and treating them identically would either leak credentials to the browser or serve stale data where it actually matters.
Session gating (who's allowed to see the admin UI at all) and write authorization (which requests the backend actually trusts) are deliberately separate: NextAuth handles session identity on the frontend, matched against an admin email — but the backend doesn't trust that at all. Every backend mutation independently requires an admin API key header, checked server-side, completely independent of whatever the frontend session claims. The frontend's admin actions are the bridge: they trust the session, then attach the backend credential — but the backend would refuse the write even if someone bypassed the frontend gate entirely.
Deploying frontend and backend as genuinely separate processes (not just separate folders in a monorepo) means each can be scaled, restarted, and deployed independently — a backend deploy doesn't require rebuilding the frontend, and vice versa. The cost is an explicit API contract between them instead of implicit function calls, which is a tradeoff worth making once a project is real enough to want that independence.
FAQ
API routes work fine for simple CRUD, but anything Python does better — data processing, ML-adjacent work — belongs in a real Python backend rather than being forced into TypeScript or split across two backend languages.