Architecture

The Website Stack I Use: One Job for Each Layer

A practical look at how Payload CMS, MongoDB, S3-compatible storage and Next.js fit together when every layer has one clear responsibility.

Published on Oct 07, 20264 min read
Summarize with ChatGPT
Website Stack I Use_ One Job for Each Layer

What does an architecture built for change look like?

An architecture built for change gives every tool one clear responsibility and keeps each tool from taking over jobs that belong somewhere else. In my previous article on the website stack I would bet the next ten years on, I argued that we should stop searching for a stack that stays perfect for a decade. Technology will change, businesses will change, and the requirements that look important today may look different in five years. The sensible response is an architecture that can change with them, and that raises a practical question about what such an architecture contains.

We have developed a habit of describing a modern website stack by listing technologies: Next.js, React, Payload, MongoDB, Tailwind, S3, this library, that framework. The longer and more current the list sounds, the more sophisticated the website appears. Adding technology does not by itself create a better architecture, though, and sometimes it only creates more technology to maintain. What matters is whether each tool has a defined responsibility. What manages the content? Where does the data live? Where is media stored? What renders the website? How do editors publish without depending on developers? And what happens when one of those answers has to change?

Which tool is responsible for what?

Payload CMS manages the content and the publishing workflow, MongoDB stores the structured data, S3-compatible storage holds the media, and Next.js renders the public website. Tailwind, shadcn/ui, Radix and motion libraries provide the foundation for the interface. This is the stack I use, and each technology in it has a job.

The harder discipline is keeping each technology out of jobs that belong to another. A CMS that also controls the entire presentation layer, or a system in which media, content, business logic and frontend functionality depend increasingly on one another, becomes difficult to change one piece at a time. What happens five years later, when only one of those pieces needs replacing? A team in that position either replaces a component or rebuilds the website, and the answer depends on how the responsibilities were divided at the start.

A stack chosen because it is modern would repeat the technology chasing I wrote against in part one, since modern tools stop being modern. I judge this one by what it allows the business to change later. Editors get a real CMS, and the CMS does not have to become the entire website. Visitors get fast, SEO-friendly pages, and the content does not depend on those pages. The frontend and backend live in one codebase while keeping separate responsibilities. Preview, revalidation, scheduled publishing, structured content and room for custom functionality are all available when the business eventually requires them.

Should every responsibility become a separate service?

No. Separating responsibilities does not require a separate service for each one. The content system could sit in one project, the frontend in another, with several APIs between them and extra services for small tasks, and the result could still be called a modern architecture. What has been gained if a simple website then needs five systems to deploy and six places to investigate when something goes wrong?

Both extremes carry a cost. Putting everything inside one platform makes change difficult, and separating everything simply because it is possible makes everyday development difficult too. The stack needs a middle ground. Payload and Next.js can live in the same codebase, so developers do not move between unrelated projects for an ordinary change. Payload remains responsible for content, and Next.js remains responsible for presenting that content to visitors.

What happens when an editor publishes a case study?

The editor opens the CMS, changes the content, previews it and publishes, and the rest happens underneath. An editor should not need to understand React, databases, caching or server rendering to publish a case study. Behind that single action, four things happen:

  • Content is saved: Payload stores the case study as structured information in MongoDB.
  • Files are stored: uploaded images go to S3-compatible storage.
  • The website is notified: publishing tells the website that the content has changed.
  • The page is served: Next.js delivers the updated page to visitors without anyone rebuilding or manually redeploying the website.

The editor experiences one publishing process, even though several parts of the system work together to carry it out.

Why does separating responsibilities matter as the business grows?

Clear boundaries let a website grow without turning every change into a rebuild. What happens when the website grows from twenty pages to two hundred, when five editors become fifty, when the company starts publishing in several languages, or when the website has to connect with more business systems? What happens when one part needs to change while everything else is working well?

If every responsibility has been mixed together, each new requirement can reach parts of the website that should have had nothing to do with it. When the responsibilities have boundaries, the system has room to grow, and a change to one layer stays within that layer.

This is what I mean by separating responsibilities without separating the business. The tools have different jobs, while the people using them still experience one website, one content workflow and one understandable system. A stack is therefore best judged by whether each tool has a stated reason to be there and a clear expectation of what it does.