Ik bouw al jaren websites met headless CMS-platforms. Contentful, Sanity, Strapi -- je kent het wel, ik heb het allemaal geïntegreerd. Maar ergens eind 2025 zag ik een patroon ontstaan: voor een steeds groter aantal projecten grepen ik helemaal niet naar een CMS. In plaats daarvan leverde ik full-stack apps af met Next.js op Vercel, Supabase voor data en authenticatie, en de Claude API voor de content intelligence die ik nodig had. Geen CMS-leverancier. Geen content modeling UI. Geen maandelijkse seat-licenties.

Dit gaat niet om eigenwijs zijn. Er zijn absoluut projecten waar een headless CMS de juiste keuze is -- we bouwen er genoeg van bij Social Animal (bekijk ons headless CMS development work). Maar er is een specifieke klasse van applicaties waar deze vendor-vrije stack niet alleen haalbaar is, maar beter. Laat me je precies laten zien hoe het werkt, wanneer je het moet gebruiken, en hoe je het van nul opzet.

Inhoudsopgave

Waarom ontwikkelaars de CMS loslaten

Laten we eerlijk zijn over wat een CMS je geeft: een UI voor niet-technische mensen om content te bewerken, een gestructureerde data layer, misschien wat mediabeheer, en een API om het allemaal op te halen. Dat is echt waardevol als je een marketingteam hebt dat elke dag blogposts publiceert.

Maar hier is wat ik steeds zie in 2026:

  • SaaS-producten waar de "content" eigenlijk door gebruikers gegenereerde data is, geen redactionele kopij
  • Interne tools waar het team technisch genoeg is om rechtstreeks een database te bewerken of een lichte admin panel te gebruiken
  • AI-native applicaties waar content on-the-fly wordt gegenereerd, samengevat of getransformeerd
  • Startups die niet $300-500 per maand voor een CMS kunnen rechtvaardigen wanneer ze drie gebruikers hebben

Voor deze projecten is een CMS overhead. Je betaalt voor content modeling-functies die je niet gaat gebruiken, beheert API-sleutels voor een service die eigenlijk gewoon een fancy databasewrapper is, en deelt je om met webhook-complexiteit om alles gesynchroniseerd te houden.

Het alternatief? Eigenaar van je data layer worden. Supabase geeft je Postgres (een echte database, geen propriëtaire content store), authenticatie, bestandsopslag en real-time subscriptions. Claude handelt de intelligence layer af. Next.js en Vercel regelen de rest.

De stack in een oogopslag

Laag Technologie Rol Prijsstelling 2026 (startend)
Frontend & API Next.js 15 (App Router) UI, Server Components, Route Handlers Gratis (open source)
Hosting & Edge Vercel Deployment, CDN, Serverless Functions Gratis tier / $20/ma Pro
Database & Auth Supabase Postgres, Row Level Security, Auth, Opslag Gratis tier / $25/ma Pro
AI Layer Claude API (Anthropic) Content generatie, samenvatting, classificatie Betaal-per-token (~$3/$15 per 1M tokens voor Sonnet 4)
Admin UI Custom (React + Supabase) Content management voor je team $0 (je bouwt het zelf)

Totale kosten voor een productie-app met matig traffic: $45-100/maand. Vergelijk dat met een typische headless CMS-setup waar alleen de CMS je al $99-500/maand kan kosten voordat je zelfs maar voor hosting betaalt.

Je Next.js-project op Vercel instellen

Ik ga ervan uit dat je Node.js 20+ en een Vercel-account hebt. Als je nieuw bent bij Next.js, heeft ons team uitgebreid erover geschreven op onze Next.js development capability page.

npx create-next-app@latest my-app --typescript --tailwind --app --src-dir
cd my-app

Next.js 15 met de App Router is hier de basis. We gebruiken standaard Server Components, wat betekent dat het meeste van onze data fetching op de server gebeurt -- geen exposed API-sleutels, geen client-side loading spinners voor initiële content.

Hier is mijn typische projectstructuur voor deze stack:

src/
├── app/
│   ├── (public)/           # Marketingpagina's, blog
│   ├── (dashboard)/        # Geverifieerd admin area
│   │   ├── layout.tsx      # Auth check wrapper
│   │   ├── posts/
│   │   ├── media/
│   │   └── settings/
│   ├── api/
│   │   ├── ai/             # Claude API routes
│   │   └── webhooks/       # Supabase realtime hooks
│   └── layout.tsx
├── lib/
│   ├── supabase/
│   │   ├── client.ts       # Browser client
│   │   ├── server.ts       # Server client
│   │   └── admin.ts        # Service role client
│   ├── claude.ts           # Anthropic SDK wrapper
│   └── utils.ts
├── components/
└── types/

Omgevingsvariabelen

Je hebt deze nodig in je .env.local:

NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key
ANTHROPIC_API_KEY=sk-ant-...

Deployment naar Vercel

Push naar GitHub. Verbind de repo in Vercel. Voeg je env-variabelen toe. Klaar. Ik ga dit niet uitbreiden -- de DX van Vercel voor Next.js deployment is het beste in de industrie en je kent het waarschijnlijk al.

Een ding dat de moeite waard is om op te merken: gebruik Vercel's Edge Config als je feature flags of configuratie nodig hebt die zonder herimplementatie update. Het is een kleinigheid maar het vervangt nog een SaaS-tool.

Supabase als je backend: auth, database en opslag

Hier gebeurt de magie. Supabase is niet gewoon "Firebase maar Postgres" -- het is een volledig backend-platform dat je eigenlijk bezit. Je data leeft in een standaard PostgreSQL database. Als je ooit wilt vertrekken, pg_dump je en je bent weg. Probeer dat maar eens met een propriëtaire CMS.

Databaseschema

Laten we zeggen dat je een content-driven app bouwt (het soort waarvoor je normaal naar een CMS zou grijpen). Hier is een schema dat articles, media en basis-taxonomie aanhandelt:

-- UUID-generatie inschakelen
create extension if not exists "uuid-ossp";

-- Content table (vervangt je CMS content model)
create table public.posts (
  id uuid default uuid_generate_v4() primary key,
  title text not null,
  slug text unique not null,
  body text, -- Markdown content
  excerpt text,
  status text default 'draft' check (status in ('draft', 'published', 'archived')),
  author_id uuid references auth.users(id),
  featured_image text, -- Supabase Storage pad
  metadata jsonb default '{}', -- Flexibele velden, geen migratie nodig
  published_at timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- Tags / taxonomie
create table public.tags (
  id uuid default uuid_generate_v4() primary key,
  name text unique not null,
  slug text unique not null
);

create table public.post_tags (
  post_id uuid references public.posts(id) on delete cascade,
  tag_id uuid references public.tags(id) on delete cascade,
  primary key (post_id, tag_id)
);

-- Row Level Security
alter table public.posts enable row level security;

-- Iedereen kan gepubliceerde posts lezen
create policy "Public can read published posts"
  on public.posts for select
  using (status = 'published');

-- Geverifieerde gebruikers kunnen hun eigen posts beheren
create policy "Authors can manage own posts"
  on public.posts for all
  using (auth.uid() = author_id);

Die metadata jsonb kolom is essentieel. Het geeft je de flexibiliteit van de aangepaste velden van een CMS zonder dat je elke keer een migratie hoeft uit te voeren wanneer het marketingteam een nieuw veld wil. Je hebt een SEO-beschrijving nodig? metadata->>'seo_description'. Moet je een Open Graph afbeelding overschrijven? metadata->>'og_image'. Het is schemaloze waar je flexibiliteit nodig hebt, gestructureerd waar je integriteit nodig hebt.

Auth-setup

Supabase Auth handelt alles af. Email/wachtwoord, magic links, OAuth met Google/GitHub -- het is allemaal ingebouwd.

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

Bestandsopslag

Supabase Storage vervangt de mediatheken van je CMS. Maak een bucket genaamd media aan, stel een policy in, en je hebt een S3-compatibele bestandsopslag met automatische CDN-URL's.

// Bestand uploaden
const { data, error } = await supabase.storage
  .from('media')
  .upload(`posts/${slug}/${file.name}`, file, {
    cacheControl: '3600',
    upsert: false,
  })

// Openbare URL ophalen
const { data: { publicUrl } } = supabase.storage
  .from('media')
  .getPublicUrl(`posts/${slug}/${file.name}`)

De Claude API integreren voor content intelligence

Dit is waar de 2026 stack het meest afwijkt van traditionele webontwikkeling. De Claude API is niet gewoon een chatbot -- het is een intelligence layer die hele categorieën van CMS-plugins en third-party services kan vervangen.

Hier is waarvoor ik het in productie gebruik:

  • Automatisch SEO-metadata genereren van post content
  • Content samenvatten voor excerpts en social cards
  • Content classificeren en automatisch taggen
  • Slim zoeken dat begrijp en intentie, niet alleen sleutelwoorden
  • Draft-assistentie voor content auteurs

De Anthropic SDK instellen

npm install @anthropic-ai/sdk
// lib/claude.ts
import Anthropic from '@anthropic-ai/sdk'

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!,
})

export async function generateSEOMetadata(content: string, title: string) {
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    messages: [
      {
        role: 'user',
        content: `Given this article title and content, generate SEO metadata.

Title: ${title}
Content: ${content.slice(0, 3000)}

Respond with JSON only:
{
  "seo_title": "50-60 char title with primary keyword",
  "seo_description": "120-160 char meta description",
  "excerpt": "1-2 sentence hook for social sharing",
  "suggested_tags": ["tag1", "tag2", "tag3"]
}`,
      },
    ],
  })

  const text = message.content[0].type === 'text' ? message.content[0].text : ''
  return JSON.parse(text)
}

export async function classifyContent(content: string, existingTags: string[]) {
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 256,
    messages: [
      {
        role: 'user',
        content: `Classify this content into the most relevant tags from the existing list. You may suggest up to 2 new tags if nothing fits.

Existing tags: ${existingTags.join(', ')}

Content: ${content.slice(0, 2000)}

Respond with JSON: { "tags": ["tag1", "tag2"], "new_tags": ["maybe-new"] }`,
      },
    ],
  })

  const text = message.content[0].type === 'text' ? message.content[0].text : ''
  return JSON.parse(text)
}

API-route voor AI-functies

// app/api/ai/seo/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import { generateSEOMetadata } from '@/lib/claude'

export async function POST(request: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { content, title } = await request.json()
  
  try {
    const metadata = await generateSEOMetadata(content, title)
    return NextResponse.json(metadata)
  } catch (error) {
    return NextResponse.json(
      { error: 'AI generation failed' },
      { status: 500 }
    )
  }
}

De kosten hier zijn verwaarloosbaar. Een typische SEO metadata generatie-aanroep gebruikt misschien 4.000 input tokens en 200 output tokens. Met Claude Sonnet 4's prijsstelling van ongeveer $3/1M input tokens en $15/1M output tokens, komt dat neer op ongeveer $0,015 per aanroep. Je zou metadata voor 1.000 artikelen voor $15 kunnen genereren.

Een aangepaste admin interface bouwen

Dit is het gedeelte dat mensen nerveus maakt. "Als ik geen CMS heb, hoe kunnen niet-technische mensen content bewerken?"

Je bouwt een eenvoudige admin UI. En in 2026 is "eenvoudig" eigenlijk eenvoudig. Hier is een basiscomponent voor post bewerking:

// app/(dashboard)/posts/[id]/editor.tsx
'use client'

import { useState } from 'react'
import { createBrowserClient } from '@supabase/ssr'

export function PostEditor({ post }: { post: Post }) {
  const [title, setTitle] = useState(post.title)
  const [body, setBody] = useState(post.body || '')
  const [saving, setSaving] = useState(false)
  
  const supabase = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )

  async function save() {
    setSaving(true)
    const { error } = await supabase
      .from('posts')
      .update({
        title,
        body,
        updated_at: new Date().toISOString(),
      })
      .eq('id', post.id)

    setSaving(false)
    if (error) alert('Save failed: ' + error.message)
  }

  async function generateSEO() {
    const res = await fetch('/api/ai/seo', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title, content: body }),
    })
    const metadata = await res.json()
    // Pas gegenereerde metadata toe op de post
    await supabase
      .from('posts')
      .update({ metadata, excerpt: metadata.excerpt })
      .eq('id', post.id)
  }

  return (
    <div className="max-w-4xl mx-auto p-6">
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        className="text-3xl font-bold w-full mb-4 border-b pb-2"
      />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        className="w-full h-96 font-mono text-sm p-4 border rounded"
      />
      <div className="flex gap-4 mt-4">
        <button onClick={save} disabled={saving}
          className="px-4 py-2 bg-blue-600 text-white rounded">
          {saving ? 'Saving...' : 'Save Draft'}
        </button>
        <button onClick={generateSEO}
          className="px-4 py-2 bg-purple-600 text-white rounded">
          ✨ Generate SEO Metadata
        </button>
      </div>
    </div>
  )
}

Ja, dit is een eenvoudige textarea. In een echt project zou je iets als Tiptap, MDXEditor of BlockNote gebruiken voor rich editing. Het punt is: de admin interface is jouw code. Je controleert elke pixel, elke workflow, elke toestemming. Geen gevecht met beperkingen van de CMS UI.

Voor complexere projecten, overweeg Refine of AdminJS als admin panel frameworks die rechtstreeks in Supabase pluggen. Ze sparen je weken.

Werkelijke kosten: wat deze stack je werkelijk kost

Laten we specifieke getallen geven voor een content-heavy site met 100K pageviews/maand:

Service Tier Maandelijkse kosten Wat je krijgt
Vercel Pro $20 1TB bandbreedte, 1000 GB-hrs serverless
Supabase Pro $25 8GB database, 250GB bandbreedte, 100K auth gebruikers
Claude API Betaal-per-gebruik ~$10-30 ~5M tokens/maand (SEO gen, samenvattingen, zoeken)
Domain Jaarlijks ~$1 .com domein
Totaal $56-76/ma

Vergelijk dit nu met een typische headless CMS-stack:

Service Tier Maandelijkse kosten
Contentful Team $300
Vercel Pro $20
Algolia (zoeken) Build $50
Auth0 (auth) Essentials $35
Totaal $405/ma

Dat is een verschil van 5-6x. En de Supabase stack geeft je meer flexibiliteit, niet minder.

Wanneer je nog steeds een CMS moet gebruiken

Ik wil hier helder over zijn. Gooi je CMS niet voor elk project weg. Een headless CMS is nog steeds de betere keuze wanneer:

  • Grote redactionele teams gestructureerde workflows nodig hebben (goedkeuringskettingen, planning, rollen die verder gaan dan basis-RBAC)
  • Content het product is -- uitgevers, mediabedrijven, documentatie sites met honderden medewerkers
  • Je visuele bewerking nodig hebt -- sommige CMS-platforms bieden live preview en visuele builders die maanden in beslag zouden nemen om na te bootsen
  • Multi-channel delivery -- als dezelfde content een website, mobiele app, digitale signage en e-mail voeden, verdient het content model van een CMS zijn geld
  • Lokalisatie op schaal -- CMS-platforms als Contentful en Sanity hebben volwassen i18n workflows

We bouwen nog steeds genoeg headless CMS-projecten bij Social Animal. Als dat wat je project nodig heeft, neem contact met ons op. Maar voor de groeiende categorie van apps waar het niet wat je nodig hebt, stop met ervoor betalen.

Productie-deployment checklist

Voordat je deze stack naar productie stuurt, loop door deze lijst:

  • Row Level Security policies getest voor elke table (Supabase's policy simulator helpt hierbij)
  • Rate limiting op Claude API routes (gebruik Vercel's @vercel/edge rate limiter of upstash/ratelimit)
  • Input validatie op alle API routes (Zod is je vriend)
  • Error boundaries in je React tree voor AI-fouten (Claude zal soms timeout)
  • Caching strategie -- gebruik unstable_cache of revalidateTag in Next.js voor database-backed pagina's
  • Monitoring -- Vercel Analytics voor performance, Supabase Dashboard voor database metrics, Anthropic Console voor API-gebruik
  • Backup strategie -- Supabase Pro bevat dagelijkse backups, maar stel ook logische replicatie of pg_dump cron in voor gemoedsrust
  • Content Security Policy headers geconfigureerd in next.config.js
  • Beeldoptimalisatie -- gebruik Next.js <Image> component met Supabase Storage URL's

FAQ

Kan Supabase echt een headless CMS vervangen?

Voor veel use cases, ja. Supabase geeft je een PostgreSQL database met een REST en GraphQL API die automatisch uit je schema worden gegenereerd, bestandsopslag, auth en real-time subscriptions. Wat het niet geeft is een gepolijste content editing UI uit het vak -- je zult dat zelf moeten bouwen of een tool als Refine gebruiken. Als je team technisch is of klein, is deze tradeoff absoluut de moeite waard.

Hoeveel kost de Claude API voor een typische website?

Voor een content site die Claude gebruikt voor SEO metadata generatie, content samenvatting en basisclassificatie, verwacht je $10-30/maand te besteden met matig gebruik (een paar honderd AI-operaties). Claude Sonnet 4's prijsstelling in 2026 staat op ongeveer $3 per miljoen input tokens en $15 per miljoen output tokens. Een enkele SEO metadata generatie-aanroep kost ongeveer $0,01-0,02.

Is deze stack geschikt voor enterprise-applicaties?

Dat hangt af van je definitie van enterprise. Vercel en Supabase bieden beide enterprise tiers met SLA's, SOC 2 compliance en dedicated support. De stack handelt hoog traffic goed af -- Next.js op Vercel schaalt automatisch en Supabase Pro ondersteunt connection pooling en read replicas. Voor compliance-zware industrieën wil je Supabase's self-hosted optie om data in je eigen infrastructuur te houden.

Hoe zit het met content previews en draft workflows?

Je bouwt ze. Next.js Draft Mode gekoppeld aan een status kolom in je posts table geeft je draft/published workflows. Voor previews maak je een geverifieerde route die posts haalt ongeacht status. Het is misschien 50 regels code versus preview URL's configureren in een CMS dashboard.

Hoe handelt je rich text editing af zonder een CMS?

Gebruik een moderne rich text editor library. Tiptap (gebouwd op ProseMirror) is de meest populaire keuze in 2026 -- het ondersteunt collaborative editing, custom blocks, slash commands en Markdown shortcuts. BlockNote is een ander solide keuze met een Notion-achtige UI. Sla de output op als HTML, Markdown of JSON in je Supabase body kolom.

Kan ik migreren van een headless CMS naar deze stack?

Absoluut. De meeste headless CMS-platforms hebben export APIs. Schrijf een migratiescript dat content uit je CMS API haalt en in Supabase tables invoegt. We hebben deze migratie voor verschillende klanten gedaan, van Contentful en Sanity naar Supabase-backed setups. Het moeilijkste deel is meestal het mappen van de propriëtaire rich text-indeling van de CMS naar standaard HTML of Markdown.

Wat gebeurt er als Supabase uitvalt?

Supabase had solide uptime in 2025-2026, maar geen service is perfect. Omdat je data in standaard PostgreSQL leeft, heb je opties: stel read replicas in, zorg voor automatische backups in S3, of voer zelfs een standby instance uit. Als je op Supabase's self-hosted tier bent, control je de infrastructuur volledig. Dit is eigenlijk meer robuust dan afhankelijk zijn van een CMS-leverancier -- als Contentful een uitval heeft, kun je niet gewoon "naar een ander Contentful schakelen."

Moet ik deze stack gebruiken voor een blog of marketingsite?

Voor een developer's persoonlijke blog of een startup's marketingsite is deze stack perfect. Je krijgt volledige controle, minimale kosten en AI-aangedreven functies die dure plugins in een CMS zouden vereisen. Voor een groot marketingteam dat 20+ artikelen per week publiceert met complexe goedkeuringswerkflows, zou je waarschijnlijk een echte CMS willen. Het gaat om het matchen van de tool met het team. Als je niet zeker bent welke benadering voor je project past, bekijk onze pricing page of neem contact met ons op voor een snelle overleg.