But in most web apps, interactive widgets are just little islands in the page. The higher concerns are routing, authorization,… which are already handled by the browser/server interaction.
Many people phrase it like this, insisting that server-rendered HTML is simpler. To me, SPA's are the simpler approach. They remind me more of the .EXE's I used to ship.
I also stay away from dynamic prerendering and/or hydrating. If an SPA can't be hosted statically, it should have a very good reason.
IMO building around Vue has really paid off over the last decade. They fixed the frequent Webpack config issues with Vite, they fixed the frequent Jest issues with Vitest, they improved build times a lot with Rolldown and oxc, they fixed all the annoying linter/formatter config stuff with oxfmt & oxlint, and Vue itself prevents tons of issues that plague React projects.
As with any SPA it should also be considered a problem if widgets updated themselves out of sync. My bank's reactive has this problem, and it's maddening. So my approach is to have all widgets and UI refresh upon central events from a single source of truth that's progressively updated as a data bundle from the server. Long poll the whole set of customer data, refresh it immediately after actions, and have those both trigger events that are recognized by all my vanilla widgets. What are we talking about? 15k over the wire every 30 seconds or on demand? Widgets that know how to adjust if something relevant to them changes? No big deal.
It's a different paradigm from either reactive or server side rendering, and the main advantage to me is the data consistency, as well as the development ergonomics of essentially having every UX element be just a view of the same data.
SSR on reservation systems looks incredibly cumbersome to me. Especially all those that still use crazy GET strings to hold state. "Pages" and user state are a front end concept.
In most SPA, state is distributed with two copies floating around. Even if you do restrict the update flow, it’s still a lot of works to keep it consistent. That’s good enough when it’s partitioned and each side take care of its own state, but in most cases, the intersection is very large.
With SSR, the “page” is a dead copy. The only source of truth is the backend.
In all client-server systems, state is distributed with "two copies floating around". The difference between SSR and REST+SPA is that in the SSR, your server code is creating the markup to make it presentable to an end-user, but it's still fundamentally a copy of the state that exists on the server, and it's not obvious why the transformation to markup rather than structured data (like JSON) is desirable on the server. If anything, with SSR you need to copy more state from the client to the server, where it doesn't really belong.
In my SPAs, server calls and the state copies they return to the client are numbered sequentially from the start of each session. A server response with a number earlier than the last client call is ignored. That mostly takes care of race conditions that arise from latency. Fundamentally this is similar to SSR in that the client will not update until the server sends new data... there is no client-side modification of the client data copy, ever, so it can never diverge from the last known server state. That would of course be a disaster. The difference is that the page can change progressively without reload, and the state is available between "pages" without more calls to the server.
Unfortunately I get shouted down by those who think we must be maximally efficient even though in makes little difference in render time, and hate “prop drilling”