CMS Integration in the Next.js Supabase SaaS Kit

Makerkit's CMS interface abstracts content storage, letting you swap between Keystatic, WordPress, or Supabase without changing your application code.

Makerkit provides a unified CMS interface that decouples your application from the underlying content storage. Write your content queries once, then swap between Keystatic, WordPress, or Supabase without touching your React components.

This abstraction means you can start with local Markdown files during development, then switch to WordPress for a content team, or Supabase for a database-driven approach, all without rewriting your data fetching logic.

Supported CMS Providers

Makerkit ships with two built-in CMS implementations and one plugin:

ProviderStorageBest ForEdge Compatible
KeystaticLocal files or GitHubSolo developers, Git-based workflowsGitHub mode only
WordPressWordPress REST APIContent teams, existing WordPress sitesYes
SupabasePostgreSQL via SupabaseDatabase-driven content, custom adminYes

You can also create your own CMS client for providers like Sanity, Contentful, or Strapi.

How It Works

The CMS interface consists of three layers:

  1. CMS Client: An abstract class that defines methods like getContentItems() and getContentItemBySlug(). Each provider implements this interface.
  2. Content Renderer: A React component that knows how to render content from each provider (Markdoc for Keystatic, HTML for WordPress, etc.).
  3. Registry: A dynamic import system that loads the correct client based on the CMS_CLIENT environment variable.
// This code works with any CMS provider
import { createCmsClient } from '@kit/cms';
const client = await createCmsClient();
const { items } = await client.getContentItems({
collection: 'posts',
limit: 10,
sortBy: 'publishedAt',
sortDirection: 'desc',
});

The CMS_CLIENT environment variable determines which implementation gets loaded:

CMS_CLIENT=keystatic # Default - file-based content
CMS_CLIENT=wordpress # WordPress REST API
CMS_CLIENT=supabase # Supabase database (requires plugin)

Default Collections

Keystatic ships with three pre-configured collections:

  • posts: Blog posts with title, description, categories, tags, and Markdoc content
  • documentation: Hierarchical docs with parent-child relationships and ordering
  • changelog: Release notes and updates

WordPress maps to its native content types (posts and pages). Supabase uses the content_items table with flexible metadata.

Choosing a Provider

Choose Keystatic if:

  • You're a solo developer or small team
  • You want version-controlled content in your repo
  • You prefer Markdown/Markdoc for writing
  • You don't need real-time collaborative editing

Choose WordPress if:

  • You have an existing WordPress site
  • Your content team knows WordPress
  • You need its plugin ecosystem (SEO, forms, etc.)
  • You want a battle-tested admin interface

Choose Supabase if:

  • You want content in your existing database
  • You need row-level security on content
  • You're building a user-generated content feature
  • You want to use Supamode as your admin

Quick Start

By default, Makerkit uses Keystatic with local storage. No configuration needed.

To switch providers, set the environment variable and follow the provider-specific setup:

# .env
CMS_CLIENT=keystatic

Then use the CMS API to fetch content in your components:

import { createCmsClient, ContentRenderer } from '@kit/cms';
import { notFound } from 'next/navigation';
async function BlogPost({ slug }: { slug: string }) {
const client = await createCmsClient();
const post = await client.getContentItemBySlug({
slug,
collection: 'posts',
});
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
<ContentRenderer content={post.content} />
</article>
);
}

Next Steps