Early in my career, everything lived in one place. One repository, one workflow. I built a feature and shipped it. There was no line between frontend and backend — just the application. UI and business logic sat together in the same codebase, and that was fine.
There was no juggling multiple repositories or running separate codebases. No distinct "frontend" and "backend" roles. A single team, often a single developer, owned the feature from start to finish.
I think we've drifted far from that.
Today, even for simple features, we start by defining API contracts so the UI team isn't blocked. We discuss request shapes, response structures, versioning, backward compatibility. All before writing any actual functionality.
When something breaks, debugging starts with figuring out which side broke. Is the frontend transforming data wrong? Is the backend returning something unexpected? Is the contract out of date? Did someone deploy only half the system?
The two halves evolve separately, and keeping them in sync becomes a job in itself. Teams spend more time coordinating than building.
Single Page Applications pushed this further. The backend became a data provider. The frontend took over rendering, state management, validation, sometimes even business rules. Now both sides validate the same things, and when those implementations drift apart, you get bugs that are hard to trace.
Most applications just need to show some data and save some forms. They don't need a client managing state that the server already knows.
There's a simpler approach. The server owns the data, the rules, and the rendering. When a user does something, the server processes it and returns HTML. The browser just displays what it receives.
This doesn't mean full page reloads. HTMX, Hotwire, and Laravel Livewire update only the parts of the page that changed. Different stacks, same idea.
When the server returns HTML, there's no contract to negotiate. The server doesn't send JSON hoping the UI interprets it correctly. It sends the result. Debugging gets easier because everything happens in one place. One developer can build and ship a feature without coordinating across repos and teams.
JavaScript doesn't disappear. You use it for things that genuinely need to happen in the browser, not for duplicating what the server already knows.
BFF and GraphQL exist because once you have multiple clients consuming your backend, something has to shape data for each screen. Both are clever solutions to a problem most applications don't have.
APIs still make sense when you're building mobile apps, exposing functionality to third parties, or serving genuinely different clients. But somewhere along the way, we started treating our own frontend like an external consumer. We called it "API-first" like it was a virtue.
What a third-party integration needs and what your own UI needs are rarely the same thing.
The appeal of server-rendered HTML varies with team size. Small teams benefit most from owning features end-to-end. Larger organizations with specialized teams may still prefer clear separation.
But I've noticed a lot of small teams adopting big-team architectures because they thought they were supposed to. They weren't.
This isn't nostalgia. It's recognizing that we've added complexity many applications don't need. Server-rendered HTML takes what worked before and combines it with what we've learned since. Modern UX, responsive interactions, less machinery.
Systems that are easier to build and easier to maintain.