Why I'm not using Next.js
Tags: javascript, react, nextjs, opinion
Published: 2021-07-05 15:27:09

So I'm building an application. Not only am I using react, but I'm also using node. Surely I'm using next.js right? I mean, every other article I see seems to be a next.js tutorial, it's so **in** right now. Well no, I'm not using it for my project.

## n.b.
This isn't just a next.js diss. A lot of people swear by it after all! I just wanted to write down my personal thought process for not adopting it.

I really struggled to find a decent pros and cons list for using it, just pros. There are definitely downsides, frustrations, and compromises if you pick next.js. I just don't feel like anybody talks about them.

On to business:

## Super opinionated / abstracted

There's nothing wrong with opinionated stuff per se. But the problem is in the javascript ecosystem we don't really have any standards or best practices for _anything_. There are 100 ways to do _everything_. Want classes and OOP? Sure! Want a strictly FP app? Go for it! Want to separate your concerns and heavily modularise your application? Why not! Want to just mash everything into a monolithic gross swiss army knife component? I mean... I suppose...

This isn't like making an opinionated framework in an already-opinionated language like, say C#. If you try to make an opinionated framework in javascript, you're not saying "this follows and enforces the best practices of the language", you're saying "my way or the highway!"

This first point is basically relevant to every other point in this piece so I won't spend any longer on it.

## Webpack

Oh man I'm only just starting to move away from webpack and now this framework is forcing me to use it! Not only is it forcing me to use it, but it's doing its best to stop me from configuring it too.

## Weighty pages

Next effectively forces you to do all of the "stuff" at the top level pages. Anything "next-y" or "server-y" has to be done directly in your pages. This creates _all kinds_ of coupling. Not to mention your pages are also tightly coupled to your routing. This means your routing logic is tied to your page which is tied to all of your server side, data fetching, caching, everything, all tightly coupled.

I've always believed that routes should be lightweight and only concerned with which component is rendered by which route. But now I have to also stuff all of my business logic and fetching activity? No thanks.

## Server routes are hardocded to /api

Next.js forces you to mount all of your backend endpoints behind `/api/`. This goes back to my very first point about this being a super duper opinionated framework, where you can't even control your endpoints. I _really_ don't want my UI framework to get to say where my api endpoints should live in my application...

## Every fetch/mutation must be done in a "next-ey" manner

Everything has to be written in a way that accounts for 2 completely different rendering methods, so it can run on the server, but also on the client.

Once again, I can't just write my code in a way that suits my architecture or my conceived best practices.

## Exposing low level operations

"You can now directly query the database in your react app, that's so cool!" - is that cool or is it scary? This is just asking for lazy people to directly import their db connection in a component and query it. I can't imagine some of the terrible things people have done with this kind of freedom...

> I forgot I needed this bit of data. I really can't be bothered to create an abraction that retrieves the data I need in a sensible, testable context...

```ts
import { db } from '@/db';

const bitOfData = db.query(...)
```

> I'll totally come back and refactor this later.

So now we're abstracting bits that, to me, don't need abstracting, like routing; and exposing low level backend functionality that we should _definitely not_ be tying into the ui? Oh man.

## Gotchas

There are gotchas everywhere:

> For the initial page load, getInitialProps will run on the server only. getInitialProps will then run on the client when navigating to a different route via the next/link component or by using next/router. However, if getInitialProps is used in a custom \_app.js, and the page being navigated to implements getServerSideProps, then getInitialProps will run on the server.

This is just one piece of confusing documentation that has tripped up friends of mine. I hear stories from them every day about weird edge cases and gotchas and "because next says so" issues. Every day I feel more validated that I don't have to deal with these frustrations.

## BUT SEO!

SEO is not stupid - even without Next. With a page that just uses client-side rendering and has to fetch some data first, Google's page crawler is not stupid, it knows to wait for this sort of thing. As long as I'm adding the necessary meta tags I can more of less trust that SEO will still work.

The only major cost of _not_ using next.js is that I can't send contextual link previews on facebook/twitter/slack/etc. They don't process javascript so I can't dynamically add meta tags to the document.

However, I don't think rewriting my entire application from the ground up, in a framework I find constrictive, just to get this single feature, is worth it. There are many many ways around this, like pre-rendering for robot useragents.

## Conclusion

So there you go. The tl;dr is that I'm a stubborn developer and next.js isn't the boss of me!

> So if you're not using next.js I guess you'll go back to CRA then?

Nope! I've only ever used CRA once and I ejected out of it after a couple of weeks. I quickly found that I couldn't do some of the more advanced stuff I wanted to do. Decisions were taken out of my hands that I didn't want to give up. Like typescript was being transpiled by a webpack loader rather than babel, but I use several babel plugins that parse typescript annotations. I also quickly realised there was - _so_ - _much_ - _stuff_ - in the webpack config that I was just never ever going to need. I stripped out hundreds of lines of code and unneeded dependencies.

For this current project I decided to use `vite`. It's still somewhat opinionated and it's super-low config but I've not yet hit an issue I couldn't easily solve with a little config.