The Monolith We Were Running From
Two years ago, a typical internal tool at Foetron was a Flask app serving Jinja2 templates, jQuery for interactivity, Bootstrap for styling, and a PostgreSQL database. It worked. It also took 3–5 seconds to load any page, required a full-page reload for every interaction, and made adding new features feel like defusing a bomb.
The rewrite to a Next.js frontend + FastAPI backend wasn't a "we love new tech" decision. It was driven by specific, measurable problems.
The Performance Case
Server-Side Rendering vs. Traditional MVC
A Flask/Jinja2 page load sequence:
- Browser requests (RTT: 200ms)
- Flask queries PostgreSQL (50–300ms depending on query)
- Jinja2 renders HTML (5–15ms)
- Browser parses and renders HTML (50–100ms)
- jQuery downloads and executes (100–400ms for deps)
- Total: 400–1000ms — and that's without cache misses
A Next.js page with SSR + React Server Components:
- Browser requests (RTT: 200ms)
- Next.js server component renders HTML with data (50–200ms, parallelised queries)
- Browser paints (50–100ms) — page is visible and interactive
- React hydrates incrementally in background
- Total to first paint: 250–500ms
The delta is the incremental hydration — users see content and can interact before all JavaScript loads.
The Numbers From Our Migration
| Metric | Flask/Jinja2 | Next.js (SSR) | Improvement |
|---|---|---|---|
| Time to First Byte (P50) | 420ms | 180ms | 57% faster |
| Largest Contentful Paint (P75) | 2.8s | 0.9s | 68% faster |
| Total Blocking Time | 890ms | 120ms | 87% lower |
| Lighthouse Performance | 41 | 87 | +46 points |
Why FastAPI, Not Node.js Backend?
This is the question everyone asks. The answer is: the team was more productive in Python for data-heavy work, FastAPI's automatic OpenAPI docs are excellent for internal tooling, and the Pydantic validation layer prevented entire categories of bugs.
FastAPI gives you:
That single endpoint definition gives you: automatic input validation with descriptive errors, automatic OpenAPI documentation at , TypeScript-compatible schema generation via , and async concurrency for Azure API calls.
The Developer Experience Win: Tailwind CSS
The argument against Tailwind is always "class soup in your HTML." The argument for it is: you never leave your editor to write or debug styles.
Before (Bootstrap + custom SCSS):
After (Tailwind):
The difference: a new developer can read the Tailwind version and immediately understand every style being applied. They don't need to hunt through SCSS files.
The Responsive Design Workflow
Tailwind's breakpoint prefix system collapses responsive design to single lines:
The equivalent in CSS:
At scale (50+ components), the Tailwind version means the difference between a 2-minute and 20-minute CSS debugging session because styles live with the component, not in a separate file.
The Integration Layer
Next.js frontend talking to FastAPI backend — the pattern that works:
The proxy route in Next.js handles CORS, auth header injection, and keeps the FastAPI URL server-side only.
The stack is more moving parts than a monolith. It's also the stack that lets two engineers ship production features 3× faster than five engineers on the monolith did.