"Headless" has turned into a sales pitch. People slap it on just about any web project, as if decoupling content from its display were always progress. Most of the time it isn't: you add servers, points of failure and hours of ops for a blog that three Markdown files would have served without complaint. But there are cases where decoupling isn't a fashion, it's just the right answer. A client project I built the architecture for is a good example.
The need was twofold from the start. On one side a multilingual public site that showcases the records, one that has to load fast and rank properly. On the other an admin interface, the "manager", where the team runs the business data. Two uses, two audiences, two update rhythms. The real question wasn't "headless or not", it was this: how do these two applications share the same data without stepping on each other.
One backend, two frontends
The answer fits in a sentence: a single backend, two separate Next.js frontends. Strapi plays the headless CMS, PostgreSQL stores everything, and both apps consume the same API. The public site reads the records, the content pages and their translations. The manager reads and writes the business data. Same source of truth, two consumers that don't have to know about each other.
What this avoids is duplicating the model. If the definition of a "record" lived in two places, once in the site and once in the manager, you'd have to maintain it twice. And one day the two versions drift apart, always at the worst moment. Here the model is defined once in Strapi, both frontends inherit it. You add a field, it shows up in the API, both apps can read it. Done.
On the Next side, reading that data from a Server Component stays straightforward. No heavy client, a fetch and Strapi's JSON response.
// app/[locale]/records/page.tsx (Server Component)
async function getRecords(locale: string) {
const res = await fetch(
`${process.env.STRAPI_URL}/api/records?locale=${locale}&populate=photos`,
{
headers: { Authorization: `Bearer ${process.env.STRAPI_TOKEN}` },
next: { revalidate: 60 },
}
);
if (!res.ok) throw new Error(`Strapi responded ${res.status}`);
const { data } = await res.json();
return data;
}The revalidate caches the records on the Next side and refreshes them in the background: the visitor never pays for the round trip to Strapi in real time, and the editor sees their change appear without anyone rebuilding a thing. The if (!res.ok) isn't decoration. A remote API goes down, returns a 500, changes its schema the day of a migration. Better to throw a clear error than to quietly serve a half-empty page.
Editing without going through the developer
The second win is less technical, but it's the one that changes daily life: the client's team edits their content without calling me. Strapi generates an admin interface from the data model. Adding a record, fixing a description, publishing a translation, all of it happens in the back office, not in a pull request.
For a developer, that's almost counter-intuitive. We like keeping content in versioned files, tidy Markdown in the repo, because it's clean and it goes through code review. Except the day a non-technical team has to publish several times a week, the repo becomes a bottleneck, and you become one along with it. Strapi puts that power in their hands and takes me out of the critical path. That's exactly what I want: for nobody to need me to change a comma.
The multilingual side follows the same logic. Strapi's i18n plugin handles translations at the record level, each record exists in several languages, and the frontend asks for the right one with a plain ?locale=. The API carries the multilingual logic, the site's code only ever requests the current language.
API-first, the long-term bet
Third reason, more strategic: everything goes through the API. Today it's two web frontends. If tomorrow the client wants a mobile app for their teams in the field, or a portal aimed at a different audience, it hits the same API and rebuilds nothing on the data side.
I say this carefully, because it's the argument that gets oversold the most. "API-first, you'll be able to plug anything in later": that "later" often never comes, and you've paid the complexity up front for a future that doesn't show. The difference on this project is that the second consumer existed from day one. The manager wasn't a roadmap hypothesis, it was in scope. The API wasn't serving a need imagined "just in case", it was serving two real consumers right away. That's the whole nuance between an architecture that anticipates a real need and one that complicates itself for the pleasure of the diagram on the whiteboard.
What decoupling actually costs
Now the honest part, the one the articles selling headless forget to mention. Decoupling means multiplying the moving parts. Instead of one application to deploy and watch, you've got four that have to run together: Strapi, PostgreSQL, the public frontend, the manager frontend, not counting the hosting and the backups for all of it. Each part has its logs, its updates, its own way of falling over. The ops surface is nothing like a monolith's.
Authentication and permissions become a topic of their own. The public site reads anonymously, or through a tightly locked read-only token. The manager writes, so it needs real auth, roles, per-collection permissions. Strapi provides the machinery, but configuring it correctly, closing what should be closed and checking that a public token can't write, is work that simply doesn't exist in a monolithic app where access is controlled in the same place as rendering.
i18n adds its own layer. Handling languages at the API level is convenient, but it means thinking about the fallback when a translation is missing, keeping records in sync across languages, and testing each path in each locale. Nothing insurmountable, but these are hours that pile up and that you never bill enough for.
When I wouldn't do it
If someone had come to me for a five-page brochure site with a contact form, I'd never have reached for Strapi. A monolithic Next with content in files would have done the job, or flat-out a WordPress if the client wants to edit it themselves, for a fraction of the cost and the maintenance. Headless doesn't justify itself by the elegance of the diagram. It justifies itself by a concrete need: several frontends, a content team that has to be autonomous, or an API meant to last and grow. Tick at least one of those boxes and decoupling does you a real service. Tick none and you've bought yourself a backend to maintain for nothing.
This project ticked two boxes out of three from day one, two frontends and a team that edits. That's why I'd build the exact same architecture again without hesitating. And this blog you're reading? Precisely not. These articles are plain MDX files in the portfolio repo, no Strapi, no Postgres behind them. The right architecture isn't the most impressive one, it's the one that fits the need. And a personal blog's need fits in a folder of files.
If you're torn between a headless CMS and something simpler for your project, let's talk.
