For a while, starting a web application meant starting a JavaScript application.

The backend would expose an API. React would own the interface. Even when the product was mostly forms, tables, permissions, and business rules, separating the frontend felt like the modern thing to do.

I worked with that shape across different projects and stacks. I have also built server-rendered applications in Rails and Laravel, Angular frontends, React applications, and mobile products in React Native and Expo. I am not opposed to rich clients. Some products need them.

What changed is that I stopped treating the need as automatic.

For many business applications, a separate frontend creates a second application before the first one has earned it.

The browser was not the difficult part

The products I tend to work on have complicated rules, not complicated canvases.

They manage pricing, inventory, safety work, orders, permissions, integrations, and records that have to remain understandable years later. The interface matters, but most interactions still follow a familiar rhythm: show the current state, let somebody change it, validate the change, and show the result.

A server-rendered framework handles that rhythm directly. The route, authorization, business rule, persistence, and response can stay close enough that one developer can follow the request without crossing an architectural border.

With a separate frontend, the same feature may require an API contract, serialization, client-side data fetching, cache invalidation, loading and error states, and another set of tests around the boundary. None of that is inherently bad. It is the cost of giving the browser more independence.

The question I was not asking often enough was whether the product received enough value from that independence to pay the cost.

Separation is not the same as clarity

“The frontend and backend are separate” sounds like a clean architectural statement. Sometimes it is.

It can also split one piece of behaviour across two repositories, two deployment pipelines, and two interpretations of the same contract. A field becomes required on the server but optional in the client. A permission changes but the interface still exposes the action. An API response evolves and an older client continues making a reasonable decision with incomplete information.

The boundary can improve a system when it represents a real separation of responsibilities. It becomes ceremony when both sides are changing together for nearly every feature.

I now pay attention to how often the frontend can deliver value without a corresponding backend change. If the answer is rarely, the organizational and deployment independence may be theoretical. The team owns the coordination cost without receiving much independence in return.

This is one reason server-rendered applications can feel faster to change even when their individual technologies are less fashionable. The code follows the shape of the work.

Server rendering did not stand still

Returning to server rendering does not mean returning to full-page reloads and an interface that ignores the last decade.

Rails with Hotwire and Laravel with tools such as Livewire can update parts of a page, preserve navigation, and support responsive interactions without requiring the entire product to become a client-side state machine. Small amounts of JavaScript still have an important place. The difference is that JavaScript is added where the interaction needs it instead of being made responsible for every screen by default.

That changes the failure mode too.

When the server remains the source of the rendered state, there are fewer places for the browser’s idea of the record to drift away from the application’s. Authentication and authorization remain server concerns. Validation has one authoritative home. A link can still be a link.

Those details sound mundane. Mundane behaviour is valuable in software that has to survive staff changes, browser updates, and years of new requirements.

Where React is still the right answer

There are products I would not build this way.

If the interface is the product—collaborative editing, complex data exploration, design tools, rich offline work, or interaction that cannot tolerate a server round trip—a client application has earned its complexity. The same is true when multiple clients need a well-designed API or when the frontend and backend genuinely move on independent schedules.

React is also a reasonable organizational choice when the team already has deep React experience and a mature way to operate it. Replacing known complexity with unfamiliar simplicity is not always a win.

My argument is narrower: forms and dashboards do not become rich client applications merely because React can render them.

My default changed

I used to begin with the architecture I expected a serious application to have, then fit the product into it.

Now I begin with the shortest path from a user action to a trustworthy result.

For many of the systems I build, that means a cohesive server-rendered application with targeted JavaScript. It is easier to trace, easier to deploy, and easier for a small team to keep in its head. If the product later develops a real need for a richer client boundary, that boundary can be introduced deliberately.

This is not full circle because older technology won an argument. The frameworks changed, browsers changed, and my sense of where software becomes expensive changed too.

I still use React.

I just want the product to ask for it before the architecture does.