Ghost

ブログ・パブリッシング特化のヘッドレスCMS。モダンな執筆体験とSEO最適化機能を提供。

ヘッドレスCMSNode.jsオープンソースブログプラットフォームREST API
ライセンス
MIT
言語
JavaScript
料金
オープンソース版無料

ヘッドレスCMS

Ghost

概要

Ghostは、Node.jsで構築されたオープンソースのヘッドレスCMSです。元々はブログプラットフォームとして開発されましたが、強力なContent APIとAdmin APIを提供し、完全なヘッドレスCMSとして機能します。美しいEmber.js製の管理画面と、開発者向けのRESTful JSON APIを組み合わせ、コンテンツの作成、管理、配信を効率的に行えます。

詳細

GhostはAPI-firstアーキテクチャを採用し、Content API(読み取り専用)とAdmin API(読み書き可能)の2つの主要なAPIを提供します。標準的なブログ機能に加えて、カスタムページ、タグ、著者管理、SEO最適化などの機能を持ち、静的サイトジェネレータとの統合も得意としています。

主な特徴:

  • Content API: 公開コンテンツの読み取り専用アクセス
  • Admin API: 完全な読み書きアクセスとロールベース認証
  • Ember.js管理画面: 美しく使いやすいエディター
  • Markdown対応: Markdownエディターとリッチテキストエディター
  • 組み込みSEO: メタデータの自動生成とSEO最適化
  • 画像最適化: 自動的な画像リサイズと最適化
  • メンバーシップ機能: 有料購読とメンバー管理(ヘッドレスモードでは制限あり)
  • テーマシステム: Handlebarsテンプレートエンジン

メリット・デメリット

メリット

  • 完全なオープンソース(MIT License)
  • Node.jsエコシステムとの親和性
  • 優れたエディター体験
  • 高速なパフォーマンス
  • 強力なAPIドキュメント
  • Ghost(Pro)による管理サービス
  • アクティブなコミュニティ
  • SEO機能が充実

デメリット

  • ヘッドレスモードでメンバーシップ機能が使えない
  • 複雑なコンテンツモデリングには不向き
  • プラグインシステムが限定的
  • エンタープライズ機能が少ない
  • フロントエンドとの統合に追加作業が必要
  • Webhookの柔軟性が低い

参考ページ

書き方の例

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

# Ghostのインストール(開発環境)
npm install ghost-cli@latest -g

# 新しいディレクトリでGhostをセットアップ
mkdir my-ghost-blog && cd my-ghost-blog
ghost install local

# 本番環境でのインストール
ghost install
// Content APIクライアントの設定
import GhostContentAPI from '@tryghost/content-api'

const api = new GhostContentAPI({
  url: 'https://your-ghost-site.com',
  key: 'your-content-api-key',
  version: 'v5.0'
})

// 最新の投稿を取得
api.posts
  .browse({ limit: 5, include: 'tags,authors' })
  .then((posts) => {
    posts.forEach((post) => {
      console.log(post.title)
    })
  })
  .catch((err) => {
    console.error(err)
  })

2. コンテンツ管理

// Admin APIを使用したコンテンツ作成
import GhostAdminAPI from '@tryghost/admin-api'

const adminApi = new GhostAdminAPI({
  url: 'https://your-ghost-site.com',
  key: 'your-admin-api-key',
  version: 'v5.0'
})

// 新しい投稿を作成
const newPost = {
  title: 'My New Post',
  slug: 'my-new-post',
  mobiledoc: JSON.stringify({
    version: '0.3.1',
    atoms: [],
    cards: [],
    markups: [],
    sections: [
      [1, 'p', [[0, [], 0, 'This is my post content']]]
    ]
  }),
  status: 'published',
  tags: ['Getting Started'],
  authors: ['your-author-id']
}

adminApi.posts.add(newPost)
  .then(response => console.log(response))
  .catch(error => console.error(error))

// 投稿の更新
adminApi.posts.edit({
  id: 'post-id',
  updated_at: 'current-updated-at',
  title: 'Updated Title'
})
  .then(response => console.log(response))
  .catch(error => console.error(error))

3. API操作

// 高度なクエリとフィルタリング
// タグでフィルタリング
api.posts
  .browse({
    filter: 'tag:javascript',
    limit: 10,
    include: 'tags,authors'
  })
  .then(posts => console.log(posts))

// ページネーション
api.posts
  .browse({
    page: 2,
    limit: 15
  })
  .then(posts => {
    console.log(posts.meta.pagination)
  })

// 単一の投稿を取得
api.posts
  .read({ slug: 'welcome' })
  .then(post => console.log(post))

// タグの取得
api.tags
  .browse({ limit: 'all' })
  .then(tags => {
    tags.forEach(tag => {
      console.log(`${tag.name}: ${tag.count.posts} posts`)
    })
  })

// 著者情報の取得
api.authors
  .browse({ include: 'count.posts' })
  .then(authors => console.log(authors))

4. 認証設定

// Content API キーの取得と設定
// Ghost管理画面 → Integrations → Custom Integration → Add

// 環境変数での管理
const api = new GhostContentAPI({
  url: process.env.GHOST_API_URL,
  key: process.env.GHOST_CONTENT_API_KEY,
  version: 'v5.0'
})

// Admin API認証(JWT)
import jwt from 'jsonwebtoken'

function getAdminToken() {
  const [id, secret] = process.env.GHOST_ADMIN_API_KEY.split(':')
  
  const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
    keyid: id,
    algorithm: 'HS256',
    expiresIn: '5m',
    audience: `/admin/`
  })
  
  return token
}

// カスタムヘッダーでの認証
fetch(`${ghostUrl}/ghost/api/admin/posts/`, {
  headers: {
    'Authorization': `Ghost ${getAdminToken()}`,
    'Content-Type': 'application/json'
  }
})

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

// Webhookの設定(Ghost管理画面から設定)
// Settings → Integrations → Webhooks

// Webhook受信エンドポイントの例
app.post('/webhook/ghost', (req, res) => {
  const { post } = req.body
  
  // 投稿が公開されたときの処理
  if (post.current.status === 'published') {
    console.log(`New post published: ${post.current.title}`)
    
    // 静的サイトの再ビルドをトリガー
    triggerStaticSiteBuild()
  }
  
  res.status(200).send('OK')
})

// カスタムヘルパーの作成
// themes/your-theme/helpers/custom-helper.js
module.exports = function customHelper(options) {
  // カスタムロジック
  return new Handlebars.SafeString(output)
}

// APIを拡張するカスタムアプリ
const customApp = express()

customApp.get('/api/custom/stats', async (req, res) => {
  const posts = await api.posts.browse({ limit: 'all' })
  const stats = {
    totalPosts: posts.length,
    totalWords: posts.reduce((sum, post) => sum + post.word_count, 0)
  }
  res.json(stats)
})

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

// Ghost(Pro)へのデプロイは自動

// セルフホスティングの場合
// config.production.json
{
  "url": "https://your-domain.com",
  "server": {
    "port": 2368,
    "host": "0.0.0.0"
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost",
      "password": "your-password",
      "database": "ghost_production"
    }
  },
  "mail": {
    "transport": "SMTP",
    "options": {
      "service": "Mailgun",
      "auth": {
        "user": "[email protected]",
        "pass": "your-mailgun-password"
      }
    }
  }
}

// Next.jsとの統合例
// pages/index.js
export async function getStaticProps() {
  const posts = await api.posts.browse({
    limit: 'all',
    include: 'tags,authors',
    filter: 'status:published'
  })

  return {
    props: { posts },
    revalidate: 60 // ISR
  }
}

// Gatsby統合
// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-ghost`,
      options: {
        apiUrl: `https://your-ghost-site.com`,
        contentApiKey: `your-content-api-key`,
      }
    }
  ]
}

// Nginxリバースプロキシ設定
server {
  listen 80;
  server_name your-domain.com;
  
  location / {
    proxy_pass http://127.0.0.1:2368;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}