Files
myeasycms-v2/docs/content/keystatic.mdoc
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

322 lines
8.7 KiB
Plaintext

---
status: "published"
title: "Keystatic CMS Setup for the Next.js Supabase SaaS Kit"
label: "Keystatic"
description: "Configure Keystatic as your CMS with local file storage for development or GitHub integration for production and team collaboration."
order: 2
---
Keystatic is a file-based CMS that stores content as Markdown/Markdoc files. It's the default CMS in Makerkit because it requires zero setup for local development and integrates with Git for version-controlled content.
## Storage Modes
Keystatic supports three storage modes:
| Mode | Storage | Best For | Edge Compatible |
|------|---------|----------|-----------------|
| `local` | Local filesystem | Development, solo projects | No |
| `github` | GitHub repository | Production, team collaboration | Yes |
| `cloud` | Keystatic Cloud | Managed hosting | Yes |
Local mode reads files directly from disk. GitHub mode fetches content via the GitHub API, making it compatible with edge runtimes like Cloudflare Workers.
## Local Storage (Default)
Local mode works out of the box. Content lives in your repository's `content/` directory:
```bash
# .env (optional - these are the defaults)
CMS_CLIENT=keystatic
NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=local
KEYSTATIC_PATH_PREFIX=apps/web
NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH=./content
```
Content structure:
```
apps/web/content/
├── posts/ # Blog posts
├── documentation/ # Docs (supports nesting)
└── changelog/ # Release notes
```
**Limitations**: Local mode doesn't work with edge runtimes (Cloudflare Workers, Vercel Edge) because it requires filesystem access. Use GitHub mode for edge deployments.
## GitHub Storage
GitHub mode fetches content from your repository via the GitHub API. This enables edge deployment and team collaboration through Git.
### 1. Set Environment Variables
```bash
# .env
CMS_CLIENT=keystatic
NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=github
NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO=your-org/your-repo
KEYSTATIC_GITHUB_TOKEN=github_pat_xxxxxxxxxxxx
KEYSTATIC_PATH_PREFIX=apps/web
NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH=./content
```
### 2. Create a GitHub Token
1. Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
2. Create a new token with:
- **Repository access**: Select your content repository
- **Permissions**: Contents (Read-only for production, Read and write for admin UI)
3. Copy the token to `KEYSTATIC_GITHUB_TOKEN`
For read-only access (recommended for production):
```bash
KEYSTATIC_GITHUB_TOKEN=github_pat_xxxxxxxxxxxx
```
### 3. Configure Path Prefix
If your content isn't at the repository root, set the path prefix:
```bash
# For monorepos where content is in apps/web/content/
KEYSTATIC_PATH_PREFIX=apps/web
NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH=./content
```
## Keystatic Cloud
Keystatic Cloud is a managed service that handles GitHub authentication and provides a hosted admin UI.
```bash
# .env
CMS_CLIENT=keystatic
NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=cloud
KEYSTATIC_STORAGE_PROJECT=your-project-id
```
Get your project ID from the [Keystatic Cloud dashboard](https://keystatic.cloud).
## Adding the Admin UI
Keystatic includes a visual editor for managing content. To add it:
```bash
turbo gen keystatic
```
This creates a route at `/keystatic` where you can create and edit content.
{% alert type="warning" title="Protect the admin in production" %}
By default, the Keystatic admin is only available in development. For production, add authentication:
```tsx {% title="app/keystatic/layout.tsx" %}
import { redirect } from 'next/navigation';
import { isSuperAdmin } from '@kit/admin';
export default async function KeystaticLayout({
children,
}: {
children: React.ReactNode;
}) {
const isAdmin = await isSuperAdmin();
if (!isAdmin) {
redirect('/');
}
return children;
}
```
{% /alert %}
### GitHub Mode Admin Setup
GitHub mode requires a GitHub App for the admin UI to authenticate and commit changes.
1. Install the Keystatic GitHub App on your repository
2. Follow the [Keystatic GitHub mode documentation](https://keystatic.com/docs/github-mode) for setup
The admin UI commits content changes directly to your repository, triggering your CI/CD pipeline.
## Default Collections
Makerkit configures three collections in `packages/cms/keystatic/src/keystatic.config.ts`:
### Posts
Blog posts with frontmatter:
```yaml
---
title: "Getting Started with Makerkit"
description: "A guide to building your SaaS"
publishedAt: 2025-01-15
status: published
categories:
- tutorials
tags:
- getting-started
image: /images/posts/getting-started.webp
---
Content here...
```
### Documentation
Hierarchical docs with ordering and collapsible sections:
```yaml
---
title: "Authentication"
label: "Auth" # Short label for navigation
description: "How authentication works"
order: 1
status: published
collapsible: true
collapsed: false
---
Content here...
```
Documentation supports nested directories. A file at `documentation/auth/sessions/sessions.mdoc` automatically becomes a child of `documentation/auth/auth.mdoc`.
### Changelog
Release notes:
```yaml
---
title: "v2.0.0 Release"
description: "Major update with new features"
publishedAt: 2025-01-10
status: published
---
Content here...
```
## Adding Custom Collections
Edit `packages/cms/keystatic/src/keystatic.config.ts` to add collections:
```tsx {% title="packages/cms/keystatic/src/keystatic.config.ts" %}
// In getKeystaticCollections()
return {
// ... existing collections
pages: collection({
label: 'Pages',
slugField: 'title',
path: `${path}pages/*`,
format: { contentField: 'content' },
schema: {
title: fields.slug({ name: { label: 'Title' } }),
description: fields.text({ label: 'Description' }),
content: getContentField(),
status: fields.select({
defaultValue: 'draft',
label: 'Status',
options: statusOptions,
}),
},
}),
};
```
## Content Format
Keystatic uses [Markdoc](https://markdoc.dev), a Markdown superset with custom components.
### Basic Markdown
Standard Markdown syntax works:
```markdown
# Heading
Paragraph with **bold** and *italic*.
- List item
- Another item
```code
Code block
```
```
### Images
Images are stored in `public/site/images/` and referenced with the public path:
```markdown
![Alt text](/site/images/screenshot.webp)
```
### Custom Components
Makerkit extends Markdoc with custom nodes. Check `packages/cms/keystatic/src/markdoc-nodes.ts` for available components.
## Cloudflare Workers Compatibility
Cloudflare Workers don't send the `User-Agent` header, which the GitHub API requires. Add this workaround to `packages/cms/keystatic/src/keystatic-client.ts`:
```tsx {% title="packages/cms/keystatic/src/keystatic-client.ts" %}
// Add at the top of the file
const self = global || globalThis || this;
const originalFetch = self.fetch;
self.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
const requestInit: RequestInit = {
...(init ?? {}),
headers: {
...(init?.headers ?? {}),
'User-Agent': 'Cloudflare-Workers',
}
};
return originalFetch(input, requestInit);
};
```
## Environment Variables Reference
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `CMS_CLIENT` | No | `keystatic` | CMS provider |
| `NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND` | No | `local` | Storage mode: `local`, `github`, `cloud` |
| `NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO` | GitHub only | - | Repository in `owner/repo` format |
| `KEYSTATIC_GITHUB_TOKEN` | GitHub only | - | GitHub personal access token |
| `KEYSTATIC_STORAGE_PROJECT` | Cloud only | - | Keystatic Cloud project ID |
| `KEYSTATIC_PATH_PREFIX` | No | - | Path to content in monorepos |
| `NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH` | No | `./content` | Content directory path |
| `KEYSTATIC_STORAGE_BRANCH_PREFIX` | No | - | Branch prefix for GitHub mode |
## Troubleshooting
### Content not loading in production
Verify GitHub mode is configured:
- `NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=github`
- `KEYSTATIC_GITHUB_TOKEN` has read access to the repository
- `NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO` matches your repository
### Admin UI shows authentication error
For GitHub mode, ensure:
- The Keystatic GitHub App is installed on your repository
- Your GitHub token has write permissions (for the admin)
### Edge runtime errors
Local mode doesn't work on edge. Switch to GitHub or Cloud mode:
- Set `NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=github`
- Configure GitHub token with read access
## Next Steps
- [CMS API Reference](/docs/next-supabase-turbo/content/cms-api): Learn the full API for fetching content
- [CMS Overview](/docs/next-supabase-turbo/content/cms): Compare CMS providers
- [Keystatic Documentation](https://keystatic.com/docs): Official Keystatic docs