Next.js notes from 3 companies
I’ve worked with Next.js at 3 different companies, on different types of applications. I’ve had good experiences with it. I’ve seen a lot of negative sentiment about it too.
At my most recent gig, my CTO told me he would prefer to see people stay away from the server-side rendering capabilities of Next, and that he primarily chose Next because of the ease of hiring engineers with experience in that framework. I think that’s a very interesting data point, and is what sparked the idea to write this.
At my previous 2 gigs there were similar feelings about Next:
- One migrated our production web app from Next to vanilla React with Vite
- One built 2 products with Next, but all future frontend development was React+Vite
If you hang around X/Twitter, you will see a lot of people shitting on Next as well.
So what gives? Why all the hate / buyers remorse?
Next is not for every project
I don’t fully understand why, but tech folk can be a bit dogmatic about tech stack choices — “I use X, and anyone who uses Y is an idiot”. I think part of why Next is being brought into contexts it is unsuited for stems from this dogmatism.
Next specifically shines in a full-stack context, where your backend & DB code is co-located with your frontend code. It’s particularly strong in contexts where you can server render screens that don’t change frequently and leverage caching for fast subsequent loads.
A lot of the friction I have observed (both on projects I have worked on and online) stems from using Next in the “wrong” context (this is debatable).
Rather than fully utilizing it’s full stack capabilities, or using it to render cacheable HTML, I have seen it used as the frontend for highly dynamic “dashboard” style applications, backed by a separately deployed API.
Caching is hard
Next 14 in particular got this wrong, with a heavy-handed approach to the default cache behavior (opt-out, not opt-in).
Because a lot of folks were building highly dynamic applications, stale data being rendered was a frequent pain point. When I was building a banking portal for Fuse in 2023, this was a recurring nuisance. There was a lot of dev frustration being expressed in GitHub issues and on Twitter around this time.
They have improved the situation somewhat since then…
…but working with dynamic/static and cached content in Next remains thorny — there are a lot of gotchas; you can easily de-opt from static rendering accidentally, special module-level “directives” to override default behaviors, and generally just a lot of moving parts to it.
Server-side rendering is… a lot
Modern server side rendering with React has a lot of moving parts. There are undoubtedly benefits for some contexts — like securely interacting with services during render and returning only the resulting HTML, or streaming it in over a loading/fallback UI — but it brings with it complexity and tradeoffs; as anyone who’s spent time debugging and resolving hydration errors can tell you.
Those tradeoffs include additional network hops, duplicate requests, and the reasoning overhead of what is running on the server and what is running on the client.
There’s a great article from Ahmad Alfy that touches on some of the downsides of the modern SSR approach in more depth. (I don’t agree with everything in the article, but I agree with the spirit of the article.)
In practical terms, unless you are working on app that serves data that is cacheable by nature, the complexity tradeoffs that SSR in React bring might just not be worth it. I have worked on an application like this — a “freemium” tool for inspecting open source packages I built while at Stacklok — and Next was a very good choice here because the rendered UI was in fact very cacheable.
An additional layer of complexity atop React
Neither vanilla React of Next are simple — although I believe both have done a reasonable job at hiding the complexity so that a new dev can be productive right away.
Take, for example React’s useEffect
hook — long understood to be one of the
biggest potential footguns in frontend. I won’t go in-depth on useEffect
— I
use this only to illustrate a point — I will only say that it is
often-misunderstood, often-misused and often causes bugs. (Indeed, Cloudflare
even managed to DDOS themselves through a misconfigured
useEffect).
Next, as a React meta-framework wraps a lot of additional complexity around correct use of React — so now, not only do you need to understand React at a deep level to do your job well, but you also need to understand Next, and how Next & React intersect.
Next can be easy to reason about provided you have good working knowledge of the framework, newer concepts like React Server Components, and have spent time studying the documentation — but this raises the barrier-to-entry for the project. I have seen backend devs, junior frontend devs and LLMs all struggle to write “modern” React in Next.js projects.
So choosing it for a project that doesn’t particularly benefit from it is often just inviting a larger surface area for bugs into your codebase, as well as making it more difficult for others to come and be productive within the codebase.
Closing notes
I actually like Next a lot. I have deployed it to great effect for the heavily cacheable UIs I mentioned above. I have also helped companies migrate project from Next to Vite. It’s a solid tool for the right job, but like any tool, it is better suited to some tasks than others.