
Contentful App Framework チュートリアル検証 - Create a custom app 編
はじめに
Contentful の公式チュートリアル の 「Create a custom app」 を実際に実行したログをまとめました。
Contentful とは
Contentful は、コンテンツを API 経由で配信するヘッドレス CMS (Content Management System) です。従来の CMS と異なり、フロントエンド表示をコンテンツから分離することで、 Web サイトや生成 AI に学習させるナレッジ用途など柔軟にコンテンツを利用できるようになります。
Contentful の Custom App とは
Contentful Custom App は、 Contentful の管理画面内で動作するカスタム機能を開発するための仕組みです。
活用例
- エントリーの文字数計算
- Cloudinary など外部 API との連携
Custom App は React ベースで開発します。 Contentful App Framework を使用して管理画面の様々な場所 (例: サイドバー、フィールドエディタ、ダイアログ、 etc...) に配置できます。
対象読者
- Contentful Custom App の開発に興味がある
- 公式の英語チュートリアルを日本語で理解したい
- 実際やってみたときに遭遇する問題と解決策を事前に知りたい
- Contentful の拡張性について知りたい
参考
検証
環境構築
まず、 Contentful App Framework のプロジェクトを作成します。 Template/Example
の選択肢が表示されたら Template
を選択します。プログラミング言語は、今回の検証では TypeScript
を選択しました。
npx create-contentful-app my-first-app
ローカル開発サーバーの起動
プロジェクト作成後、開発サーバーを起動します。
cd my-first-app
npm run start
http://localhost:3000
にブラウザでアクセスすると、次の文章が表示されます。
App の実装
チュートリアルに従って、サイドバーにブログ記事の「Word count」と「Reading time」を表示するアプリを実装します。今回は、src/locations/Sidebar.tsx
を以下のように編集します。
import React, { useState, useEffect } from 'react';
import { List, ListItem, Note } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';
import { SidebarAppSDK } from '@contentful/app-sdk';
const CONTENT_FIELD_ID = 'body';
const WORDS_PER_MINUTE = 200;
const Sidebar = () => {
const sdk = useSDK<SidebarAppSDK>();
const contentField = sdk.entry.fields[CONTENT_FIELD_ID];
const [blogText, setBlogText] = useState('');
useEffect(() => {
// フィールド値を文字列に変換する関数
const processFieldValue = (value) => {
if (value && typeof value === 'object' && value.nodeType === 'document') {
// Rich Text の場合はテキストを抽出
return extractTextFromRichText(value);
} else if (typeof value === 'string') {
return value;
}
return '';
};
// 初期値の設定
const initialValue = contentField.getValue();
setBlogText(processFieldValue(initialValue));
// フィールド変更の監視
const detach = contentField.onValueChanged((value) => {
setBlogText(processFieldValue(value));
});
return () => detach();
}, [contentField]);
// Rich Text オブジェクトからプレーンテキストを抽出
const extractTextFromRichText = (richTextDoc) => {
if (!richTextDoc || !richTextDoc.content) return '';
let text = '';
const extractFromNode = (node) => {
if (node.nodeType === 'text') {
text += node.value + ' ';
} else if (node.content && Array.isArray(node.content)) {
node.content.forEach(extractFromNode);
}
};
richTextDoc.content.forEach(extractFromNode);
return text.trim();
};
// 単語数と読書時間を計算
const readingTime = (text) => {
if (!text || typeof text !== 'string') {
return { words: 0, text: '0 min read' };
}
const wordCount = text.split(' ').filter(word => word.length > 0).length;
const minutes = Math.ceil(wordCount / WORDS_PER_MINUTE);
return {
words: wordCount,
text: `${minutes} min read`,
};
};
const stats = readingTime(blogText);
return (
<>
<Note style={{ marginBottom: '12px' }}>
Metrics for your blog post:
<List style={{ marginTop: '12px' }}>
<ListItem>Word count: {stats.words}</ListItem>
<ListItem>Reading time: {stats.text}</ListItem>
</List>
</Note>
</>
);
};
export default Sidebar;
トラブルシューティング: Rich Text 形式への対応
実装中に以下のエラーに遭遇しました。
Uncaught TypeError: text.split is not a function
原因は、 body
フィールドが Rich Text 形式で設定されており、 contentField.getValue()
が文字列ではなくオブジェクト構造を返していたことでした。
{
"nodeType": "document",
"data": {},
"content": [
{
"nodeType": "paragraph",
"data": {},
"content": [
{
"nodeType": "text",
"value": "実際のテキスト内容",
"marks": [],
"data": {}
}
]
}
]
}
このオブジェクトに対して text.split()
を実行しようとしたため、エラーが発生していました。解決のため、 Rich Text オブジェクトから再帰的にテキストを抽出する extractTextFromRichText
関数を実装しました。
Contentful 管理画面での統合
ローカルでの実装が完了したら、実際に Contentful 管理画面で App を動作させるための設定を行います。
App の設定
- Contentful コンソールを開き、Apps > Custom apps > Manage app definitions から Create app を選択
- App を設定
- Name: 任意の名前 (例:
Blog Post Metrics
) - Frontend:
http://localhost:3000
- Locations: Entry sidebar を選択
- Name: 任意の名前 (例:
- Save をクリックし保存
スペースへの App インストール
- 使用したいスペースに移動
- 上部メニューの Apps > Custom apps を選択
- 作成したアプリの Install を選択
Content Type への割り当て
- Content model を開き
BlogPost
Content Type を選択 - 左ペインメニューより Sidebar を選択
- Available items から Blog Post Metrics を見つけて + ボタンをクリック
- Save をクリックして変更を保存
動作確認
設定完了後、実際に App が動作することを確認します。
- Content を開き BlogPost エントリーを作成または編集
- エントリー編集画面のサイドバーに「Blog Post Metrics」が表示されるのを確認
body
の内容を編集すると、即座にメトリクスが再計算されて表示が更新されます。
トラブルシューティング
実際の検証の中で起きた問題とその解決方法について記載します。
リロード後 App が表示されなくなった
検証のため再インストールを行ったため、Content Type のサイドバー設定で Blog Post Metrics の表示設定が外れてしまっていたことが原因でした。Content model > BlogPost > Sidebar で再度追加したところ解決しました。
コードの変更が反映されない
実装したコードではなく「Hello Sidebar Component」が表示されました。localhost を停止してアプリの動作を確認したところエラー表示に変化したため、Contentful がリアルタイムで localhost:3000 を参照していることは確認できました。
最終的な原因としては、テストファイルである Sidebar.spec.tsx
を編集していたことが原因でした。正しく Sidebar.tsx
を編集したところ解決しました。
console.log
.tsx ファイルに console.log を仕込んでおくことで、ブラウザのデベロッパーツールの Console から動作検証を行うことが可能です。
console.log('=== Rich Text Debug ===');
console.log('Full Rich Text object:', JSON.stringify(richTextDoc, null, 2));
console.log('Content array:', richTextDoc.content);
console.log('======================');
まとめと所感
Contentful App Framework は、管理画面の機能を自由度高く拡張できるツールであり、非常に簡単にカスタム機能を実装できることが分かりました。ただし、Rich Text フィールドの対応など実際の開発では公式チュートリアルに記載されていない課題に遭遇することがあります。問題に遭遇した際は、 console.log でのデバッグやエラーメッセージの確認を通じた、原因の切り分けが解決への近道です。