Frameworks & CMS

Headless or Traditional CMS Which Should Junior Devs Pick?

Your team needs a site where editors can publish without asking a developer for help. Should you keep content and presentation together in WordPress, or put content in Sanity and build the site with Next.js? I would default to WordPress for one website, even if the headless architecture looks cleaner. Change that default only when a second destination has a real owner and a real publishing requirement.

A coupled WordPress site wins while one website owns the content

By “coupled WordPress,” I mean that editors use the WordPress block editor and visitors receive pages rendered by the same installation. By “headless,” I mean that editors work in Sanity Studio while a separate Next.js application fetches and renders their content. These are competing approaches to the same publishing job, but they give different people control over the final page.

WordPress wins when editors need to arrange a page and immediately see roughly what visitors will see, because its editing and rendering tools share templates and blocks. Sanity plus Next.js wins when several applications must interpret the same content differently, because the content model can describe facts and relationships without prescribing a website layout. The cost of WordPress is tighter coupling between content and page design. The cost of Sanity plus Next.js is that your team must build and maintain the bridge between an editor’s draft and every rendered destination.

That distinction matters more than whether an API exists. WordPress has a REST API at /wp-json/wp/v2/, so choosing it does not prevent a small integration later. Conversely, putting a page title and a large rich-text field in Sanity does not create reusable content, because another client still has to guess what the prose means. For a site publishing articles and landing pages, I would rather give an editor usable WordPress templates than create a headless schema that merely imitates pages.

The Rise of Headless CMS in Modern Web Architecture makes the case for separating content from presentation; I disagree with treating that separation as the default for a single site, because separation also transfers preview, routing, and publishing behavior to application code. That is a reasonable trade when those capabilities serve more than one product. It is unnecessary work when the website is the only consumer anyone can name.

Ask an editor to publish a representative page before committing. Give them a headline, an image with alt text, a callout, and an internal link. Watch where they hesitate. If they need a developer to adjust the placement of an ordinary callout in your headless prototype, the architecture has already imposed a cost that a diagram will not show. If they instead keep fighting WordPress blocks to enter structured facts used elsewhere, that is evidence against my default.

Headless wins when the second destination needs structured facts

“We might build an app” is not a requirement. A stronger case is an existing app whose team has agreed to display the same product specifications or event records as the website. In that case, Sanity can store fields such as a stable ID, start time, venue ID, and translated title, while Next.js decides how to present them on the web. The app can consume those fields without parsing HTML written for a particular page.

Before choosing headless, write down what each destination will read. Use 12 representative entries as a test fixture: include a missing image, a rescheduled event, a changed URL, and an unpublished draft. Twelve is a test-design choice, not a platform limit. If both destinations genuinely need the same fields but render them differently, the shared model has a purpose. If the second destination only needs a link to the website, a shared CMS model buys little.

Model the awkward cases rather than only the happy path. Does a translated title belong to the same record as its original, or to a separate document? What happens when a referenced author is unpublished? Does a URL slug change propagate to the app, and who handles redirects on the site? Sanity’s GROQ queries can express relationships, but queries cannot decide these editorial rules for you. Record the rules before a developer turns an accidental schema choice into a permanent dependency.

WordPress can also hold structured content through custom post types and registered REST fields. It may therefore beat Sanity even with a second consumer if your editors already have a working WordPress workflow and the shared data is modest. I would not move an established site to Sanity merely to expose its articles through an API, because the WordPress REST API may already supply what that consumer needs. Migration becomes defensible when the existing content shape forces repeated parsing, duplicated entry, or conflicting edits—not when “headless” appears on a roadmap.

Top Frameworks and CMS for Modern Web Development is useful for naming possible tools, but a framework comparison cannot tell you who will repair a broken editorial preview. Pick the team responsible for that repair before picking Next.js. Otherwise, a small content correction can become a release request that sits behind unrelated application work.

Preview and cache invalidation expose the real operating cost

A published-content API demo is easy; a trustworthy draft-to-live path is harder because drafts and public pages have different access rules. In coupled WordPress, an editor can normally preview through its authenticated interface. In Sanity plus Next.js, your team must connect Sanity Studio’s preview action to a protected Next.js Draft Mode route, fetch draft content with appropriate credentials, and keep that response out of public caches. A draft preview that shows last week’s published text is worse than no preview because it invites approval of the wrong page.

Next.js caching also needs an explicit publishing contract. For each content type, specify what a publish, edit, unpublish, and slug change should invalidate. A Sanity webhook can notify a Next.js route, and Next.js cache tags or route revalidation can refresh affected pages. Authenticate the webhook, because an unauthenticated revalidation endpoint lets outsiders trigger work. Plan for retries and out-of-order events, because delivery timing can otherwise make an older update appear newer than the editor’s latest change.

Start with a target of 5 minutes from clicking “publish” to seeing the change on the public URL; this is a value to tune with your editors, not a promise supplied by either CMS. Test unpublishing separately, because leaving a removed page in a CDN cache is a different failure from showing a new page late. Cloudflare cache rules, Next.js caching, and the CMS webhook all need to agree on which response can remain public. For private previews, verify that authentication is required and that responses cannot be indexed; an X-Robots-Tag header is a safeguard, not a substitute for access control.

Here is a small published-content check you can run with Node.js 20 or later. It uses the WordPress REST API, requests only the fields it needs, and fails on an empty or unsuccessful response:

const endpoint = process.env.WP_API ||
  "https://wordpress.org/news/wp-json/wp/v2/posts";
const url = new URL(endpoint);
url.searchParams.set("per_page", "1");
url.searchParams.set("_fields", "id,slug,modified_gmt,link");
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const posts = await response.json();
if (posts.length === 0) throw new Error("No published posts");
console.log(posts[0]);

Save it as check-content.mjs and run node check-content.mjs; set WP_API to test your own public endpoint. WordPress’s REST API documentation puts the usual per_page maximum at 100, a vendor-published limit that matters if you later build a migration script. This check does not prove that draft preview works or that a webhook reached Next.js. Its narrower job is to catch a broken published endpoint or a changed response shape before someone mistakes an API screenshot for a complete publishing system.

Choose with a publishing rehearsal, not a feature checklist

Give each option the same rehearsal: create an entry, preview it, publish it, correct it, change its slug, and unpublish it. Have an editor perform the editorial steps while a developer observes rather than intervenes. Record elapsed time and every point where a developer must act. If your measured rehearsal takes 40 seconds to preview in one setup and 2 minutes in the other, those are observations about your prototypes, not general benchmarks for WordPress or Sanity.

Then price the work you actually found. For coupled WordPress, include block or template changes, redirect rules, and any custom REST fields your second consumer needs. For Sanity plus Next.js, include schema changes, preview authentication, webhook handling, cache invalidation, and tests for unpublished content. Count editorial friction too: a developer hour saved is not a win if every routine update now requires an editor to ask what a field means.

Choose WordPress when the website is the primary destination, editors arrange pages directly, and the API needs are small enough to state precisely. Choose Sanity plus Next.js when two maintained destinations already require the same structured facts and their owners will test changes to that shared model. If the result is close, keep the coupled system: the headless option should win by solving a demonstrated coordination problem, because it adds a publishing path your team must own.

Run one real content change before choosing a stack

Tomorrow, take a page that editors expect to change this month and write down every step from draft to public URL, including preview and correction. Repeat the steps in both prototypes with the same editor and the same content. Keep the timings and the questions they ask. That small record gives your team a decision grounded in publishing work rather than architectural preference.