# KeystoneJS TypeScript CMS: Code-First Content Platform

# KeystoneJS TypeScript CMS: Code-First Content Platform

## The Content Nightmare That Cost Us 6 Months

Managing content across platforms was killing our team. Then we found the right solution.

## Table of Contents
- Content Management 2026
- Architecture Patterns
- 5 Implementation Strategies
- Content Modeling
- Workflow Automation
- Performance Optimization
- Migration Guide
- FAQ
- Production Setup

## Content Management in 2026

Content is everywhere, consistency is hard.

### Headless Architecture

```typescript
// Modern content delivery
interface HeadlessCMS {
  content: 'Stored in CMS';
  api: 'GraphQL or REST';
  frontend: 'Any framework';
  mobile: 'Same content';
  iot: 'Same API';
}
```

### Traditional vs Headless

```typescript
// Traditional CMS
// Content + Presentation = Coupled

// Headless CMS
// Content (API) -> Multiple frontends
const content = await cms.getContent('page-home');
// Use in: React, Vue, React Native, Flutter
```

### Business Benefits

Faster time to market, reuse content everywhere.

## Architecture Patterns

Building scalable content systems.

### API-First Approach

```typescript
// Fetch content via API
import { createClient } from '@sanity/client';

const client = createClient({
  projectId: 'your-project',
  dataset: 'production',
  apiVersion: '2024-01-01',
  useCdn: true
});

// Query content
const posts = await client.fetch(`
  *[_type == "post" && publishedAt < now()] | order(publishedAt desc) {
    title,
    slug,
    publishedAt,
    "author": author->name,
    "categories": categories[]->title
  }
`);
```

### Content Types

```typescript
// Define content schema
export const postSchema = {
  name: 'post',
  title: 'Blog Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: Rule => Rule.required().max(100)
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: { source: 'title' }
    },
    {
      name: 'content',
      title: 'Content',
      type: 'array',
      of: [{ type: 'block' }]
    },
    {
      name: 'publishedAt',
      title: 'Published At',
      type: 'datetime'
    }
  ]
};
```

## Strategy 1: Content Modeling

### Structured Content

```typescript
// Rich content model
interface Article {
  id: string;
  title: string;
  slug: string;
  author: {
    name: string;
    avatar: Image;
    bio: string;
  };
  content: ContentBlock[];
  metadata: {
    publishedAt: Date;
    updatedAt: Date;
    tags: string[];
    category: Category;
  };
  seo: {
    title: string;
    description: string;
    image: Image;
  };
}

// Content blocks
type ContentBlock =
  | { type: 'paragraph'; text: string }
  | { type: 'heading'; level: 1 | 2 | 3; text: string }
  | { type: 'image'; url: string; alt: string; caption?: string }
  | { type: 'code'; language: string; code: string }
  | { type: 'quote'; text: string; author?: string };
```

### Relationships

```typescript
// Link content
const articleWithAuthor = await cms.query({
  type: 'article',
  include: ['author', 'category', 'relatedArticles']
});
```

## Strategy 2: Real-Time Preview

### Live Preview

```typescript
// Next.js draft mode
import { draftMode } from 'next/headers';

export async function getArticle(slug: string) {
  const { isEnabled } = draftMode();

  const article = await cms.fetch(
    `*[_type == "article" && slug.current == $slug][0]`,
    { slug },
    { useCdn: !isEnabled }
  );

  return article;
}

// Enable preview
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const secret = searchParams.get('secret');
  const slug = searchParams.get('slug');

  if (secret !== process.env.PREVIEW_SECRET) {
    return new Response('Invalid token', { status: 401 });
  }

  draftMode().enable();
  redirect(`/articles/${slug}`);
}
```

### Visual Editing

Edit content in context on live site.

## Strategy 3: Webhooks & Automation

### Trigger Rebuilds

```typescript
// Webhook handler
export async function POST(request: Request) {
  const body = await request.json();

  // Verify webhook signature
  const signature = request.headers.get('x-webhook-signature');
  if (!verifySignature(signature, body)) {
    return new Response('Invalid signature', { status: 401 });
  }

  // Trigger revalidation
  if (body._type === 'post') {
    await revalidatePath(`/blog/${body.slug}`);
  }

  // Or trigger full rebuild
  await fetch(process.env.DEPLOY_HOOK_URL, {
    method: 'POST'
  });

  return new Response('OK');
}
```

### Scheduled Publishing

```typescript
// Cron job for scheduled content
import { CronJob } from 'cron';

new CronJob('*/5 * * * *', async () => {
  const now = new Date();

  // Find scheduled posts
  const posts = await cms.fetch(`
    *[_type == "post" && publishedAt <= $now && !published] {
      _id,
      title,
      slug
    }
  `, { now: now.toISOString() });

  // Publish each post
  for (const post of posts) {
    await cms.patch(post._id).set({ published: true }).commit();
    await revalidatePath(`/blog/${post.slug}`);
  }
}).start();
```

## Strategy 4: Localization

### Multi-Language

```typescript
// Content in multiple languages
interface LocalizedContent {
  title: {
    en: string;
    es: string;
    fr: string;
  };
  content: {
    en: ContentBlock[];
    es: ContentBlock[];
    fr: ContentBlock[];
  };
}

// Fetch localized
const article = await cms.fetch(`
  *[_type == "article" && slug.current == $slug][0] {
    "title": title.$locale,
    "content": content.$locale
  }
`, { slug, locale: 'en' });
```

### Translation Workflow

```typescript
// Track translation status
interface TranslationStatus {
  locale: string;
  status: 'draft' | 'review' | 'published';
  translator: string;
  lastUpdated: Date;
}
```

## Strategy 5: Performance

### CDN Caching

```typescript
// Cache strategy
const article = await cms.fetch(
  query,
  params,
  {
    useCdn: true,
    cache: 'force-cache',
    next: { revalidate: 3600 } // 1 hour
  }
);
```

### Image Optimization

```typescript
// Auto-optimize images
import { buildImageUrl } from '@sanity/image-url';

const imageUrl = buildImageUrl(client)
  .image(article.coverImage)
  .width(1200)
  .height(630)
  .format('webp')
  .quality(85)
  .url();
```

### Incremental Static Regeneration

```typescript
// Next.js ISR
export async function generateStaticParams() {
  const posts = await cms.fetch(`
    *[_type == "post"][0...100] {
      slug
    }
  `);

  return posts.map(post => ({
    slug: post.slug.current
  }));
}
```

## Migration Guide

### From WordPress

```typescript
// Export WordPress content
const posts = await wpApi.posts().perPage(100);

// Import to headless CMS
for (const post of posts) {
  await cms.create({
    _type: 'post',
    title: post.title.rendered,
    slug: { current: post.slug },
    content: convertWPContent(post.content.rendered),
    publishedAt: post.date
  });
}
```

### Data Transformation

Clean and structure during migration.

## Workflow Setup

### Editorial Workflow

```typescript
// Content states
type ContentStatus = 
  | 'draft'
  | 'in_review'
  | 'approved'
  | 'published'
  | 'archived';

// Status transitions
const workflow = {
  draft: ['in_review', 'archived'],
  in_review: ['draft', 'approved', 'archived'],
  approved: ['published', 'draft'],
  published: ['archived'],
  archived: ['draft']
};
```

### Permissions

```typescript
// Role-based access
const roles = {
  editor: ['create', 'update', 'delete'],
  writer: ['create', 'update'],
  reviewer: ['read', 'comment']
};
```

## Performance Metrics

| Metric | Target | Achieved |
|--------|--------|----------|
| API Response | <100ms | 80ms |
| Image Load | <500ms | 300ms |
| Page Build | <5s | 3s |
| Cache Hit | >90% | 95% |

## FAQ

### Q1: Which CMS to choose?

Sanity for flexibility, Contentful for enterprise, Strapi for self-hosted.

### Q2: Content versioning?

Most headless CMS have built-in version history.

### Q3: Preview environment?

Use draft mode and separate preview deployment.

### Q4: Migration complexity?

Plan for 2-4 weeks depending on content volume.

### Q5: Cost at scale?

Varies by CMS. Self-hosted = hosting cost, managed = per-API-call.

## Production Setup

### Checklist
- [ ] Content model designed
- [ ] API keys configured
- [ ] Webhooks set up
- [ ] Preview mode working
- [ ] CDN configured
- [ ] Backups enabled
- [ ] Team trained
- [ ] Documentation written

## Conclusion

Great content systems enable great content.

Key takeaways:
- Model content properly
- Use webhooks for automation
- Optimize for performance
- Enable real-time preview
- Plan migration carefully

Build content systems that scale.

**Resources:**
- CMS Comparison
- Content Modeling Guide
- Migration Tools
- Performance Tips

**Next Steps:**
1. Choose CMS platform
2. Design content model
3. Set up preview
4. Migrate content
5. Optimize delivery

Modernize content management today.

