From 51146da746ab2a877b6161b00585994616e3ba0a Mon Sep 17 00:00:00 2001 From: Corey Haines <34802794+coreyhaines31@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:21:58 -0700 Subject: [PATCH] Add headless CMS reference and integration guides (#174) * feat: add headless CMS reference and integration guides Add CMS selection guide, content modeling patterns, and editorial workflow reference for marketers. Add integration guides for Sanity (GROQ API), Contentful (CDA/CMA), and Strapi (v5 REST). Register all three in REGISTRY.md and link from content-strategy skill. Co-Authored-By: Claude Opus 4.6 * fix: address review feedback on headless CMS guides - Use {curly_brace} placeholder convention for Strapi relation example - Add note that Contentful CMA create uses PUT with client-generated ID - Add URL-encoding note for Sanity GROQ query parameter - Switch Sanity mutation example from createOrReplace to create (no _id needed) - Add Content-Type header to Contentful update entry example - Fix outdated "Assembly" product name to "Studio" for Contentful Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- skills/content-strategy/SKILL.md | 6 + .../references/headless-cms.md | 194 ++++++++++++++++++ tools/REGISTRY.md | 8 +- tools/integrations/contentful.md | 160 +++++++++++++++ tools/integrations/sanity.md | 148 +++++++++++++ tools/integrations/strapi.md | 167 +++++++++++++++ 6 files changed, 682 insertions(+), 1 deletion(-) create mode 100644 skills/content-strategy/references/headless-cms.md create mode 100644 tools/integrations/contentful.md create mode 100644 tools/integrations/sanity.md create mode 100644 tools/integrations/strapi.md diff --git a/skills/content-strategy/SKILL.md b/skills/content-strategy/SKILL.md index 6d042c1..6d0e41d 100644 --- a/skills/content-strategy/SKILL.md +++ b/skills/content-strategy/SKILL.md @@ -348,6 +348,12 @@ Visual or structured representation of how content interconnects. --- +## References + +- **[Headless CMS Guide](references/headless-cms.md)**: CMS selection, content modeling for marketing, editorial workflows, platform comparison (Sanity, Contentful, Strapi) + +--- + ## Related Skills - **copywriting**: For writing individual content pieces diff --git a/skills/content-strategy/references/headless-cms.md b/skills/content-strategy/references/headless-cms.md new file mode 100644 index 0000000..61ef10e --- /dev/null +++ b/skills/content-strategy/references/headless-cms.md @@ -0,0 +1,194 @@ +# Headless CMS Guide + +Reference for choosing, modeling, and implementing a headless CMS for marketing content. + +## When to Use This Reference + +Use this when selecting a CMS for a new project, designing content models for marketing sites, setting up editorial workflows, or connecting CMS content to programmatic pages. + +--- + +## Headless vs Traditional CMS + +A headless CMS separates content management from presentation. Content is stored in a structured backend and delivered via API to any frontend. + +### When Headless Makes Sense + +- Multiple frontends consume the same content (web, mobile, email) +- Developers want full control over the frontend stack +- Content needs to be reused across channels +- You're building with a modern framework (Next.js, Remix, Astro) +- Marketing needs structured, reusable content blocks + +### When Traditional Works Better + +- Small team with no dedicated developers +- Simple blog or brochure site +- WYSIWYG editing is a hard requirement +- Budget is tight and WordPress/Webflow does the job + +### Decision Checklist + +| Factor | Headless | Traditional | +|--------|----------|-------------| +| Multi-channel delivery | Yes | Limited | +| Developer control | Full | Constrained | +| Non-technical editing | Requires setup | Built-in | +| Time to launch | Longer | Faster | +| Content reuse | Native | Manual | +| Hosting flexibility | Any frontend | Platform-dependent | + +--- + +## Content Modeling for Marketing + +### Core Principles + +1. **Think in types, not pages.** A "Landing Page" is a content type with fields — not an HTML file. This lets you reuse components across pages. +2. **Separate content from presentation.** Store the headline text, not the styled headline. Presentation belongs in the frontend. +3. **Design for reuse.** If testimonials appear on 5 pages, create a Testimonial type and reference it — don't duplicate. +4. **Keep models flat.** Deeply nested structures are hard to query and maintain. Prefer references over nesting. + +### Common Marketing Content Types + +| Type | Key Fields | Notes | +|------|-----------|-------| +| **Landing Page** | title, slug, hero, sections[], seo | Modular sections for flexibility | +| **Blog Post** | title, slug, body, author, category, tags, publishedAt, seo | Rich text or Portable Text body | +| **Case Study** | title, customer, challenge, solution, results, metrics[], logo | Link to related products/features | +| **Testimonial** | quote, author, role, company, avatar, rating | Reference from landing pages | +| **FAQ** | question, answer, category | Group by category for programmatic pages | +| **Author** | name, bio, avatar, social links | Reference from blog posts | +| **CTA Block** | heading, body, buttonText, buttonUrl, variant | Reusable across pages | + +### SEO Fields Checklist + +Every page-level content type needs: + +- `metaTitle` — 50-60 characters +- `metaDescription` — 150-160 characters +- `ogImage` — 1200x630px social preview +- `slug` — URL path segment +- `canonicalUrl` — optional override +- `noIndex` — boolean for excluding from search +- `structuredData` — optional JSON-LD override + +--- + +## Editorial Workflows + +### Draft → Review → Publish Cycle + +1. **Draft** — Author creates or edits content +2. **Review** — Editor reviews for accuracy, brand voice, SEO +3. **Approve** — Stakeholder signs off +4. **Schedule** — Set publish date/time +5. **Publish** — Content goes live via API + +### Preview APIs + +All major headless CMS platforms support draft previews: + +- **Sanity**: Real-time preview with `useLiveQuery` or Presentation tool +- **Contentful**: Preview API (`preview.contentful.com`) with separate access token +- **Strapi**: Draft & Publish system with `status=draft` query parameter (v5; replaces v4's `publicationState`) + +Set up a preview route in your frontend (e.g., `/api/preview`) that authenticates and renders draft content. + +### Roles and Permissions + +| Role | Can Create | Can Edit | Can Publish | Can Delete | +|------|:----------:|:--------:|:-----------:|:----------:| +| Author | Yes | Own | No | Own drafts | +| Editor | Yes | All | Yes | Drafts | +| Admin | Yes | All | Yes | All | + +Exact permission models vary by platform. Sanity uses role-based access. Contentful has space-level roles. Strapi has granular RBAC. + +--- + +## Platform Comparison + +| Feature | Sanity | Contentful | Strapi | +|---------|--------|------------|--------| +| Hosting | Cloud (managed) | Cloud (managed) | Self-hosted or Cloud | +| Query Language | GROQ | REST / GraphQL | REST / GraphQL | +| Free Tier | Generous | Limited | Open source (free) | +| Real-time Collab | Yes (built-in) | Limited | No | +| Best For | Developer flexibility | Enterprise multi-locale | Budget / self-hosted | +| Content Modeling | Schema-as-code | Web UI | Web UI or code | +| Media Handling | Built-in DAM | Built-in | Plugin-based | + +### Sanity + +**Strengths**: GROQ query language is powerful and flexible. Schema defined in code (version-controlled). Real-time collaborative editing. Portable Text for rich content. Generous free tier. + +**Considerations**: Steeper learning curve for non-developers. Studio customization requires React knowledge. Vendor lock-in on GROQ queries. + +**Marketing fit**: Best when developers and marketers collaborate closely. Strong for content-heavy sites with complex models. + +### Contentful + +**Strengths**: Mature enterprise platform. Excellent multi-locale support. Strong ecosystem of integrations. Composable content with Studio. Well-documented APIs. + +**Considerations**: Pricing scales with content types and locales. Two separate APIs (Delivery and Management). Rate limits can be tight on lower plans. + +**Marketing fit**: Best for enterprises with multi-market content needs. Good when you need established vendor reliability. + +### Strapi + +**Strengths**: Open source, self-hosted option. Full control over data. No per-seat pricing. Customizable admin panel. Plugin ecosystem. REST by default, GraphQL via plugin. + +**Considerations**: Self-hosting means you handle infrastructure. Smaller ecosystem than Sanity/Contentful. V5 migration can be significant from V4. + +**Marketing fit**: Best for teams with DevOps capability who want full control and no vendor lock-in. Good for budget-conscious projects. + +### Others Worth Knowing + +- **Hygraph** — GraphQL-native, strong for federation and multi-source content +- **Keystatic** — Git-based, good for developer-content hybrid workflows +- **Payload** — TypeScript-first, self-hosted, code-configured like Sanity +- **Builder.io** — Visual editor with headless backend, good for non-technical marketers +- **Prismic** — Slice-based content modeling, strong Next.js integration + +--- + +## Integration with Marketing Skills + +### Programmatic SEO + +Use CMS as the data source for programmatic pages. Store structured data (FAQs, comparisons, city pages) as content types and generate pages from queries. See **programmatic-seo** skill. + +### Copywriting + +CMS content models enforce consistent structure. Define fields that match your copy frameworks (headline, subheadline, social proof, CTA). See **copywriting** skill. + +### Site Architecture + +URL structure, navigation hierarchy, and internal linking all depend on how content is organized in the CMS. Plan your content model and site architecture together. See **site-architecture** skill. + +### Email Sequences + +Pull CMS content into email templates for consistent messaging across web and email. Case studies, testimonials, and blog posts can feed email nurture sequences. See **email-sequence** skill. + +--- + +## Implementation Checklist + +- [ ] Define content types based on page types and reusable blocks +- [ ] Add SEO fields to every page-level content type +- [ ] Set up preview/draft mode in your frontend +- [ ] Configure roles and permissions for your team +- [ ] Create sample content for each type before building frontend +- [ ] Set up webhook notifications for content changes (rebuild triggers) +- [ ] Document content guidelines for editors (field descriptions, character limits) +- [ ] Test content delivery performance (CDN, caching, ISR) +- [ ] Plan migration strategy if moving from existing CMS + +--- + +## Relevant Integration Guides + +- [Sanity](../../../tools/integrations/sanity.md) — GROQ queries, mutations, CLI +- [Contentful](../../../tools/integrations/contentful.md) — Delivery/Management APIs, publishing +- [Strapi](../../../tools/integrations/strapi.md) — REST CRUD, filters, document API diff --git a/tools/REGISTRY.md b/tools/REGISTRY.md index 52bb118..3f005f1 100644 --- a/tools/REGISTRY.md +++ b/tools/REGISTRY.md @@ -82,6 +82,9 @@ Quick reference for AI agents to discover tool capabilities and integration meth | shopify | Commerce | ✓ | - | ✓ | ✓ | [shopify.md](integrations/shopify.md) | | wordpress | CMS | ✓ | - | ✓ | ✓ | [wordpress.md](integrations/wordpress.md) | | webflow | CMS | ✓ | - | ✓ | ✓ | [webflow.md](integrations/webflow.md) | +| sanity | Headless CMS | ✓ | - | ✓ | ✓ | [sanity.md](integrations/sanity.md) | +| contentful | Headless CMS | ✓ | - | ✓ | ✓ | [contentful.md](integrations/contentful.md) | +| strapi | Headless CMS | ✓ | - | ✓ | ✓ | [strapi.md](integrations/strapi.md) | --- @@ -386,8 +389,11 @@ E-commerce platforms and content management systems. | **shopify** | E-commerce, product sales | ✓ | | **wordpress** | Blogs, content sites | ✓ | | **webflow** | Design-focused marketing sites | ✓ | +| **sanity** | Headless CMS, structured content | ✓ | +| **contentful** | Enterprise headless CMS, multi-locale | ✓ | +| **strapi** | Open-source headless CMS, self-hosted | ✓ | -**Agent recommendation**: Shopify for e-commerce. Webflow for marketing sites. WordPress for blogs. +**Agent recommendation**: Shopify for e-commerce. Webflow for marketing sites. WordPress for blogs. For headless CMS: Sanity for developer-flexible content, Contentful for enterprise multi-locale, Strapi for self-hosted/budget-conscious. See [headless CMS guide](../skills/content-strategy/references/headless-cms.md) for selection criteria. --- diff --git a/tools/integrations/contentful.md b/tools/integrations/contentful.md new file mode 100644 index 0000000..9a279a6 --- /dev/null +++ b/tools/integrations/contentful.md @@ -0,0 +1,160 @@ +# Contentful + +Enterprise headless CMS with multi-locale support, two-API architecture, and composable content. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Content Delivery API (read), Content Management API (write) | +| MCP | - | No official MCP server | +| CLI | ✓ | `contentful-cli` for spaces, content types, migrations | +| SDK | ✓ | `contentful` (delivery), `contentful-management` (management) | + +## Authentication + +- **Delivery API (CDA)**: `Authorization: Bearer {delivery_token}` + - Base URL: `https://cdn.contentful.com` + - Read-only, CDN-cached +- **Preview API (CPA)**: `Authorization: Bearer {preview_token}` + - Base URL: `https://preview.contentful.com` + - Read-only, returns draft content +- **Management API (CMA)**: `Authorization: Bearer {management_token}` + - Base URL: `https://api.contentful.com` + - Read/write, not cached +- **Tokens**: Create in Settings → API keys (delivery) or Settings → CMA tokens (management) + +## Common Agent Operations + +### Get entries (Delivery API) + +```bash +GET https://cdn.contentful.com/spaces/{space_id}/environments/{environment}/entries?content_type=blogPost&limit=10 + +Authorization: Bearer {delivery_token} +``` + +### Get single entry + +```bash +GET https://cdn.contentful.com/spaces/{space_id}/environments/{environment}/entries/{entry_id} + +Authorization: Bearer {delivery_token} +``` + +### Search and filter + +```bash +# By field value +GET https://cdn.contentful.com/spaces/{space_id}/environments/{environment}/entries?content_type=blogPost&fields.slug=my-post + +# Full-text search +GET https://cdn.contentful.com/spaces/{space_id}/environments/{environment}/entries?query=marketing+strategy + +# By date range +GET https://cdn.contentful.com/spaces/{space_id}/environments/{environment}/entries?content_type=blogPost&fields.publishDate[gte]=2024-01-01 +``` + +### Create entry (Management API) + +CMA uses PUT with a client-generated `entry_id`. To auto-generate, use POST without an ID in the path. + +```bash +PUT https://api.contentful.com/spaces/{space_id}/environments/{environment}/entries/{entry_id} +Content-Type: application/vnd.contentful.management.v1+json +X-Contentful-Content-Type: blogPost +Authorization: Bearer {management_token} + +{ + "fields": { + "title": {"en-US": "New Post"}, + "slug": {"en-US": "new-post"}, + "body": {"en-US": "Post content here"} + } +} +``` + +### Update entry + +```bash +PUT https://api.contentful.com/spaces/{space_id}/environments/{environment}/entries/{entry_id} +Content-Type: application/vnd.contentful.management.v1+json +X-Contentful-Version: {current_version} +Authorization: Bearer {management_token} + +{ + "fields": { + "title": {"en-US": "Updated Title"} + } +} +``` + +### Publish entry + +```bash +PUT https://api.contentful.com/spaces/{space_id}/environments/{environment}/entries/{entry_id}/published +X-Contentful-Version: {current_version} +Authorization: Bearer {management_token} +``` + +### Unpublish entry + +```bash +DELETE https://api.contentful.com/spaces/{space_id}/environments/{environment}/entries/{entry_id}/published +X-Contentful-Version: {current_version} +Authorization: Bearer {management_token} +``` + +## CLI Commands + +```bash +# Login +contentful login + +# List spaces +contentful space list + +# Export space content +contentful space export --space-id {space_id} + +# Import content +contentful space import --space-id {space_id} --content-file export.json + +# Create migration +contentful space migration --space-id {space_id} migration.js + +# List content types +contentful content-type list --space-id {space_id} +``` + +## Key Objects + +- **Space** — Top-level container for content (one per project) +- **Environment** — Isolated content branch (`master`, `staging`, etc.) +- **Content Type** — Schema definition with fields and validations +- **Entry** — Content item of a specific content type +- **Asset** — Media file (image, video, document) +- **Locale** — Language/region variant (e.g., `en-US`, `de-DE`) + +## When to Use + +- Multi-locale marketing content (global sites) +- Enterprise content operations with approval workflows +- Composable content architecture +- Teams needing established vendor support and SLAs +- Content reuse across multiple channels + +## Rate Limits + +Rate limits are plan-dependent. Check `X-Contentful-RateLimit-Second-Limit` response header for your actual limits. + +- Delivery API (CDA): Varies by plan (typically high throughput) +- Preview API (CPA): Lower than CDA (varies by plan) +- Management API (CMA): ~10 requests per second (default) +- See [Contentful technical limits](https://www.contentful.com/developers/docs/technical-limits/) for current values + +## Relevant Skills + +- content-strategy (CMS selection, content modeling) +- programmatic-seo (CMS as data source for generated pages) +- site-architecture (multi-locale URL structure) diff --git a/tools/integrations/sanity.md b/tools/integrations/sanity.md new file mode 100644 index 0000000..8bc04e7 --- /dev/null +++ b/tools/integrations/sanity.md @@ -0,0 +1,148 @@ +# Sanity + +Headless CMS with real-time collaboration, GROQ query language, and schema-as-code. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | GROQ queries, Mutations API, Assets API | +| MCP | - | No official MCP server | +| CLI | ✓ | `sanity` CLI for studio, datasets, deployment | +| SDK | ✓ | `@sanity/client`, `next-sanity`, `@sanity/image-url` | + +## Authentication + +- **Type**: API Token (Bearer) +- **Header**: `Authorization: Bearer skXXXXXX` +- **Tokens**: Create in Sanity Manage → API → Tokens +- **Permissions**: Read-only or Read+Write per token + +## Common Agent Operations + +### Query documents (GROQ) + +URL-encode the `query` parameter value in practice. + +```bash +GET https://{projectId}.api.sanity.io/v2024-01-01/data/query/{dataset}?query=*[_type == "post"]{title, slug, publishedAt} +``` + +### Query with parameters + +```bash +GET https://{projectId}.api.sanity.io/v2024-01-01/data/query/{dataset}?query=*[_type == "post" && slug.current == $slug][0]&$slug="my-post" +``` + +### Get document by ID + +```bash +GET https://{projectId}.api.sanity.io/v2024-01-01/data/doc/{dataset}/{documentId} +``` + +### Create document (Mutations API) + +```bash +POST https://{projectId}.api.sanity.io/v2024-01-01/data/mutate/{dataset} + +{ + "mutations": [ + { + "create": { + "_type": "post", + "title": "New Post", + "slug": {"_type": "slug", "current": "new-post"}, + "body": [{"_type": "block", "children": [{"_type": "span", "text": "Hello"}]}] + } + } + ] +} +``` + +Use `createOrReplace` instead if you want to upsert (requires `_id` field). + +### Delete document + +```bash +POST https://{projectId}.api.sanity.io/v2024-01-01/data/mutate/{dataset} + +{ + "mutations": [ + {"delete": {"id": "document-id"}} + ] +} +``` + +### Patch document + +```bash +POST https://{projectId}.api.sanity.io/v2024-01-01/data/mutate/{dataset} + +{ + "mutations": [ + { + "patch": { + "id": "document-id", + "set": {"title": "Updated Title"} + } + } + ] +} +``` + +## CLI Commands + +```bash +# Create a new Sanity project +sanity init + +# Start the studio locally +sanity dev + +# Deploy studio to Sanity hosting +sanity deploy + +# Export dataset +sanity dataset export production ./backup.tar.gz + +# Import dataset +sanity dataset import ./data.ndjson production + +# List datasets +sanity dataset list + +# Run a GROQ query +sanity documents query '*[_type == "post"][0..9]{title, slug}' +``` + +## Key Objects + +- **Document** — Top-level content item with `_id`, `_type`, `_rev` +- **Asset** — Images and files stored in Sanity CDN +- **Reference** — Link between documents (`{_type: "reference", _ref: "doc-id"}`) +- **Portable Text** — Rich text as structured array of blocks +- **Dataset** — Isolated content database (e.g., `production`, `staging`) +- **Slug** — URL-friendly identifier (`{_type: "slug", current: "my-slug"}`) + +## When to Use + +- Structured content for marketing sites and blogs +- Multi-channel content delivery (web, mobile, email) +- Real-time collaborative editing workflows +- Content-heavy sites with complex models +- Next.js or React-based frontends + +## Rate Limits + +Rate limits vary by plan. Documented defaults: + +- CDN API (queries): High throughput, globally distributed (no hard per-second cap published) +- API (without CDN): Rate-limited per project (varies by plan) +- Mutations: Rate-limited per project (varies by plan) +- See [Sanity technical limits](https://www.sanity.io/docs/technical-limits) for current values + +## Relevant Skills + +- content-strategy (CMS selection, content modeling) +- programmatic-seo (CMS as data source for generated pages) +- site-architecture (URL structure from CMS slugs) diff --git a/tools/integrations/strapi.md b/tools/integrations/strapi.md new file mode 100644 index 0000000..0ebcd06 --- /dev/null +++ b/tools/integrations/strapi.md @@ -0,0 +1,167 @@ +# Strapi + +Open-source headless CMS with self-hosted option, REST and GraphQL APIs, and customizable admin panel. Targets Strapi 5. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | REST (default), GraphQL (plugin) | +| MCP | - | No official MCP server | +| CLI | ✓ | `strapi` CLI for project setup, content types, plugins | +| SDK | ✓ | `@strapi/sdk-js`, `@strapi/blocks-react-renderer` | + +## Authentication + +- **Type**: API Token or Users & Permissions JWT +- **Header**: `Authorization: Bearer {api_token}` +- **Tokens**: Create in Settings → API Tokens (full access, read-only, or custom) +- **JWT**: `POST /api/auth/local` with identifier + password returns JWT + +## Common Agent Operations + +### List documents + +```bash +GET http://localhost:1337/api/articles?populate=* + +Authorization: Bearer {api_token} +``` + +### Get single document + +```bash +GET http://localhost:1337/api/articles/{documentId}?populate=* + +Authorization: Bearer {api_token} +``` + +### Filter and sort + +```bash +# Filter by field +GET http://localhost:1337/api/articles?filters[slug][$eq]=my-post + +# Multiple filters +GET http://localhost:1337/api/articles?filters[category][name][$eq]=Marketing&filters[publishedAt][$notNull]=true + +# Sort +GET http://localhost:1337/api/articles?sort=publishedAt:desc + +# Pagination +GET http://localhost:1337/api/articles?pagination[page]=1&pagination[pageSize]=10 +``` + +### Create document + +```bash +POST http://localhost:1337/api/articles +Content-Type: application/json +Authorization: Bearer {api_token} + +{ + "data": { + "title": "New Article", + "slug": "new-article", + "body": "Article content here", + "category": "{category_documentId}" + } +} +``` + +### Update document + +```bash +PUT http://localhost:1337/api/articles/{documentId} +Content-Type: application/json +Authorization: Bearer {api_token} + +{ + "data": { + "title": "Updated Title" + } +} +``` + +### Delete document + +```bash +DELETE http://localhost:1337/api/articles/{documentId} + +Authorization: Bearer {api_token} +``` + +### Get draft content + +```bash +# Strapi 5 uses status parameter (replaces v4 publicationState) +GET http://localhost:1337/api/articles?status=draft + +Authorization: Bearer {api_token} +``` + +Publishing and unpublishing are managed through the Strapi admin panel or Document Service API (server-side). The public REST API does not expose dedicated publish/unpublish endpoints. + +### Populate relations and components + +```bash +# Populate all relations +GET http://localhost:1337/api/articles?populate=* + +# Populate specific relations +GET http://localhost:1337/api/articles?populate[0]=author&populate[1]=category + +# Deep populate +GET http://localhost:1337/api/articles?populate[author][populate]=avatar +``` + +## CLI Commands + +```bash +# Create new Strapi project +npx create-strapi@latest my-project + +# Start development server +strapi develop + +# Build admin panel +strapi build + +# Generate content type +strapi generate content-type + +# Generate controller +strapi generate controller + +# Add GraphQL plugin +npm install @strapi/plugin-graphql +``` + +## Key Objects + +- **Content Type** — Schema definition (collection type or single type) +- **Document** — Content item identified by `documentId` (Strapi 5 pattern) +- **Component** — Reusable field group (e.g., SEO fields, CTA block) +- **Dynamic Zone** — Flexible content area accepting multiple component types +- **Media** — Files managed through the Media Library +- **Locale** — i18n locale for content translation (plugin-based) + +## When to Use + +- Self-hosted CMS with full data ownership +- Budget-conscious projects (no per-seat pricing) +- Custom admin panel or plugin requirements +- Teams with DevOps capability +- Projects needing both REST and GraphQL access + +## Rate Limits + +- Self-hosted: No built-in rate limits (configure via middleware or reverse proxy) +- Strapi Cloud: Varies by plan +- Recommended: Add rate limiting middleware for production APIs + +## Relevant Skills + +- content-strategy (CMS selection, content modeling) +- programmatic-seo (CMS as data source for generated pages) +- site-architecture (URL structure from CMS slugs)