If you’re new to the idea of a “headless CMS”, then you’ve been living under a rock it is a way of building applications that uses APIs to retrieve the data it needs to render your content.
As you might already know, websites would traditionally server-render their content on-the-fly and couple the rendering layer (the markup) with the data itself. And unless you wired in something like Cloudflare to cache your API requests, this meant that not only was your server working overtime, but you were also locked into something like PHP to build interfaces.
OK, now back to 2023. For the past handful of years, decoupling the API layer meant that we could build in any framework on any platform by sharing APIs. This means that we can ship entirely separate applications in isolation from one another, from static Next.js sites to React Native apps.
To no one’s surprise, Payload has gone all-in with the idea of headless CMS. We love the idea of giving front-end developers the tools they need to build apps using their framework of choice, and do it quickly. But not every headless setup is the same.
Some of them are wildly different.
Ultimately, the requirements of your project should determine which setup you choose, but it can be confusing to know where to start.
This post will guide you through these concepts at a high-level, provide you some fully working examples to learn from, and most importantly, give you some production-ready templates to jumpstart your next project.
Payload supports every kind of headless setup and includes authentication, for free, with one line of code. These are concepts that apply to any front-end framework, not just Next.js.
At a high level there are three main ways of using Payload in a headless capacity:
This has long been the standard for myself and many other developers: deploy Payload on one server, deploy Next.js on another, then connect the two through network requests.
This is probably the easiest way of understanding the relationship of your front-end and its headless CMS. You could even host these applications entirely separately from one another.
For example, when Vercel began offering front-end hosting solutions, this meant that our static web pages could be served through their global network and cached on their CDN. Our Payload instance would be completely isolated, running entirely on its own infrastructure, with relatively low CPU usage.
Now fast forward a few years and a few dozen sites, and we’re admittedly exhausted with this setup.
Don’t get me wrong, this is a great situation if you’re maintaining a small number of projects. But deployments are tedious at scale this way because they need to be done in sync. Changes to the CMS need to be deployed before the front-end is. Made a change to your GraphQL schema? Forget about it. Whole things breaks. Good thing we’ve deployed statically and that Vercel has instant rollbacks.
I’m exaggerating here, but only slightly. This can be a serious pain. Plus when deploying this way, there are additional security hoops to jump through because your site is accessing your API across domains and ports. It’s not the end of the world, especially if this is your only site, but it does require a bit of brain power up front.
That being said, most of our examples showcase this setup (listed toward the end of this post) and I’ll continue recommending it.
So while deploying separately might be a great choice for many projects, using a combined setup has become increasingly more popular over time, and for good reason. Here’s why:
When you use Payload, you plug it into your server (Next.js, Express, etc.). That's a fundamental difference between Payload and other application frameworks. It means that when you use Payload, you're technically adding Payload to your app, and not building a "Payload app".
Let that sink in for a moment.
This means you can integrate your Payload instance directly with your front-end on the same server. This means:
That last point is huge. This setup makes it possible for you to use Payload Local API directly on your front-end. Unlike fetch
requests, you don't need to deal with server latency or network speed whatsoever and can interact directly with your database.
Check out the official Custom Server Example to see exactly how this is done using the Next.js App Router.
It's very popular right now to deploy serverlessly, or even on the Edge. While Payload is not fully on the Edge (yet), we did go serverless in Next.js and now...Next.js native (see update below).
This is a big win for so many developers.
You get all the benefits of a combined setup, plus some. However, it's not for everyone, and below I've outlined why (but first, let’s back up to explain what “serverless” means): Instead of spinning up a long-running Payload app that is always open, and always listening for incoming requests to its API, we spin up Payload fresh every single time we need it.
What this ultimately means is that when you visit /admin
, for instance, Payload may or may not be running, and may require a moment to boot. This means that lightly used sites may experience the infamous “cold start", a known caveat of this pattern.
But if your app receives regular, heavy traffic, this might still be a great option for you. Or if your site is statically generated, your users may never even notice. Plus, some platforms are hard at working constantly minimizing boot times.
This pattern allows you to deploy your entire stack to Vercel, including Payload (see update below).
Now let’s talk about the front-end stack. React has pretty much swept the floor in terms of popularity in recent years. While there have always been some great alternatives out there, this has always been the single most important deciding factor in our adoption of it.
And long before Payload, way back when React was class-based and before Vercel had been coined, we were server-rendering React and hydrating the client ourselves.
This is why we value Next.js so highly. We no longer had to jump through these hoops because Next.js had our backs. To us, getServerSideProps
was like magic.
Then just like that they shipped support for static site generation which immediately became a game-changer for us. Over night, getStaticProps
became our best friend. This meant that our sites could now generate HTML at build time, passing huge savings in server bandwidth onto us. We implemented incremental static revalidation, on-demand ISR, preview mode, the works (examples below). And for smaller sites, this also meant that we no longer had a strong need to implement API-level caching.
Plus, our websites felt like they loaded at light speed ... and we’d casually take all the credit. 😉
Now the tides are turning once again and we’re all for it. We’re really coming around to the idea of React Server Components and we’re leaning into it big time. For some in the Next.js world, the move from the Pages Router to the App Router has proven to be a learning curve, and admittedly it has for us too.
This change was especially difficult to embrace because it was a complete paradigm shift (and the page router was that good). But looking back, this is sort of how I felt when React Hooks was first introduced. Once I got the hang of it, it made total sense. I was really only upset because I had to learn something new and that it took away from real paying work. Real concerns, but this has since payed dividends in site speed and DX.
Ultimately shipping less JavaScript to the browser is in everyone’s best interest and this is why Payload continues to recommend Next.js. But React Server Components mean more to Payload than this. They've enabled Payload to finally split the config clearly between server-only and client-friendly code (see update below).
Payload 3.0 is now "Next.js native". After receiving huge support, Payload's admin panel and http layer are now built on top of Next.js itself. If you are not a Next.js developer, no worries, you might barely notice the move. There are no breaking changes in our API and the interface is no different.
But to everyone else, this change makes all the difference
This means that you can now deploy Payload serverlessly to Vercel out of the box. And you can now use the Payload Local API in custom components. You no longer have to alias server-only code, and our team no longer has to maintain a bundler pattern.
If I haven’t made this clear enough, there are many different ways of integrating your front-end with Payload. But we don’t want to lock you into a box or force you to stay up all night pulling your hair out trying to get all the latest and greatest features in place, especially when it comes to Next.js.
This is exactly why we built the Examples Directory.
If you’re working with Next.js and Payload in any capacity, we probably have an example for that:
App Router:
Pages Router:
Or if you’re interested in Vercel Visual Editing Integration, we’ve got that, too.
The Examples Directory is constantly changing, and we’re always looking to showcase features from all different frameworks. If you’re looking to become a Payload contributor, this is a great place to start.
If you’re working on a feature or framework that is not yet demonstrated, please consider contributing your work for others to see.
OK, phew, that was a lot.
If you’ve tuned out until now, good news, there’s an option for that 😊. We have fully-featured, end-to-end templates which put together all the best practices to the best of our ability. Each is built in the same way we’d build an application for ourselves, making all the right decisions so you don’t have to. But just like with examples, this is an ongoing effort.
We’re constantly adding new features, making improvements to our templates, as well as making new ones.
It’s possible to build all kinds of other applications with Payload too, from CRM to LMS and everything in between.
If you read this post and are still lost then I have failed you, but I’d love to know about it. Please reach out with any questions you might have. Or better yet, publicly shame me on X.