I've been building websites with headless CMS platforms for years. Contentful, Sanity, Strapi -- you name it, I've integrated it. But sometime in late 2025, I started noticing a pattern: for a growing number of projects, I wasn't reaching for a CMS at all. Instead, I was shipping full-stack apps with Next.js on Vercel, Supabase for data and auth, and the Claude API for any content intelligence I needed. No CMS vendor. No content modeling UI. No monthly seat licenses.

This isn't about being contrarian. There are absolutely projects where a headless CMS is the right call -- we build plenty of those at Social Animal (check out our headless CMS development work). But there's a specific class of application where this vendor-free stack is not just viable, it's better. Let me walk you through exactly how it works, when to use it, and how to set it up from zero.

Table of Contents

Why Developers Are Dropping the CMS

Let's be honest about what a CMS gives you: a UI for non-technical people to edit content, a structured data layer, maybe some media management, and an API to fetch it all. That's genuinely valuable when you have a marketing team publishing blog posts every day.

But here's what I keep seeing in 2026:

  • SaaS products where the "content" is user-generated data, not editorial copy
  • Internal tools where the team is technical enough to edit a database directly or use a lightweight admin panel
  • AI-native applications where content is generated, summarized, or transformed on the fly
  • Startups that can't justify $300-500/month for a CMS when they have three users

For these projects, a CMS is overhead. You're paying for content modeling features you won't use, managing API keys for a service that's basically a fancy database wrapper, and dealing with webhook complexity to keep things in sync.

The alternative? Own your data layer entirely. Supabase gives you Postgres (a real database, not a proprietary content store), auth, file storage, and realtime subscriptions. Claude handles the intelligence layer. Next.js and Vercel handle everything else.

The Stack at a Glance

Layer Technology Role 2026 Pricing (Starting)
Frontend & API Next.js 15 (App Router) UI, Server Components, Route Handlers Free (open source)
Hosting & Edge Vercel Deployment, CDN, Serverless Functions Free tier / $20/mo Pro
Database & Auth Supabase Postgres, Row Level Security, Auth, Storage Free tier / $25/mo Pro
AI Layer Claude API (Anthropic) Content generation, summarization, classification Pay-per-token (~$3/$15 per 1M tokens for Sonnet 4)
Admin UI Custom (React + Supabase) Content management for your team $0 (you build it)

Total cost for a production app with moderate traffic: $45-100/month. Compare that to a typical headless CMS setup where the CMS alone might run you $99-500/month before you even pay for hosting.

Setting Up Your Next.js Project on Vercel

I'm assuming you've got Node.js 20+ and a Vercel account. If you're new to Next.js, our team has written extensively about it in our Next.js development capability page.

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

Next.js 15 with the App Router is the foundation here. We're using Server Components by default, which means most of our data fetching happens on the server -- no exposed API keys, no client-side loading spinners for initial content.

Here's my typical project structure for this stack:

src/
├── app/
│   ├── (public)/           # Marketing pages, blog
│   ├── (dashboard)/        # Authenticated 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/

Environment Variables

You'll need these in your .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-...

Deploying to Vercel

Push to GitHub. Connect the repo in Vercel. Add your env vars. Done. I'm not going to belabor this -- Vercel's DX for Next.js deployment is the best in the industry and you probably already know how it works.

One thing worth noting: use Vercel's Edge Config if you need feature flags or configuration that updates without redeploying. It's a small thing but it replaces yet another SaaS tool.

Supabase as Your Backend: Auth, Database, and Storage

This is where the magic happens. Supabase isn't just "Firebase but Postgres" -- it's a full backend platform that you actually own. Your data lives in a standard PostgreSQL database. If you ever want to leave, you pg_dump and walk away. Try doing that with a proprietary CMS.

Database Schema

Let's say you're building a content-driven app (the kind of thing where you'd normally reach for a CMS). Here's a schema that handles articles, media, and basic taxonomy:

-- Enable UUID generation
create extension if not exists "uuid-ossp";

-- Content table (replaces your 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 path
  metadata jsonb default '{}', -- Flexible fields, no migration needed
  published_at timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- Tags / taxonomy
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;

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

-- Authenticated users can manage their own posts
create policy "Authors can manage own posts"
  on public.posts for all
  using (auth.uid() = author_id);

That metadata jsonb column is key. It gives you the flexibility of a CMS's custom fields without needing to run migrations every time the marketing team wants a new field. Need an SEO description? metadata->>'seo_description'. Need an Open Graph image override? metadata->>'og_image'. It's schemaless where you need flexibility, structured where you need integrity.

Auth Setup

Supabase Auth handles everything. Email/password, magic links, OAuth with Google/GitHub -- it's all built in.

// 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)
          )
        },
      },
    }
  )
}

File Storage

Supabase Storage replaces whatever media library your CMS had. Create a bucket called media, set up a policy, and you've got an S3-compatible file store with automatic CDN URLs.

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

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

Integrating the Claude API for Content Intelligence

This is where the 2026 stack diverges most from traditional web dev. The Claude API isn't just a chatbot -- it's an intelligence layer that can replace entire categories of CMS plugins and third-party services.

Here's what I'm using it for in production:

  • Auto-generating SEO metadata from post content
  • Content summarization for excerpts and social cards
  • Content classification and auto-tagging
  • Smart search that understands intent, not just keywords
  • Draft assistance for content authors

Setting Up the Anthropic SDK

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 for AI Features

// 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 }
    )
  }
}

The cost here is negligible. A typical SEO metadata generation call uses maybe 4,000 input tokens and 200 output tokens. With Claude Sonnet 4's pricing at roughly $3/1M input tokens and $15/1M output tokens, that's about $0.015 per call. You could generate metadata for 1,000 articles for $15.

Building a Custom Admin Interface

This is the part that makes people nervous. "If I don't have a CMS, how do non-technical people edit content?"

You build a simple admin UI. And in 2026, "simple" is actually simple. Here's a basic post editor component:

// 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()
    // Apply generated metadata to the 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>
  )
}

Yes, this is a simple textarea. In a real project, you'd swap in something like Tiptap, MDXEditor, or BlockNote for rich editing. The point is: the admin interface is your code. You control every pixel, every workflow, every permission. No fighting with CMS UI limitations.

For more complex projects, consider Refine or AdminJS as admin panel frameworks that plug directly into Supabase. They'll save you weeks.

Real Costs: What This Stack Actually Runs You

Let's get specific with numbers for a content-heavy site doing 100K pageviews/month:

Service Tier Monthly Cost What You Get
Vercel Pro $20 1TB bandwidth, 1000 GB-hrs serverless
Supabase Pro $25 8GB database, 250GB bandwidth, 100K auth users
Claude API Pay-as-you-go ~$10-30 ~5M tokens/month (SEO gen, summaries, search)
Domain Annual ~$1 .com domain
Total $56-76/mo

Now compare this to a typical headless CMS stack:

Service Tier Monthly Cost
Contentful Team $300
Vercel Pro $20
Algolia (search) Build $50
Auth0 (auth) Essentials $35
Total $405/mo

That's a 5-6x cost difference. And the Supabase stack gives you more flexibility, not less.

When You Should Still Use a CMS

I want to be clear-eyed about this. Don't throw away your CMS for every project. A headless CMS is still the better choice when:

  • Large editorial teams need structured workflows (approval chains, scheduling, roles that go beyond basic RBAC)
  • Content is the product -- publishers, media companies, documentation sites with hundreds of contributors
  • You need visual editing -- some CMS platforms offer live preview and visual builders that would take months to replicate
  • Multi-channel delivery -- if the same content feeds a website, mobile app, digital signage, and email, a CMS's structured content model earns its keep
  • Localization at scale -- CMS platforms like Contentful and Sanity have mature i18n workflows

We still build plenty of headless CMS projects at Social Animal. If that's what your project needs, talk to us about it. But for the growing category of apps where it's not what you need, stop paying for it.

Production Deployment Checklist

Before you ship this stack to production, run through this list:

  • Row Level Security policies tested for every table (Supabase's policy simulator helps here)
  • Rate limiting on Claude API routes (use Vercel's @vercel/edge rate limiter or upstash/ratelimit)
  • Input validation on all API routes (Zod is your friend)
  • Error boundaries in your React tree for AI failures (Claude will occasionally time out)
  • Caching strategy -- use unstable_cache or revalidateTag in Next.js for database-backed pages
  • Monitoring -- Vercel Analytics for performance, Supabase Dashboard for database metrics, Anthropic Console for API usage
  • Backup strategy -- Supabase Pro includes daily backups, but also set up logical replication or pg_dump cron for peace of mind
  • Content Security Policy headers configured in next.config.js
  • Image optimization -- use Next.js <Image> component with Supabase Storage URLs

FAQ

Can Supabase really replace a headless CMS?

For many use cases, yes. Supabase gives you a PostgreSQL database with a REST and GraphQL API auto-generated from your schema, file storage, auth, and realtime subscriptions. What it doesn't give you is a polished content editing UI out of the box -- you'll need to build that yourself or use a tool like Refine. If your team is technical or small, this tradeoff is absolutely worth it.

How much does the Claude API cost for a typical website?

For a content site that uses Claude for SEO metadata generation, content summarization, and basic classification, expect to spend $10-30/month with moderate usage (a few hundred AI operations). Claude Sonnet 4 pricing in 2026 sits at approximately $3 per million input tokens and $15 per million output tokens. A single SEO metadata generation call costs roughly $0.01-0.02.

Is this stack suitable for enterprise applications?

It depends on your definition of enterprise. Vercel and Supabase both offer enterprise tiers with SLAs, SOC 2 compliance, and dedicated support. The stack handles high traffic well -- Next.js on Vercel scales automatically, and Supabase Pro supports connection pooling and read replicas. For compliance-heavy industries, you'd want Supabase's self-hosted option to keep data in your own infrastructure.

What about content previews and draft workflows?

You build them. Next.js Draft Mode combined with a status column in your posts table gives you draft/published workflows. For previews, create an authenticated route that fetches posts regardless of status. It's maybe 50 lines of code versus configuring preview URLs in a CMS dashboard.

How do you handle rich text editing without a CMS?

Use a modern rich text editor library. Tiptap (built on ProseMirror) is the most popular choice in 2026 -- it supports collaborative editing, custom blocks, slash commands, and Markdown shortcuts. BlockNote is another solid option with a Notion-like UI. Store the output as HTML, Markdown, or JSON in your Supabase body column.

Can I migrate from a headless CMS to this stack?

Absolutely. Most headless CMS platforms have export APIs. Write a migration script that pulls content from your CMS API and inserts it into Supabase tables. We've done this migration for several clients, moving from Contentful and Sanity to Supabase-backed setups. The hardest part is usually mapping the CMS's proprietary rich text format to standard HTML or Markdown.

What happens if Supabase goes down?

Supabase has had solid uptime in 2025-2026, but no service is perfect. Because your data lives in standard PostgreSQL, you have options: set up read replicas, keep automated backups in S3, or even run a standby instance. If you're on Supabase's self-hosted tier, you control the infrastructure entirely. This is actually more resilient than depending on a CMS vendor -- if Contentful has an outage, you can't just "switch to another Contentful."

Should I use this stack for a blog or marketing site?

For a developer's personal blog or a startup's marketing site, this stack is perfect. You get complete control, minimal costs, and AI-powered features that would require expensive plugins in a CMS. For a large marketing team publishing 20+ articles a week with complex approval workflows, you'd probably want a proper CMS. It's about matching the tool to the team. If you're unsure which approach fits your project, check our pricing page or get in touch for a quick consultation.