Do I live in a parallel universe where building SSR/SPA sites with Next.js is somehow incredibly complex? I know below the abstractions there is complexity, but building SSR rendered SPA's today with something like Next.js has never been more simple. I think web development has taken a huge leap forward where we now have stable tooling, a strongly typed language with TypeScript and top notch boilerplate free state management with MobX. To top it off you can write performant backend API's in Node.js with again TypeScript sharing contracts (Interfaces) between backend and frontend. I for one am really happy with the current state of affairs.
It's easy to server-side render Next.js apps, but usually they still have to talk to a backend. I don't think Next.js' API routes are good for this, especially if you need messages queues, cron jobs, etc. Now you have three distinct parts, client-side rendering, server-side rendering and the API, usually all communicating via JSON.
Traditionaly with PHP, C#, Ruby, Elixir, etc, your backend and frontend was tightly coupled, your code has complete access to backend resources, and the view/template would map the state to a HTML document.
Now, for the sake of interactivity, the view has effectively moved from the backend to the frontend with the introduction of SSR/SPA, JSON everywhere and code duplication so that they can all talk and understand each other. It's cool when it works, but it is easily at least 3x the amount of work.
There is of course the argument of "just write both the client and server in JS/TS and use a monorepo", that brings its own challenges. Limiting the backend stack to what browsers support is not great, especially when Node.js is single-threaded with cooperative scheduling. No, lambda functions don't solve this entirely, and they're freaking expensive for CPU time. Honsetly, other ecosystems have it so much simpler IMO, even if it isn't as flashy.
"Limiting the backend stack to what browsers support is not great" Node.js shares the same language but that's about it. In the Node.js backend you have access to modules like lovell/sharp with optimized c code (libvips) for image processing. There really isn't much you can't do. With pm2 it is easy to spin up say 8 instances of Next.js utilizing all available cores. In my experience there is barely a distinction between client and server side rendered components.
getServerSideProps() or getInitialProps(), even though not perfect allow you to either call your backend code directly or call an API with the session cookie as a parameter.
In Next.js you'd typically use getServerSideProps on a page to talk to the backend at runtime. API routes have their place of course, but I've built fairly large-ish websites that almost entirely get their data via getServerSideProps.
Next.js is easy peasy and lovely and wonderful until you need to mix in validations + translations + authentication + authorisation + calling upstream APIs with user's credentials + ...
That's when you realise using what would be a perfect framework for building landing pages might not be the best one to build a full web application.
Isn't that the part where you as a developer step in? Most of these things are solved problems with mature libraries to integrate. I much rather mix and match then fight some all-in-one super framework that doesn't do quite what you need.
I think my job is to ship useful, secure and robust features to my company's customers. Dealing with technology is a consequence of that, not the end goal itself, which seems to be what's most wrong about this industry.
Certainly tying together libraries (or writing your own framework) is a valid approach, it's just a lot more expensive to reach the same quality level. That's why usually you end up with half assed solutions or never ending projects.