One for the server - where you can go wild.
One for the client - that should be thoughtful and careful.
So what went wrong?
"Write once, run everywhere"
This used to be the goal. To be able to write one code base that would run in all environments.
In the early 2000s I considered this to be run on any browser on any operating system, but in the last decade, with frameworks (or libraries…who knows) such as React and Vue, that's extended to the server too.
And for a time, it was amazing. Amazing for developers, not so much for visitors and end users. Though I think (or assume) that over the years those frameworks have worked to repay the performance dept they borrowed from the visitor but a lot of harm (through design patterns) has already been done.
An 11 meg anecdote
I was working on a Vue app that used Nuxt (version 2). It was heavily data driven, but entirely static. My thinking was that Nuxt (a server rendering framework for Vue, akin to Next for React), would build the HTML, send it over the wire and the visitor would get the content they want.
Except behind the scenes, Nuxt was also sending all the data that built the page, along with all the Vue framework. In some cases, the data that was used to build the page (remember, on the server) was 11mb large. On the server, that's fine, 11mb of JSON dumped into the page especially when it's not going to be used is problematic.
Eventually we worked out how to (literally) hack the Nuxt system to prevent this, but the framework was working against us.
Write once is a sledge hammer approach
Writing one codebase that runs both on the server and on the client seems great in principle, but no amount of logic is going to sensibly separate what is intended for for the server and what's intended for the client.
I think this is how the crappy reputation has come about. As a developer, it's "easier" to add another dependency to my project to do some simple task like offer me a themeable template than it is to carefully craft the HTML required to perform the same task. The problem is that it then generates way more output than required and puts that burden on the visitor.
Another example that comes to mind is the humble
Try to implement that using Next.js (which I'm definitely a fan of) and I can't think (off the top of my head) how. You can post to "api endpoint", but that'll return JSON ordinarily, plus it looks weird in the URL. I'm sure there's a work around, but the point is that it has to be worked around, intentionally.
It looks to be project that's leaning hard on progressive enhancement, which means everyone benefits from working web sites.