Why Next.js should focus on reducing source code complexity

On my last post, I got wild comments from the "social" internet community.

It went from: "I didn't read it but I disagree"

to people telling me I'm just a random guy to randomly (moreso unfoundedly) ranting on Next.js - despite the fact that I brought facts to the table and preambling that I love using Next.js and that's why I need to critizice - cause I want it in my toolstack and I want it to be a great foundation lasting for years. Still, my love for Next.js doesn't change the fact that it's core is still a piece of complexity that is beyond acceptable - IMO.

This time let me give you even more facts and more preamble context.

TL;DR preamble

  • I'm a Web Software Architect working in the Web for 22+ years and was happy to observe every change since the ages of Netscape and "what is border-radius?"

  • Been leading huge enterprise software migrations in well-known companies

  • Complexity is normal in big projects, but there is a difference of something being complex and something being at the edge of unmaintainability. The latter can be observed and identified very easily: Take 5 different experienced people in the field, give them a few different, similiar, tasks to dig in the sourcecode and let them do it / find it for each project. The amount of time and frustration will show you if it's just big and complex or also unmaintainable.

  • I have been digging a lot of sourcecode repositories, some for contribution, some for interest, some for research. I either contribute or help in contribution (e.g. through my team/discord/slack channels) in many different repos from Mantine, Storybook, Listmonk, tabler-icons, Supabase and probably a few more which I don't remember

  • I have digged into the Next.js sourcecode for a reason to find why the request object isn't the same from middleware to page (it's the same object origin, I'm not elaborating on this again in this post) so I wanted to find out. And as part of trying to find the reason, I was utterly stunned by the amount of time I had to invest as opposed to any other repository I've digged into.

What's this about?

It's about facts and attention. It's not about ranting or hating. It's about driving attention and causing change. I love using NextJS from the perspective of using it. Most people won't go deeper than that because there's no reason for them to dip into the sourcecode. And because the result they're seeing often looks great and is well-marketed, they get the impression "they know what they're doing" - this is something I've also been told when criticizing their caching decisions. Now they finally changed it in v15 so let's just reflect this and agree that they certainly have tried their best but "they know what they're doing" didn't age well.

I need to highlight: this is normal. Things change, things are wrong, things get reverted/changed. Means: community feedback is respected if it's loud enough. That's good to hear!

Valid critique is the path for improvement. Always, at every state. And this is another post criticizing what's wrong in the vercel-driven Next.js world and what is seeking change.

Let's get into it.

The source code of Next.js is way too huge and complex and Vercel admits it

I've tried tagging Lee Robinson multiple times, so am I doing now (ping Guillermo Rauch ). I've been long enough in enterprises to know that it's all about politics and marketing to get more paying customers. The shiny, perfect videos from Vercel at every gig speak a clear language: A lot of money is spent on making things look good for everything that's seen on the outside. That's lovely, I like that. But I want equal money spent on improving the source code.

The Next.js sourcecode isn't seen on the outside. It's something that happens on the inside. It's by definition a historically grown codebase and has seen a lot of change over the last years. Now, let me tell you this: In huge projects that undergo huge breaking changes over the years I've seen most often two things:

  1. At one point of time, the project is rewritten because the code has changed so much that the complexity was unbearable or

  2. Company expects same or even faster feature implementation but due to the given complexity this is only possible by adding more flags, more code, more complexity and this is then "backed" to run safely by adding tests. And this feeling i get with Next.js' sourcecode.

I've been talking to one person at Vercel (I won't mention names here because I know how internal politics of enterprises can backfire with such things) and that person admitted my statements being right about Vercel having problems with the grown complexity of the Next.js code and that they'd be happy for "contributions". I told that person, that I, being an individual person who needs to earn money on a daily, cannot simply provide one PR to fix that. Especially, since I cannot simply work on it for weeks, then open a huge PR and they'd be like "Yeah can't merge that, had like 100 other PRs in between which would conflict". This thing has to be tackled from the inside.

Now, let's get to the facts.

The facts

As everyone complains all the time, even about facts, let me also give a preamble here: If you take multiple analysis tools you'll get multiple different results, maybe even opposing ones as metrics differ or as the final indicator based on the metrics differ. There is no tool that clearly can identify this, so we have to use our own brain and make use of the numbers.

Comparison does / is based on / respects

  • dist/ and src/compiled files are deleted before scan

  • Simlilar project features (nuxt.js, remix)

  • Similiar project complexity (I'm not comparing next.js to my one-file repo)

A few facts about Next.js in general

Next.js is a composition of React + Express server (tl;dr version). So, the crucial tools, like the server and the React rendering, were already provided by other frameworks. Why do I state this? Using one server tool and one frontend tool should be reachable with a clean structure. Adding more tools surrounding it, as well.

Within the src/ belongs source code. Next.js, has a lot of dist files within src subdirectories that are comitted. It's not clear why, it's confusing and it's convoluting but I made sure to get rid of those to not have the analysis compare dist files.

See here:

Sonarqube + Embold

Being well-known, I first initialized Sonarqube CE and analyzed Next.js amongst other repos. Obviously, the next repo consists of more packages than just the framework itself (like create-next-app) so I made sure to focus on the framework each, not the side packages.

The only frameworks for which I deleted files to really getting the "best results" are Next.js and React. In all other repos I didn't give a single damn about deleting files. I say this to highlight once more how fair I tried to be towards Next.js - even unfair towards the others. In React I deleted all files unnecessary for the actual core framework (testing files, devtools, etc, mocks, fixtures).

Besides Sonarqube, I wanted another measurement tool with maybe even completely different results to have some more numbers - I chose Embold. Unfortunately I couldn't remove any test files there as I had been using the raw fork so you have to keep that in mind.

Comparing Next, Nuxt, Remix, SvelteKit, React

If you ask "Why React?" - just to have something out of fullstack context but with huge featureset to compare with - it's not suprising React comes with a huge codebase but it is suprising that Next is bigger, core-wise.

Fun fact: In all (!) analysis processes Next.js took the longest. Even if I used whatever other repositories to compare with. Some Repos take less than a minute to analyze, Next.js, with some tools, allows you knead and bake a pizza meanwhile if the whole repository is analyzed. It's a monorepo after all but so are others.

NextNuxtRemixSvelteKit (whole Repo!)React
Lines of Code123k15k72k48k101k
Maintainability Issues2.8k / 2,3%306 / 2%541 / 0,75%307 / 0,63%3100 / 3%
Security Hotspots94 / 0,07%6 / 0,04%51 / 0,07%65 / 0,13%35 / 0,03%
Embold Ranking (5 is best, -5 is worst) -[whole Repo]-1.11/54.48/53.79/52.63/52.46/5
Embold > Medium Code Issues[whole Repo]~40k/5.3%0/0%175/0,6%374 / 1%5964 / 0,2%
Embold Worst File RatingsQuite some below -3 but be aware that this is due to it checking the duplicated compiled/ files. Besides that many files above 2.02.98 (3 HTML files, other than that, all files above 4.7)-1.37 (entry.server.jsx) but then rapidly most files being at 2.6+-0.54, then rapidly most files at 2.0+Very few benchmark files below -3, then jsx-dev-runtime below -1.3, Quite some files below 2.0

What I found surprising is the fact that Embold rated SvelteKit so bad but when I looked deeper into it, it was mainly due to low-level issues (like snake-case instead of default-case) which is why I added the \> medium issues section to accomodate for that voting.

Conclusion

The most important conclusion is: People love numbers but numbers are always as wrong / right as you present them. Even asking AI about the complexity of those framework files, whatever you chose, it responds with 4/5 because AI can't have mental space for mental load (or I wasn't prompting it right, who knows...).

So you might be asking: So was all the effort of getting those numbers worth nothing if they don't mean a thing?

Even if I highly recommend you to not give too much fucks about these numbers (especially stuff like Security Hotspot which is sometimes highlighted when potentially troublesome RegExps are used), numbers can align with other factors.

So what I urge you to do is taking those numbers and trying to understand common things you do in these frameworks. Ask yourself things like "When I create a server route, how is it rendered?"

What the numbers underline at least: Next.js is unnecessarily huge. It's hugeness alone isn't the problem if each file was easy to understand but Next.js has single files like next-server.ts which have only import statements in the first 100 lines and what comes after is nearly impossible to grasp. And I'm not just nitpicking. Sure, next.js has small files as well but there are many files so intertwined (not just "do one thing") and so context-bound, that I find it to stand out complexity-wise as opposed to all of the other frameworks above.

None of these frameworks is super-easy to get into, they're frameworks after all but understanding the files from Nuxt or Remix, was more comforting and less time-consuming as opposed to Next. And I had this feeling all throughout the repo. And this also means that it prevents people from contributing and this is something you want to avoid.