Ghost
ブログ・パブリッシング特化のヘッドレスCMS。モダンな執筆体験とSEO最適化機能を提供。
ヘッドレス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;
}
}