Contentful

企業向けのクラウドネイティブヘッドレスCMS。強力なCDN、リアルタイム配信、豊富な統合機能を提供。

ヘッドレスCMSAPI駆動型クラウドベース多言語対応エンタープライズ
ライセンス
Commercial
言語
SaaS
料金
無料プランあり(制限あり)

ヘッドレスCMS

Contentful

概要

Contentfulは、コンテンツをAPIとして提供するクラウドベースのヘッドレスCMSです。コンテンツモデルの柔軟な設計、強力なContent Delivery API、多言語対応、リアルタイムプレビューなどの機能を提供し、大規模なマルチチャネル配信に適しています。エンタープライズ向けの機能も充実しており、開発者とコンテンツ編集者の両方にとって使いやすいプラットフォームです。

詳細

Contentfulは、API-firstアプローチを採用したヘッドレスCMSのパイオニアです。コンテンツを構造化されたデータとして管理し、RESTful APIとGraphQL APIを通じて配信します。強力なコンテンツモデリング機能により、複雑なデータ構造も柔軟に定義できます。

主な特徴:

  • 柔軟なコンテンツモデリング: カスタムコンテンツタイプとフィールドの定義
  • Content Delivery API: 高速でスケーラブルなコンテンツ配信
  • Content Management API: プログラムによるコンテンツ管理
  • リアルタイムプレビュー: 編集中のコンテンツを即座に確認
  • 多言語対応: 複数言語のコンテンツを一元管理
  • 画像変換API: 動的な画像の最適化とリサイズ
  • Webhooks: コンテンツ変更時の自動通知
  • ロールベースアクセス制御: 細かな権限管理

メリット・デメリット

メリット

  • クラウドホスティングで運用管理が不要
  • 優れたドキュメントとSDKサポート
  • グローバルCDNによる高速配信
  • 強力なコンテンツモデリング機能
  • エンタープライズグレードのセキュリティ
  • 充実したサードパーティ統合
  • スケーラビリティが高い
  • 開発者フレンドリーなツール

デメリット

  • 価格が比較的高い(特に大規模利用時)
  • データがクラウドに依存
  • カスタマイズの制限がある
  • 学習曲線がやや急
  • APIレート制限がある
  • セルフホスティング不可

参考ページ

書き方の例

1. Hello World(基本的なセットアップ)

// Contentful SDKのインストール
// npm install contentful

import * as contentful from 'contentful'

// クライアントの作成
const client = contentful.createClient({
  space: 'your-space-id',
  accessToken: 'your-access-token'
})

// エントリーの取得
client.getEntry('entry-id')
  .then((entry) => {
    console.log('Entry:', entry)
  })
  .catch((err) => {
    console.error('Error:', err)
  })

// 全エントリーの取得
client.getEntries()
  .then((response) => {
    console.log('Total entries:', response.total)
    response.items.forEach((item) => {
      console.log(item.fields)
    })
  })

2. コンテンツ管理

// Content Management APIの使用
import { createClient } from 'contentful-management'

const client = createClient({
  accessToken: 'your-management-token'
})

// スペースの取得
client.getSpace('space-id')
  .then((space) => space.getEnvironment('master'))
  .then((environment) => {
    // コンテンツタイプの作成
    return environment.createContentType({
      name: 'Blog Post',
      fields: [
        {
          id: 'title',
          name: 'Title',
          type: 'Text',
          required: true
        },
        {
          id: 'content',
          name: 'Content',
          type: 'RichText'
        },
        {
          id: 'author',
          name: 'Author',
          type: 'Link',
          linkType: 'Entry'
        }
      ]
    })
  })
  .then((contentType) => {
    console.log('Content type created:', contentType)
    return contentType.publish()
  })

// エントリーの作成
environment.createEntry('blogPost', {
  fields: {
    title: {
      'en-US': 'My First Blog Post'
    },
    content: {
      'en-US': {
        nodeType: 'document',
        content: [{
          nodeType: 'paragraph',
          content: [{
            nodeType: 'text',
            value: 'Hello, Contentful!'
          }]
        }]
      }
    }
  }
})

3. API操作

// GraphQL APIの使用
const GRAPHQL_ENDPOINT = `https://graphql.contentful.com/content/v1/spaces/${spaceId}`

const query = `
  query GetBlogPosts($limit: Int!) {
    blogPostCollection(limit: $limit) {
      total
      items {
        sys {
          id
          publishedAt
        }
        title
        content {
          json
        }
        author {
          name
          bio
        }
      }
    }
  }
`

fetch(GRAPHQL_ENDPOINT, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${accessToken}`
  },
  body: JSON.stringify({
    query,
    variables: { limit: 10 }
  })
})
.then(response => response.json())
.then(data => console.log(data))

// Sync APIの使用(差分取得)
let syncToken = null

async function syncContent() {
  const response = await client.sync({
    initial: !syncToken,
    ...(syncToken && { nextSyncToken: syncToken })
  })
  
  console.log('New entries:', response.entries)
  console.log('Updated entries:', response.entries)
  console.log('Deleted entries:', response.deletedEntries)
  
  syncToken = response.nextSyncToken
  return response
}

4. 認証設定

// Preview APIの設定
const previewClient = contentful.createClient({
  space: 'your-space-id',
  accessToken: 'your-preview-token',
  host: 'preview.contentful.com'
})

// 環境変数の使用
const client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
  environment: process.env.CONTENTFUL_ENVIRONMENT || 'master'
})

// カスタムHTTPクライアントの設定
const client = contentful.createClient({
  space: 'space-id',
  accessToken: 'access-token',
  httpAgent: new https.Agent({
    keepAlive: true
  }),
  proxy: {
    host: 'proxy.example.com',
    port: 8080
  }
})

// レート制限の処理
client.getEntries()
  .catch((error) => {
    if (error.status === 429) {
      const resetTime = error.headers['x-contentful-ratelimit-reset']
      console.log(`Rate limited. Reset at: ${new Date(resetTime * 1000)}`)
    }
  })

5. プラグイン・拡張機能

// UI Extension(カスタムフィールド)の作成
const extension = {
  id: 'color-picker',
  name: 'Color Picker',
  fieldTypes: ['Symbol'],
  src: 'https://example.com/color-picker.html',
  parameters: {
    instance: [
      {
        id: 'defaultColor',
        name: 'Default Color',
        type: 'Symbol',
        default: '#000000'
      }
    ]
  }
}

environment.createUiExtension(extension)
  .then((ext) => ext.publish())

// App Framework(カスタムアプリ)
import { AppExtensionSDK } from '@contentful/app-sdk'

const sdk = await AppExtensionSDK.init()

// カスタムサイドバーの実装
if (sdk.location.is(locations.LOCATION_ENTRY_SIDEBAR)) {
  const entry = await sdk.entry.get()
  
  // エントリーの更新
  await sdk.entry.fields.title.setValue('New Title')
  
  // 通知の表示
  sdk.notifier.success('Entry updated!')
}

// Webhookの設定
environment.createWebhook({
  name: 'Deploy Hook',
  url: 'https://api.netlify.com/build_hooks/xyz',
  topics: [
    'Entry.publish',
    'Entry.unpublish',
    'Entry.delete'
  ],
  filters: [
    {
      equals: [{ doc: 'sys.contentType.sys.id' }, 'blogPost']
    }
  ]
})

6. デプロイ・本番環境設定

// Next.jsとの統合
// pages/api/preview.js
export default async function handler(req, res) {
  const { secret, slug } = req.query

  if (secret !== process.env.PREVIEW_SECRET || !slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  // Contentfulからプレビューデータを取得
  const previewClient = createClient({
    space: process.env.CONTENTFUL_SPACE_ID,
    accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN,
    host: 'preview.contentful.com'
  })

  const entry = await previewClient.getEntries({
    content_type: 'page',
    'fields.slug': slug
  })

  if (!entry.items.length) {
    return res.status(401).json({ message: 'Invalid slug' })
  }

  // プレビューモードを有効化
  res.setPreviewData({})
  res.redirect(`/${slug}`)
}

// 静的サイト生成(SSG)
export async function getStaticProps() {
  const entries = await client.getEntries({
    content_type: 'blogPost',
    order: '-sys.createdAt'
  })

  return {
    props: {
      posts: entries.items
    },
    revalidate: 60 // ISRの設定
  }
}

// CDNキャッシュの活用
const client = contentful.createClient({
  space: 'space-id',
  accessToken: 'access-token',
  retryOnError: true,
  // レスポンスキャッシュの設定
  responseLogger: (response) => {
    console.log('Cache:', response.headers['x-cache'])
    console.log('CDN:', response.headers['x-served-by'])
  }
})

// 環境の管理
const environment = await space.createEnvironment('staging', {
  name: 'Staging',
  sourceEnvironmentId: 'master'
})

// 環境間のコンテンツ同期
await environment.createEntry('blogPost', masterEntry.toPlainObject())