チュートリアル 2025-11-26

NILTOで作るスタッフ紹介とFAQ:モデル設計の基本と実装例

aono iconaono

NILTOで作るスタッフ紹介とFAQ:モデル設計の基本と実装例

はじめに

スタッフ紹介ページやFAQページは、多くのWebサイトで使用されるコンテンツです。

この記事では架空の美容院のスタッフ紹介やFAQのページを例として、モデルをどう活用すれば構造化したコンテンツが作れるのかを解説します。

スタッフ紹介のモデル設計

まずは、美容院の顔とも言える「スタイリスト紹介」ページを作っていきます。

どんな情報を掲載するか整理する

まずは「何を表示したいか」を整理しましょう。
一般的な美容院のスタッフ紹介に掲載する情報として以下のような構成にします。

名前: スタイリストの名前

役職: 「トップスタイリスト」「アシスタント」など

写真: スタッフの顔写真

自己紹介: 挨拶や経歴などの文章

得意なスタイル: 「ショート」「カラー」「パーマ」などのタグ

SNS: InstagramやXなどのリンク

これらをNILTOのモデルとして定義していきます。

スタッフ紹介モデルのフィールド構成

● スタッフ名

フィールド種類:1行テキスト

フィールド名:スタッフ名

LUID:name


● 役職

フィールド種類:1行テキスト

フィールド名:役職

LUID:position


● 写真

フィールド種類:メディア

フィールド名:写真

LUID:photo



● 自己紹介

フィールド種類:フレキシブルテキスト

フィールド名:自己紹介

LUID:bio


● 得意なスタイル

フィールド種類:複数選択

フィールド名:得意技術

LUID:skills

選択項目:[ショートスタイル, ミディアム, ロング, 骨格にあったスタイル, ストリート, カジュアル, ナチュラル, ボブ, パーマ, カラー]


● SNS

フィールド種類:繰り返しフィールド(ブロック)

フィールド名:SNSリンク

LUID:socials


● サービス名

フィールド種類:単一選択

フィールド名:SNSサービス

LUID:service

選択項目:[X, Instagram, Facebook]


● URL

フィールド種類:1行テキスト

フィールド名:URL

LUID:url

上記の内容で実際に構成されたフィールドがこちらになります。

コンテンツを入力してみよう

モデルができたら、実際にコンテンツを入力してみましょう。 例えば、「オーナーの加藤さん」のデータを作ってみます。

スタッフ名: 中村 慎吾

役職: トップスタイリスト

得意なスタイル: [ショートスタイル, ナチュラル, ストリート]

自己紹介: 雑誌やヘアショーで培った経験を活かし、モードでエッジの聞いたスタイルを作り出します。受賞歴多数。唯一無二のデザインを求める方はぜひご指名ください。

SNS:

[X] https://x.com/...

データを取得・表示する

データが入ったら、Webサイト側に表示しましょう。
ここでは Next.js を例に解説します。

まず、以下のようなスタッフ紹介データを取得する関数を用意します。

// NILTOからのデータ取得を行うユーティリティ

const API_KEY = process.env.NILTO_API_KEY;
const API_ENDPOINT = 'https://cms-api.nilto.com/v1';

export async function fetchStaffList() {
  // modelパラメータで「staff」モデルのコンテンツのみを取得します
  const res = await fetch(`${API_ENDPOINT}/contents?model=staff`, {
    headers: {
      'X-NILTO-API-KEY': API_KEY, 
    },

    // Next.jsのキャッシュ設定(必要に応じて調整)
    next: { revalidate: 60 },
  });

  if (!res.ok) {
    throw new Error('スタッフデータの取得に失敗しました');
  }
  const json = await res.json();
  return json.data; // コンテンツの配列を返します
}

次に取得したデータを、以下のようなコードでカード型のUIとして表示します。

export default async function StaffPage() {
  const staffList = await fetchStaffList();

  return (
    <section className="space-y-8">
      <div className="space-y-3 text-center">
        <p className="text-sm uppercase tracking-[0.3em] text-rose-400">Our Team</p>
        <h1 className="text-4xl font-semibold">スタイリスト紹介</h1>
        <p className="text-slate-600">
          サロンで活躍するスタイリストの個性や得意分野をまとめてご紹介します。
        </p>
      </div>
      {staffList.length === 0 ? (
        <p className="rounded-2xl border border-dashed border-slate-300 bg-white/70 px-4 py-6 text-center text-slate-500">
          スタッフ情報がまだ登録されていません。
        </p>
      ) : (
        <div className="grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
          {staffList.map((staff) => (
            <StaffCard key={staff._id} staff={staff} />
          ))}
        </div>
      )}
    </section>
  );
}

上記のようなコードで作成したページがこちらになります。

無事サイトに反映できていますね。

このようにNILTOでは、様々なフィールドを組み合わせてコンテンツに最適なモデル設計をすることが基本となります。

次はさらに一歩進んで、データを別のモデルとして独立させる「コンテンツ参照」を利用したテクニックを解説します。

FAQのモデル設計

データを別のモデルとして独立させる「コンテンツ参照」を利用したテクニックとして、美容院の「FAQ」ページを作っていきます。

どんな情報を掲載するか整理する

美容院のFAQには、以下のようなカテゴリとFAQの内容が必要になります。

FAQカテゴリ:料金についてなど

質問文

回答文

関連FAQ

これらをすべて1つのモデルで管理するのではなく、カテゴリ管理をしやすくするために、今回は「カテゴリ」と「質問項目」を別のモデルに分けて、関連付ける(参照する)方法をとります。

FAQカテゴリモデルのフィールド構成

● カテゴリ名

フィールド種類:1行テキスト

フィールド名:カテゴリ名

LUID:name

● スラッグ

フィールド種類:1行テキスト

フィールド名:スラッグ

LUID:slug

上記の内容で実際に構成されたフィールドがこちらになります。

FAQモデルのフィールド構成

● 質問文

フィールド種類:1行テキスト

フィールド名:質問文

LUID:question

● 回答文

フィールド種類:複数行テキスト

フィールド名:回答文

LUID:answer

● FAQカテゴリ

フィールド種類:コンテンツ参照

フィールド名:FAQカテゴリ

LUID:category

● 関連FAQリスト

フィールド種類:繰り返し

フィールド名:関連FAQリスト

LUID:related_faq_list

● 関連FAQ

フィールド種類:コンテンツ参照

フィールド名:関連FAQ

LUID:related_faq

上記の内容で実際に構成されたフィールドがこちらになります。

コンテンツを入力してみよう

モデルができたら、実際にコンテンツを入力してみましょう。

質問文:駐車場はありますか?

回答文:申し訳ございませんが、当店専用の駐車場はご用意がございません。お車でお越しの際は、近隣のコインパーキングをご利用くださいますようお願いいたします。

FAQカテゴリ:お店について

関連FAQ: Wi-Fiは利用できますか?

データを取得・表示する

データが入ったら、Webサイト側に表示しましょう。

まず、以下のようなNILTOのAPIからデータを取得する関数を用意します。

// カテゴリ一覧を取得
export async function fetchFaqCategories() {
  const res = await fetch(`${API_ENDPOINT}/contents?model=faq_category`, {
     headers: { 'X-NILTO-API-KEY': API_KEY } 
  });
  const json = await res.json();
  return json.data;
}

// すべてのFAQを取得
export async function fetchFaqItems() {
  // depth=1 を指定することで、参照しているカテゴリの詳細情報(名前など)も同時に取得できます
  const res = await fetch(`${API_ENDPOINT}/contents?model=faq_item&depth=1`, {
     headers: { 'X-NILTO-API-KEY': API_KEY } 
  });
  const json = await res.json();
  return json.data;
}

次に取得したデータを、以下のようなコードでFAQカテゴリ一覧ページとFAQ詳細ページとして表示します。

//FAQカテゴリ一覧

  {categories.length === 0 ? (
    <p className="rounded-2xl border border-dashed border-slate-300 bg-white/70 px-4 py-6 text-center text-slate-500">
      FAQカテゴリがまだ登録されていません。
    </p>
  ) : (
    <div className="grid gap-4 sm:grid-cols-2">
      {categories.map((category) => (
        <Link
          key={category._id}
          href={`/faq/${category.slug}`}
          className="rounded-3xl border border-slate-200 bg-white px-6 py-5 text-left shadow-sm transition hover:-translate-y-0.5 hover:border-indigo-200"
        >
          <p className="text-sm font-medium uppercase tracking-[0.2em] text-slate-400">
            Category
          </p>
          <p className="text-2xl font-semibold text-slate-900">{category.name}</p>
          <p className="mt-2 text-sm text-slate-500">詳しく見る →</p>
        </Link>
      ))}
    </div>
  )}

上記のようなコードで作成したFAQカテゴリページがこちらになります。

//FAQ詳細ページ

<article className="space-y-6 rounded-3xl border border-slate-200 bg-white px-6 py-8 shadow-sm">
  <header className="space-y-2">
    <p className="text-sm uppercase tracking-[0.3em] text-indigo-400">Question</p>
    <h2 className="text-3xl font-semibold text-slate-900">{faqItem.question}</h2>
  </header>
  <div className="whitespace-pre-line text-lg leading-relaxed text-slate-700">
    {faqItem.answer}
  </div>

  {faqItem.related_faqs && faqItem.related_faqs.length > 0 && (
    <div className="border-t border-dashed border-slate-200 pt-6">
      <p className="mb-3 font-semibold text-slate-900">関連する質問</p>
      <ul className="flex flex-wrap gap-2 text-sm">
        {faqItem.related_faqs.map((related) => {
          const relatedSlug = related.slug ?? fallbackSlug;
          const href = relatedSlug
            ? `/faq/${relatedSlug}/${related._id}`
            : `#faq-${related._id}`;
          return (
            <li key={related._id}>
              <Link
                href={href}
                className="rounded-full bg-slate-100 px-3 py-1 text-slate-600 hover:bg-slate-200"
              >
                {related.question ?? `FAQ #${related._id}`}
              </Link>
            </li>
          );
        })}
      </ul>
    </div>
  )}
</article>

上記のようなコードで作成したFAQ詳細ページがこちらになります。

無事サイトに反映されました。

このようにFAQカテゴリを別モデルとして管理し、コンテンツ参照でカテゴリを指定する応用的にモデル設計をすることができます。

まとめ

今回は「美容院サイト」を題材に以下のようなスタッフ紹介とFAQのモデル設計を解説しました。

スタッフ紹介
1つのモデル(スタッフ紹介)に情報を集約。
「繰り返し」を使って、複数のSNSリンクを管理。

FAQ
2つのモデル(FAQカテゴリ / FAQ)に分割。
「コンテンツ参照」を活用することで、別モデルで管理しているFAQカテゴリを指定。
「繰り返し」と「コンテンツ参照」を活用して、関連するFAQの指定。

NILTOでの開発において、最も重要なのは「運用を見据えたモデル設計」です。
「どんなデータが必要か?」「将来どのように増えていくか?」を想像しながらフィールドを組み合わせることで、開発者には「扱いやすいAPI」を、運用者には「迷わない管理画面」を提供することができます。

この基本さえ押さえれば、複雑なモデル設計にも対応できます。ぜひ今回のチュートリアルをベースに、皆さんのプロジェクトに合わせた最適なモデル設計に挑戦してみてください。