I respect your privacy. Unsubscribe at any time.
You’ve got a new project to work on. Or you’ve got an existing project you’re motivated to upgrade to a more modern approach. Or perhaps you’re dissatisfied with your current modern framework or second-guessing yourself and you’re investigating alternatives. In any case, you’ve got a decision to make.
There are lots of “modern” frameworks to choose from. Even if you’re not facing this choice right now, you may be trying to decide which framework to invest time learning to make yourself more marketable and productive in the future.
I’ve been using Remix since it was first released in 2020. I loved it so much I joined the company a year later to help get the community going and 10 months later I left to work on EpicWeb.dev full time where I teach people what they need to know to build full stack applications. And Remix is a big part of that. Remix is a full stack web framework and strives to solve problems faced by people building web applications–much like Next.js.
As Next.js is an alternative to Remix, people ask me why I chose Remix instead of Next.js for the framework I use when teaching full stack development on EpicWeb.dev. These people are probably facing one of those scenarios I mentioned. So this post is for those people.
I like to focus most of my time and attention on the positive side of software development. I would much rather write a post titled “Why I Use Remix” and written about the things I love about Remix (I have already done this). But a lot of people have asked me specifically about Next.js and this post is for them.
I’m not here to “bash on Next.js.” I’m just here to add an honest take of my personal perception and experience with Next.js. If you’d rather not hear negative things about Next.js, then I invite you to stop reading now, go outside, and touch some grass.
Before I go on, I need to acknowledge the fact that you’re reading this on a site that’s built with Next.js (you can check in the browser console and you’ll find a global
__NEXT_DATA__ variable rather than a
__remixContext one). This is because the EpicWeb.dev site is built by a team that has been building software with Next.js for years and they make their own decisions.
This actually gives me an opportunity to make another important point before we get started:
Whatever you use is probably fine.
Your tool choice matters much less than your skill at using the tool to accomplish your desired outcome (a great user experience).
In this post, I’m going to argue for why I won’t be using Next.js because I think Remix is a better tool for creating excellent user experiences. But that does not mean you are a failure or a bad person if you are using Next.js. (That said, I do think you would be happier and more productive if you used Remix, otherwise I wouldn’t bother writing this).
Finally, I want to mention that I’ve been an outsider to the Next.js framework for years. It’s been a long time since I shipped something with Next.js myself. But before you dismiss my opinion as uninformed, you may want to know that this article has resonated with a lot of people's actual experiences with the framework, so you'll have to dismiss all of their experience as well (I do not recommend this).
Also, I keep up with Next.js developments and hear of the experience of others. My past experience as a web developer gives me an intuition on the approach frameworks take and I can get a good sense for where a framework doesn’t align with my sensibilities.
So, with that out of the way, let’s get into why I won’t use Next.js.
The Web Platform
I’ve been deploying stuff via HTTP for over a decade. I dabbled in native development (desktop and mobile), but I really found my home on the web. I want to explain why you should care about your framework embracing the web platform with a quick story.
Years ago, I was working in React and I became dissatisfied with the de facto standard for testing my React components: enzyme. To make a long story short, I decided to build Testing Library which is now the recommended testing utility for React and other UI libraries.
One of the primary differences between enzyme and Testing Library is that while enzyme gave you a wrapper with a bunch of (overly) helpful (dangerous) utilities for interacting with rendered elements, Testing Library gave you the elements themselves. To boil that down to a principle, I would say that instead of wrapping the platform APIs, Testing Library exposed the platform APIs.
The primary benefit to this is transferability. By focusing on the standard APIs, Testing Library helps people become familiar with those APIs which helps them in their work elsewhere. And the utilities available in other tools that rely on the standard APIs integrate with Testing Library without a special adapter and vice versa.
Every library has its own APIs for things, of course. Testing Library has
findByRole for example, and you need to understand the inputs to that. But the point is that it operates directly on the DOM and returns DOM nodes back to you. Rather than wrapping the APIs, it exposes those APIs to you. It’s a balance of usefulness and transferability.
Next.js is like enzyme. Where Next.js has utilities to allow you to interact with the request, headers, cookies, etc, Remix exposes those APIs directly to you through its
actions. In Remix, these functions accept a web fetch
Request and return a
Response. If you need to understand how to return JSON with some set headers, you go to MDN (the de facto standard web platform documentation) rather than the Remix docs. There are many such examples. As you get better at Remix, you get better at the web and vice versa.
When Next.js was having trouble with static build times, instead of recommending using the web platform's Stale While Revalidate Cache Control directive, they invented a highly complicated feature called Incremental Static Regeneration (ISR) to accomplish the same goal (which they point out in their own docs accomplishes the same thing as SWR).
When I transitioned from Angular.js to React, I left a lot of Angular.js behind me. All of the time I had invested at getting really good at Angular.js felt like a huge waste. I don’t want that to ever happen to me again. So I prefer to focus on a framework that can not only give me what I want from the user experience perspective, but can also give me skills that I can use wherever I develop for the web.
Have you heard of OpenNext? If not, here’s how it describes itself:
OpenNext takes the Next.js build output and converts it into a package that can be deployed to any functions as a service platform. As of now only AWS Lambda is supported.
While Vercel is great, it's not a good option if all your infrastructure is on AWS. Hosting it in your AWS account makes it easy to integrate with your backend. And it's a lot cheaper than Vercel.
Next.js, unlike Remix or Astro, doesn't have a way to self-host using serverless. You can run it as a Node application. This however doesn't work the same way as it does on Vercel.
OpenNext exists because Next.js is difficult to deploy anywhere but Vercel. I'm not making moral judgements here. I appreciate the company’s incentives to make their own hosting offering as attractive as possible, but it’s evident that this incentive has deprioritized making Next.js easy to deploy anywhere.
I know the Netlify team spent a LOT of time getting Next.js support and keeping up with changes in Next.js. I understand that other infra hosts are the best ones to build adapters for frameworks (Vercel manages the Remix adapter). But I've consistently heard from these hosts that Next.js is particularly difficult to support and maintain.
"NextJS is easy to run anywhere"— Dax (@thdxr) October 28, 2023
this is not true - i'm getting tired of people continuing to claim this
it dismisses the tedious work that the OpenNext community is doing to actually make feasible to run it correctly outside of Vercel
here is yet another example:
I have also heard from many individuals that hosting Next.js yourself as a regular Node.js application is a huge pain as well. Interestingly when this was first published I had several people say they just throw Next.js in a docker container and call it a day. Easy peasy. And I'm glad that's worked out for them.
But part of the problem is that the line between Next.js and Vercel is very thin so if you're not deploying on Vercel, you're actually using a different framework from what's documented in the Next.js docs and it's not always clear what those differences are because Vercel isn't incentivized to invest time in that.
We can argue about whether Vercel is right or wrong about their current approach. But the fact remains that if Vercel’s pricing or other things become a problem for you, getting off of Vercel will also be a problem. It comes back down to the incentives.
And unfortunately, I keep hearing that Vercel's pricing has become a big problem for a lot of folks.
We we're quoted $40k for ▲ (after rejecting 150k for enterprise tier) for just static asset applications we wanted to move.— Zackery Griesinger (@zackerydev) October 26, 2023
Our current Cloudfront bill per month: 12$
Similarly, we were quoted $4k/month for 'Vercel Secure Compute', as we need to use VPC to be HIPAA compliant.— Roland Király (@kiralykek) October 27, 2023
Add this to the fact that Vercel is still allegedly not yet profitable (even after 8 years they're still growing aggressively, for sure, but I question their unit economics). This should give you great concern when putting all your eggs in that basket.
Remix was acquired by Shopify which has been a terrific steward of the project. The Remix team started shipping faster when they joined Shopify in large part because of the wide variety of environments where Shopify is utilizing Remix (marketing pages, ecommerce, internal and external apps, etc.). Additionally, getting acquired by Shopify has allowed the Remix team to focus all of their time and attention on the framework rather than figuring out how to leverage the framework to make money.
Next.js is eating React
My misgivings of Meta as a company always made me feel a little uneasy about Meta owning React. However, as Vercel has been hiring many of the React team members, this hasn’t really helped things for me. Ever since then, the React team has felt much less collaborative.
I know for myself, it seems like Vercel is trying to blur the lines between what is Next.js and what is React. There is a lot of confusion for people on what is React and what is Next.js, especially with regard to the server components and server actions features.
I would feel more comfortable if React belonged to an open foundation. But short of that, it would be nice at least if they were more collaborative than they’ve been since joining Vercel.
I guess you could say this is a point in favor of Next.js because at least they’re reaping the benefits of closer collaboration with React. But in my experience, a team not being collaborative is a bad sign for their software.
Redwood and Apollo maintainers have had a big problem with this lack of collaboration:
> The React canary channel is stable for frameworks like Next.js to adopt— Lenz Weber-Tronic (@phry) October 28, 2023
Be honest: This point is such a fig leaf.
The only framework that can get the insights to do so right now is Next.js, because you have the React team next door and there's no doc.
Experimenting on my users
I’m highly concerned by some questionable decisions made by the Next.js team primarily in the marketing of experimental features as stable. Features that Next.js is shipping as stable are in the canary release of React. Honestly, it’s pretty funny and also sad…
Do you know what “canary” refers to? It refers to sentinel species which are “used to detect risks to humans by providing advance warning of a danger.” So Next.js is building into itself a canary feature, calling it stable, and then sending it off to all your users effectively turning your app into the sentinel species. You may not see it this way, and maybe it’s just a messaging problem, but I’ve heard from a lot of people who have tried that their experience with Next.js’s App Router has been far from positive and I think it’s largely because of its incompleteness. They’re the canaries.
And while some people report having a great time with the App Router, I’m convinced that a lot of their enjoyment is coming from dropping the weight of the
pages directory and getting the nested routing feature, not necessarily these canary features.
For more on these problems, check this thread:
So, React recommends:— Michael Jackson (@mjackson) October 28, 2023
- you should use a framework with React
- frameworks should pin to a canary version of React in order to take advantage of the latest “stable” features that do not yet appear in a stable release
I have no idea how this is going to play out in the Remix…
Too much magic
Have you heard of the principle of least surprise? It states:
A component of a system should behave in a way that most users will expect it to behave, and therefore not astonish or surprise users.
This bit could probably exist under the “web platform” heading because the best way to avoid surprising people is by following the web platform APIs as well as possible and reducing the amount of “magic” your software does on top of that. Magic is nice, and can reduce boilerplate etc, but I want to opt-into that magic so it’s clear what’s going on rather than this happening automatically for me.
Next.js violates this principle in many ways. One example of this is the decision to override the global
fetch function to add automatic caching. To me, this is a huge red flag. And it’s decisions like this one that make me pause and wonder what else they’re doing that I would be surprised by if I decided to adopt Next.js.
Most of us learned from the MooTools days that overriding built-in features of the platform leads to problems (it’s the reason we have
String.prototype.includes instead of
String.prototype.contains). Doing this has negative impacts on the future of the web platform, and it also means that when you go to debug why something isn’t working you have to sift through the resources available to find the “Next.js version of fetch” vs “the web platform version of fetch.”
I keep hearing from people they’re finding Next.js is getting overly complex. This factors into the “too much magic” bit as well. React has server actions as well as the new experimental “taint” API which became the subject of many jokes (also where I learned the alternative definition of "taint" 🤦♂️).
I’m excited about the prospect of React adding built-in support for mutations. But I’m definitely concerned about them changing semantics of how web forms work. Each of these things increases the level of complexity.
I really appreciate that the Remix team is led by people who share my principles and will ensure once these types of features are included, they don’t go down the same road of added complexity. In fact, the Remix team is committed to reduce overall API footprint in the future rather than increase it. This leads me to my next point.
Next.js is on version 13. React Router (built by the same team as Remix) has been around for much longer and is only version 6. Remix was on version 1 for almost two years and only a month ago hit version 2. And it’s famously the most boring major version bump of a web framework ever thanks to Remix team’s emphasis on stability.
I need to acknowledge that the Next.js team has cared a lot about making upgrade paths easier with codemods. And I appreciate the need for a framework to evolve over time. But I've seen a lot of people complain about instability in what the Next.js team has pushed out in Next 13 and wrapping a canary feature and calling it stable just doesn't sit right with me.
Earlier this year, the Remix team shared their plans for getting version 2 features released as an opt-in part of version 1 using a strategy called “future flags.” This played out extremely well and a huge number of actively developed Remix apps were upgraded in less than a day.
The Remix team cares a great deal about stability. This is why they didn’t jump on the band wagon years ago and implement support for React Server Components even though everyone was asking them to. This is also why there has effectively only been a single breaking change in 8 years of React Router.
That kind of stability has a major impact on me and the apps I build. There are some libraries that I’m always terrified to upgrade because I’ve had a history of hours of confusion as I try to update all of my code to adapt to the new version. For something as impactful as a web framework, I would prefer to not have that feeling. Remix has been a gift in this regard.
You may have expected this blog post to be a comparison of the features and capabilities of Next.js vs other frameworks like Remix. But the fact is that you can build awesome things with both frameworks. I want to point out that features matter less than capabilities. I personally feel like the pit of success with Remix is wider than with Next.js, but I’m not going to go to great pains to describe why. A lot of this stuff is pretty subjective anyway.
When the Remix team rewrote the Next.js ecommerce demo to answer the “Remix vs Next.js” question, it demonstrated really well that Remix resulted in a better user experience with much less code (which is an important input in user experience). Since then, Next.js has updated it to use the App Router (which they are calling stable, but relies on canary features as I’ve already mentioned) so I think it's worth making another comparison. Remix has also learned some new tricks since that article was written, like out-of-order streaming.
You may agree or disagree with things I’ve said. You may think I’ve been unfair. You may wish I had said more or less. That’s your prerogative and I welcome you to share your opinions on my take on 𝕏, YouTube, Twitch etc. Just remember that if you dismiss my experience, you're also dismissing the experience of many others for whom this article truly resonated.
Lee Robinson (VP of DX at Vercel) posted a thoughtful response on his blog you may be interested in reading. Lee and I are friends and I admire him a lot. The post touches on many of the concerns I brought up, but doesn't satisfy my concerns personally.
I just wanted to share why I’m recommending and teaching Remix instead of Next.js so next time someone asks me I can simply point them to this article.
In short, the answer is I feel like both are highly capable frameworks, but Remix aligns better with my own sensibilities on what makes software maintainable and a joy to work with long term. I also feel like between the two frameworks, you’ll walk away from EpicWeb.dev with more transferrable knowledge than if I taught Next.js instead.
… I'm now building mostly on the NextJS stack and I still feel your class gave me the mental framework I needed to quickly ramp up and feel competent.
Foundations are everything.
So whether you’re using Next.js and plan to stay, or you’re hoping to adopt Remix, or even if you’d like to use some other web framework, my hope is that by choosing to teach Remix, I’ve equipped you to take on any challenge you face on the full stack web.
Because at the end of the day, I just want to make the world a better place by teaching you how to build quality software.