Should We Move to Next.js?

Published On
Payload on Next.js
Payload on Next.js
Today, I gave a talk at Next.js Conf about something I've been considering for years. I'm going to recap it quickly in this post.

Payload just released 2.0, and with it we shipped many of the most important features that our community has requested over the past year since going open source in May 2022.

We've added a lot of stuff and honestly I think we are already punching above our weight feature-wise. But one thing that I've been constantly thinking about is that new features are great, but what's better is simplicity. I think what people like about Payload is that you don't really have to learn a whole lot about Payload-specific APIs.

It's really just TypeScript and React.

And speaking of simplicity, I have a certain "utopia" in my head that I can't shake. I have been thinking a lot about why I wanted to build a headless CMS in the first place, and I have been going down memory lane remembering some of the points I'd use to "pitch" my agency clients on the headless approach.

I'd say things like "separation of concerns" and "omnichannel content" but looking back, those were just the value props that I was parroting from the headless CMS vendors that I was using.

At the end of the day, I just wanted to build React apps.

I wanted frontend freedom. That's it.

What's next for Payload?

We're never going to stop shipping new features. We've got a lot of new stuff on the radar that we'll talk about soon, and our roadmap is going to be updated to speak to these next week.

But the most important thing I think Payload needs to do as a next step is to promote simplicity and a beautiful developer experience as much as possible.

To optimize our DX, we have a few difficult technical challenges to overcome.

Webpack aliases

Time to air my dirty laundry.

The No. 1 thing that frustrates me while working with Payload is having to use Webpack aliases to separate server-side code from the admin UI bundle.

This was a stop-gap solution that we implemented, which works, but it's unwieldy and it hampers efficient development.

We want to remove our reliance on Webpack aliases entirely. But how?

We've evaluated lots of different approaches to separating server-side code and client-side code from the Payload config.

We've considered building a "loader" or custom build process to parse the Payload config as AST (abstract syntax tree), and dynamically remove server-side properties like hooks, access control, etc. If we could remove those properties, we could then remove unused imports—and come up with a "built" version of the Payload config that's safe for browsers without the need for aliases.

We could also implement something similar to the use server directive and dynamically omit code that is tagged with that string. Again, here, we'd need a custom compiler.

But each of these approaches require a lot of mental overhead, and would be a lot of work to maybe pull off.

I never even want to think of bundlers

In 2.0, we released Vite support, and I totally respect why the community has asked for Vite. It speeds up the development process and is a more modern bundler when compared to Webpack.

But I honestly do not think that Vite is the end-all-be-all. It's got an entirely different paradigm than Webpack and relies on completely separate approaches for dev and prod builds.

If you want to cry with your browser, you should open up your inspector's Network panel when you load a React app built with Vite.

This can't ever be the end state in my eyes.

But Payload is a headless CMS. I don't want to focus on bundling. I don't want to even think about bundling. I want to build the most powerful Payload that I can, and that means concentrate on things that make us who we are.

I think that if we kept with our current bundler adapter approach, we could "let the best bundler win" over time, which is great. But what would be better is if we could completely remove the need to care about any of this and leave it to tooling where this is their job.

We want to implement server-side HMR

Right now, Payload projects typically ship with Nodemon configured, which automatically restarts the server when the Payload config is changed. This is nice, but it could be better.

We had a community PR open up that automatically re-mounts Express endpoints, rebuilds models, and effectively offers server-side HMR without the need for Nodemon.

That's super cool. It could work. But like bundlers, this feels like a problem that is both solved, and should be solved outside of our own concerns as a CMS / app framework.

I want not only client-side HMR, but also server-side HMR.

Getting simple

If we could solve the above three aspects of Payload, the DX would shine even more than it does now. The entire Payload development experience would become significantly simpler, and you could concentrate on what you're building rather than doing Webpack gymnastics and waiting for the server to restart when you make a change.

Speaking of getting simple, the vast majority of apps being powered by Payload are Next.js.

We maintain a series of official boilerplates that demonstrate how to deploy Next apps alongside of Payload, and admittedly, they could be simpler. I would love to see how far we could simplify the relationship between Payload and Next.js.

Next.js has some answers here

Wait a second.

Next.js literally solves all of the above concerns of mine. I trust in the future of Next.js, and I see that it's currently by far the most popular frontend framework that exists. I don't think that's going to change any time soon, and I for one am excited by the growing relationship between both React and Next.js.

When I first heard of server components, I thought that we were really getting into the weeds about saving a couple hundred KBs of JavaScript when I'm going to turn around and serve a megabyte or more of images.

But recently it dawned on me, and this might seem obvious, but server components literally exist to clearly distinguish the line between the server and the client in React apps. A solution like this is exactly what I am searching for when I look to remove our reliance on Webpack aliases.

Also, Next.js already has server-side HMR. All functions are rebuilt dynamically as separate bundles, and there is no Nodemon in Next apps.

And many of you have heard me talk about this before, but regarding bundlers, I think the future is going toward Rust. I love the idea of Turbopack and I love the fact that I don't have to build it even more. If we could automatically inherit all of the future development that Next.js is inheriting from the work being done on Turbopack, out of the box, that would be a huge win.

What if we moved to Next.js?

Right now, Payload plugs into an Express app and is basically a bunch of Express middleware. Our admin UI is a vanilla React app that has no element of server-side rendering. It's all just straight up client-side JS.

But we're rolling a lot of this from scratch, and while it's nice, we're on our own for bundling, separating server / client code, HMR, and lots more.

The more I think about this, the more I think it's a universal win for everyone building on Payload. It doesn't matter if you're using Remix, Nuxt, Sveltekit, or anything else—I think you would universally benefit from Payload moving to Next.

We'd still be headless. Our config would barely change. All of our API endpoints would remain exactly as they are. The Payload Local API would remain the same.

But it would become significantly easier to deploy Payload and we could alleviate many of the different Next.js + Payload boilerplates that we maintain now, ensuring a much higher degree of consistency and quality for Payload + Next apps.

We would also be able to more easily maintain first-party boilerplates for other frontend frameworks because we won't have to keep up with so many different Next approaches.

Next.js + Payload = Holy Shit

You probably already know that I'm on the Next.js train. I've been building Next apps since Next came out and I feel the same way about it that I do about React in general—it does what I need it to do and it stays out of my way.

A vast majority of Payload projects are built on Next, and if we moved to native Next.js, you could so much more easily build Next + Payload apps. Everything would be in one repo. Payload endpoints would be opened up directly inside of your Next app folder, right alongside your frontend.

Everyone that deploys a Payload app could deploy their whole stack to Vercel serverlessly, or maybe even at the edge.

We could build a suite of automatic connections for things like CMS redirects, metadata, forms, and more.

You'd never have to write another Webpack alias ever again.

As the Next.js compiler gets faster, Payload would get faster for free.

And if you still wanted to deploy as a traditional Express server, you could, thanks to Next.js custom server capabilities.

Honestly, I can't really see any drawbacks and the idea excites me to my core.

We want your feedback

What do you think? Again, I don't think this would have a single negative repercussion to you if you're using other frontend frameworks. This is about Payload at its core, and you will still be able to leverage it completely headless just as you do now with Sveltekit, etc.

But we are open source and our community is everything. We want to understand what you think.

Hop in our Discord and let us know. We'd be happy to chat about this further, and we're going to have a live Discord event this coming Monday to beat this idea up with our community involved, in realtime.