Menu

CMS モジュールの使用方法

注目すべき点

v3.2.3 - 3.2.4 の更新で、NEXTY.DEV の CMS は TipTap を使用してリファクタリングされ、より多くの機能を獲得し、コンテンツエディタの使用体験もより完璧になりました。

サポートされている機能

NEXTY.DEV ボイラープレートが提供する CMS 機能は、すべての SaaS ボイラープレートの中で最も包括的なものの一つで、非常に豊富な機能をサポートしています。TipTap の豊富なプラグインエコシステムを通じて、さらに多くの機能を簡単に拡張できます。

以下は、NEXTY.DEV ボイラープレートが既にサポートしている機能です:

コア機能

  • 複数コンテンツモジュールサポート:デフォルトでブログ(blog)と用語集(glossary)の2つのモジュールを提供
  • 統一されたアーキテクチャ設計:基本コンポーネントが共通で、データテーブルが共有されており、最小限のファイルとコードを追加するだけで新しいコンテンツモジュールを作成可能
  • 複数コンテンツソースサポート:ローカル MDX ファイルのレンダリングとサーバーサイド CMS データのレンダリングをサポートし、各モジュールは独立して設定可能
  • 閲覧統計:閲覧数の統計をサポートし、2つのモードを提供:
    • all モード:ページ読み込みのたびにカウント
    • unique モード:同じ IP アドレスで1時間に1回のみカウント
  • 目次ナビゲーション(TOC):詳細ページで記事の目次を自動生成し、デスクトップサイドバーとモバイル折りたたみ表示をサポート
  • 関連記事の推薦:詳細ページで関連記事の表示をサポート(同じタグを判断基準として)
  • カバー画像サポート:ローカルアップロード、外部リンク、CloudFlare R2 選択をサポート
  • AI 翻訳:エディタに組み込まれた翻訳ボタンをサポートし、選択したコンテンツの迅速な翻訳を可能に
  • 柔軟な設定:ツールバーのほとんどのプラグインは設定を通じて有効化または無効化可能

コンテンツ管理機能

コンテンツの作成・編集でサポートされる機能:

  • 基本情報
    • タイトル(title)、URL エイリアス(slug)、説明(description)
    • タグ(tags)管理、最大5つのタグをサポート
    • カバー画像アップロード(ローカルアップロード、外部リンク、CloudFlare R2 選択をサポート)
  • 多言語サポート
    • コンテンツ言語の選択(設定されたすべての言語をサポート)
    • 異なる言語で同じ slug の設計をサポート
  • 高度な機能
    • ステータス設定:下書き(draft)、公開(published)、アーカイブ(archived)
    • 可視性制御:公開(public)、ログインユーザー(logged_in)、購読者(subscribers)
    • ピン留め機能:重要なコンテンツをピン留めして表示することをサポート
  • コンテンツ保護:ログインまたは購読が必要なコンテンツの場合、システムは自動的にアクセス制限のプロンプトを表示

ダッシュボード管理機能

  • データテーブル表示
    • すべてのコンテンツのページネーション付き閲覧
    • 検索フィルタリングをサポート(タイトル、slug、説明)
    • ステータス、言語によるフィルタリングをサポート
  • タグ管理
    • タグの作成、編集、削除
    • タグはコンテンツモジュールに関連付け(各モジュールは独立したタグシステムを持つ)
  • 一括操作
    • コンテンツの複製(duplicate)機能
    • コンテンツの削除
  • カバー画像管理
    • リストページでカバー画像を表示するかどうかの設定をサポート(showCoverInList 設定項目)
    • カバー画像がアップロードされていない場合、動的 OG 画像を自動生成

ディレクトリ構造

components/cms/               // CMS 共通コンポーネント
  ├─ PostDataTable.tsx        // ダッシュボードリスト & ページネーション
  ├─ PostEditorClient.tsx     // 作成/編集エントリ(クライアントコンポーネント)
  ├─ PostForm.tsx             // フォームコンポーネント
  ├─ PostList.tsx             // フロントエンド無限スクロールリスト
  ├─ PostCard.tsx             // フロントエンドコンテンツカード
  ├─ PostListActions.tsx      // ダッシュボードアクションボタン(編集、複製、削除)
  ├─ RelatedPosts.tsx         // 関連記事コンポーネント
  ├─ ContentRestrictionMessage.tsx  // コンテンツアクセス制限プロンプト
  ├─ ImageUpload.tsx          // 画像アップロードコンポーネント(R2 をサポート)
  ├─ TagInput.tsx             // タグ入力コンポーネント
  ├─ TagSelector.tsx          // タグセレクター(フロントエンドフィルタリング)
  ├─ TagSelectDialog.tsx      // タグ選択ダイアログ
  ├─ TagCreateForm.tsx        // タグ作成フォーム
  ├─ TagManagementDialog.tsx  // タグ管理ダイアログ
  └─ post-config.ts           // PostType 設定センター

components/tiptap/            // TipTap リッチテキストエディタ
  ├─ TiptapEditor.tsx         // リッチテキストエディタメインコンポーネント
  ├─ TiptapRenderer.tsx       // フロントエンド読み取り専用レンダラー
  ├─ ImageGridExtension.ts    // 画像グリッドカスタム Node
  ├─ ImageGridNodeView.tsx    // 編集モード画像グリッドビュー
  ├─ ImageGridReadOnlyView.tsx // 読み取りモード画像グリッドビュー
  ├─ R2ResourceSelector.tsx   // R2 メディアセレクター
  ├─ TableMenu.tsx            // テーブルフローティングツールバー
  ├─ TableOfContents.tsx      // 目次生成コンポーネント
  └─ TranslationButton.tsx    // AI 翻訳ボタン

lib/cms/
  └─ index.ts                 // CMS モジュールファクトリー関数

actions/posts/                // Server Actions
  ├─ posts.ts                 // コンテンツ CRUD + フロントエンド読み取り
  ├─ tags.ts                  // タグ CRUD
  └─ views.ts                 // 閲覧数統計

app/[locale]/(protected)/dashboard/(admin)/blogs/
  ├─ page.tsx                 // ダッシュボードリストページ
  ├─ Columns.tsx              // テーブル列定義
  ├─ new/page.tsx             // 作成ページ
  └─ [postId]/edit/page.tsx   // 編集ページ

app/[locale]/(protected)/dashboard/(admin)/glossary/
  ├─ page.tsx                 // ダッシュボードリストページ
  ├─ Columns.tsx              // テーブル列定義
  ├─ new/page.tsx             // 作成ページ
  └─ [postId]/edit/page.tsx   // 編集ページ

app/[locale]/(basic-layout)/blog/
  ├─ page.tsx                 // フロントエンドリスト
  └─ [slug]/
      ├─ page.tsx             // 詳細ページ
      └─ opengraph-image.tsx  // 動的 OG 画像

app/[locale]/(basic-layout)/glossary/
  ├─ page.tsx                 // フロントエンドリスト
  └─ [slug]/
      ├─ page.tsx             // 詳細ページ
      └─ opengraph-image.tsx  // 動的 OG 画像

lib/db/schema.ts              // Drizzle データモデル
  // 関連テーブル:posts、tags、postTags

types/cms.ts                  // TypeScript 型定義

config/common.ts              // 共通設定(画像パスなど)

設定説明

PostConfig 設定項目

components/cms/post-config.ts で、各コンテンツモジュールには以下の設定項目があります:

export interface PostConfig {
  postType: PostType;                    // コンテンツタイプ
  schema: z.ZodSchema;                   // 検証スキーマ
  actionSchema: z.ZodSchema;             // アクション検証スキーマ
  imagePath: string;                     // 画像保存パス
  enableTags: boolean;                   // タグを有効にするかどうか
  localDirectory?: string;               // ローカル MDX ファイルディレクトリ(オプション)
  viewCount: ViewCountConfig;            // 閲覧数統計設定
  showCoverInList: boolean;              // リストページでカバー画像を表示するかどうか
  routes: {                              // ルート設定
    list: string;
    create: string;
    edit: (id: string) => string;
  };
}

閲覧数統計設定

export interface ViewCountConfig {
  enabled: boolean;      // 閲覧数統計を有効にするかどうか
  mode: 'all' | 'unique'; // 統計モード
  showInUI: boolean;     // UI に表示するかどうか
}

機能展示

CMS 機能はすべて WYSIWYG であるため、ここでは詳しく説明しません。画像を通じて機能を理解できます。

cms
cms
cms
cms
cms
cms

画像アップロードはローカルアップロード、外部リンク、CloudFlare R2 選択をサポート

cms
cms

画像表示は複数の画像を並べて表示することをサポート

cms

YouTube 動画の挿入をサポート

cms

エディタコンテンツはクイック翻訳をサポート

cms

コンテンツエディタ機能の変更方法

コンテンツエディタのすべての機能ファイルは components/tiptap の下にあります。コードに慣れた後で変更することも、AI に変更を依頼することもできます。AI は Tiptap に非常に精通しています。

エディタ設定オプション

TiptapEditor コンポーネントは以下の設定をサポートします:

interface TiptapEditorProps {
  content: string;
  onChange: (content: string) => void;
  placeholder?: string;
  disabled?: boolean;
  enableYoutube?: boolean;        // YouTube 動画を有効にする
  enableImage?: boolean;          // 画像アップロードを有効にする
  enableCodeBlock?: boolean;      // コードブロックを有効にする
  enableTable?: boolean;          // テーブルを有効にする
  enableBlockquote?: boolean;     // ブロッククォートを有効にする
  enableHorizontalRule?: boolean; // 水平線を有効にする
  enableR2Selector?: boolean;     // R2 リソースセレクターを有効にする
  r2PublicUrl?: string;           // R2 パブリックアクセス URL
  imageUploadConfig?: {
    maxSize?: number;             // ファイルサイズ制限(バイト)
    filenamePrefix?: string;      // ファイル名プレフィックス
    path?: string;                // アップロードパス
  };
  enableTranslation?: boolean;    // AI 翻訳を有効にする
  postType?: PostType;            // コンテンツタイプ
  outputFormat?: "markdown" | "text" | "html"; // 出力形式
}

新しいコンテンツモジュールの追加方法

コンテンツサイトを開発している場合、ブログと用語集モジュールでは不十分な場合があります。複数のコンテンツモジュールを追加して、サイトのコンテンツ構造を豊かにしたい場合、NEXTY.DEV ボイラープレートを使用すれば簡単に解決できます。

新しい guide(ガイド)モジュールを追加する例として、以下の手順を完了するだけで済みます:

1. データベーススキーマの拡張

lib/db/schema.ts で CMS コンテンツタイプを拡張:

export const postTypeEnum = pgEnum('post_type', [
  'blog',
  'glossary',
  'guide', // 新規追加
])

次に、データベースマイグレーションコマンドを実行:

pnpm db:generate
pnpm db:migrate

2. CMS 設定の拡張

components/cms/post-config.ts で新しいモジュール設定を追加:

export const POST_CONFIGS: Record<PostType, PostConfig> = {
  blog: {
    // ... 既存の設定
  },
  glossary: {
    // ... 既存の設定
  },
  // 新しい guide 設定
  guide: {
    postType: "guide",
    schema: basePostSchema,
    actionSchema: postActionSchema,
    imagePath: GUIDE_IMAGE_PATH, // config/common.ts で定義する必要がある
    enableTags: true,
    // localDirectory: 'guides', // ローカル MDX ファイルをサポートする必要がある場合
    viewCount: {
      enabled: true,  // 閲覧数統計を有効にする
      mode: 'unique', // ユニーク IP 統計を使用
      showInUI: true,
    },
    showCoverInList: true,
    routes: {
      list: "/dashboard/guides",
      create: "/dashboard/guides/new",
      edit: (id: string) => `/dashboard/guides/${id}`,
    },
  },
};

3. 画像パス設定の追加

config/common.ts で追加:

export const GUIDE_IMAGE_PATH = "guide-images";
 
export const R2_CATEGORIES: R2Category[] = [
  { name: "All", prefix: "" },
  { name: "Admin Uploads", prefix: `${ADMIN_UPLOAD_IMAGE_PATH}/` },
  { name: "Blogs Images", prefix: `${BLOGS_IMAGE_PATH}/` },
  { name: "Glossary Images", prefix: `${GLOSSARY_IMAGE_PATH}/` },
  { name: "Guide Images", prefix: `${GUIDE_IMAGE_PATH}/` }, // 新規追加
];

4. ダッシュボード管理ページの作成

app/[locale]/(protected)/dashboard/(admin)/glossary フォルダをコピーし、guides にリネーム。

以下のファイルを変更:

guides/page.tsx

// postType を "guide" に変更
const result = await listPostsAction({
  pageIndex: 0,
  pageSize: PAGE_SIZE,
  postType: "guide", // ここを変更
});
 
// 設定を更新
<PostDataTable
  config={{
    postType: "guide", // ここを変更
    columns,
    listAction: listPostsAction,
    createUrl: "/dashboard/guides/new", // ここを変更
    enableTags: true,
    searchPlaceholder: "Search guides...", // ここを変更
  }}
  // ...
/>

guides/Columns.tsx:必要に応じてテーブル列定義を調整。

guides/new/page.tsxguides/[postId]/edit/page.tsx

<PostEditorClient
  postType="guide" // ここを変更
  mode="create" // または "edit"
  r2PublicUrl={r2PublicUrl}
  postId={postId}
/>

5. フロントエンド表示ページの作成

app/[locale]/(basic-layout)/glossary フォルダをコピーし、guide にリネーム。

guide/page.tsx

// CMS モジュールインスタンスを作成(lib/cms/index.ts で)
export const guideCms = createCmsModule('guide');
 
// ページで使用
const { posts: localPosts } = await guideCms.getLocalList(locale);
 
const initialServerPostsResult = await listPublishedPostsAction({
  pageIndex: 0,
  pageSize: SERVER_POST_PAGE_SIZE,
  postType: "guide", // ここを変更
  locale: locale,
});
 
// タグ
const tagsResult = await listTagsAction({ postType: "guide" });
 
// PostList コンポーネント
<PostList
  postType="guide"
  baseUrl="/guide"
  localPosts={localPosts}
  initialPosts={initialServerPosts}
  initialTotal={totalServerPosts}
  serverTags={serverTags}
  locale={locale}
  pageSize={SERVER_POST_PAGE_SIZE}
  showTagSelector={true}
  showCover={POST_CONFIGS.guide.showCoverInList}
  emptyMessage="No guides found for this tag."
/>

guide/[slug]/page.tsx

// guideCms を使用してコンテンツを取得
const { post, errorCode } = await guideCms.getBySlug(slug, locale);
 
// 閲覧数統計
const viewCountConfig = POST_CONFIGS.guide.viewCount;
if (viewCountConfig.enabled) {
  if (viewCountConfig.mode === "unique") {
    await incrementUniqueViewCountAction({ slug, postType: "guide", locale });
  } else {
    await incrementViewCountAction({ slug, postType: "guide", locale });
  }
}
 
// 関連記事
<RelatedPosts
  postId={post.id}
  postType="guide"
  limit={10}
  title="Related Guides"
  locale={locale}
  CardComponent={GuidePostCard}
/>

6. lib/cms/index.ts で新しいモジュールをエクスポート

// ファイルの末尾に追加
export const guideCms = createCmsModule('guide');

7. sitemap.ts の更新

app/sitemap.ts で Guide ページのルートを追加:

// guide リストページを追加
{
  url: `${siteUrl}/${locale}/guide`,
  lastModified: new Date(),
  changeFrequency: 'daily',
  priority: 0.8,
}
 
// guide 詳細ページを追加
const guideResult = await listPublishedPostsAction({
  locale: locale,
  pageSize: 1000,
  visibility: "public",
  postType: "guide",
});
 
if (guideResult.success && guideResult.data?.posts) {
  guideResult.data.posts.forEach((post) => {
    sitemap.push({
      url: `${siteUrl}/${locale}/guide/${post.slug}`,
      lastModified: post.updatedAt ? new Date(post.updatedAt) : new Date(),
      changeFrequency: 'weekly',
      priority: 0.7,
    });
  });
}

8. 国際化翻訳の追加

messages/ ディレクトリの各言語ファイルに関連する翻訳を追加。

注意事項

  1. ローカル MDX ファイルサポート:新しい CMS モジュールがローカル MDX ファイルをサポートする必要がない場合、localDirectory 設定項目を設定せず、データベースを直接使用してください。

  2. 独立したタグシステム:各コンテンツモジュールのタグシステムは独立しており、postType フィールドで区別されます。

  3. 画像パス計画:各コンテンツモジュールに独立した画像保存パスを設定し、管理と整理を容易にします。

  4. 閲覧数統計:閲覧数統計機能を使用するには Redis の設定が必要です。Redis が設定されていない場合、システムは適切に降格し、0 カウントを返します。

  5. コンテンツの可視性

  • public:すべての人に表示
  • logged_in:ログインが必要
  • subscribers:購読が必要(actions/posts/posts.tscheckUserSubscription 関数で購読ロジックをカスタマイズ)
  1. SEO 最適化
  • カバー画像がアップロードされていない場合、システムは自動的に動的 OG 画像を生成
  • 新しいモジュールのルートを含めるように sitemap.ts を更新することを忘れないでください
  • constructMetadata 関数を使用して正しいメタデータを確保

これで、まったく新しい CMS モジュールが完成しました!

自分でモジュールを追加するのが面倒だと感じる場合は、ドキュメントリンクを AI に送信してください。AI がすべての手順を完了するのを手伝います。

まとめ

NEXTY.DEV の CMS システムには以下の利点があります:

  1. 明確なアーキテクチャ:統一された設定センターで、拡張と保守が容易
  2. 包括的な機能:コンテンツの作成、編集からフロントエンド表示まで、すべてが揃っています
  3. 柔軟な設定:各モジュールは独立して機能特性を設定可能
  4. 優れたユーザー体験:TipTap エディタは強力で、操作がスムーズ
  5. 高い開発効率:新しいモジュールの追加には最小限のコードのみが必要で、ほとんどのコンポーネントは再利用可能
  6. 型安全性:完全な TypeScript 型定義
  7. パフォーマンス最適化:無限スクロール、ページネーション、キャッシュなどの最適化手段をサポート

個人ブログ、企業コンテンツサイト、複雑なマルチモジュールコンテンツプラットフォームのいずれであっても、NEXTY.DEV の CMS システムが簡単に対応できます!