Tailwind Logo

XM Cloud で管理している記事の RSS フィードを作成する(後編)

XM CloudNext.js

公開日: 2024-07-05

前回の記事までで、XM Cloud に対象となるアイテムのテンプレートを作成、サンプルのアイテムを GraphQL を利用して Json のデータで取得することができました。今回は後半で、RSS でデータが見れるようにしていきます。

Next.js 経由で GraphQL を呼び出す

まず GraphQL の Endpoint を利用してデータを取得するための汎用関数を用意します。このコードは少し長いため、以下の URL を参照してください。

ここでは fetchGraphQL が用意されており、動作は以下の通りです。

  • fetchGraphQL(query: string) としており、query には GraphQL の文字列を指定します
  • 以下の2つのキーを用意しています
    • apiKey : evn.local に SITECORE_API_KEY の環境変数を指定する必要があります
    • endpointUrl : evn.local に GraphQL のエンドポイントとして GRAPH_QL_ENDPOINT を指定します
  • 上記2つのキーを利用して fetch で GraphQL エンドポイントに対して問い合わせ、その結果を返します

今回は .env.local に以下の定義を追加します。

Plain Text
GRAPH_QL_ENDPOINT=https://xmcloudcm.localhost/sitecore/api/graph/edge
SITECORE_API_KEY=1830F204-87EB-4132-9A7A-C9E4CD7C9A19

これで fetchGraphQL を利用することが可能となりました。

rss.xml の作成

今回は Next.js の App Router を利用するため、ルートのパスで rss.xml を返すことを想定して、以下のパスに route.ts ファイルを作成します。

  • src\tailwindcss\src\app\rss.xml\route.ts

インターフェイスの作成

前回、最後に取得したデータをもとに返ってくる Json のデータを扱うための Interface をまず作成します。今回は以下のように作りました。

TypeScript
interface News {
  id: string;
  url: {
    hostName: string;
    path: string;
  };
  title: {
    value: string;
  };
  publishDate: {
    value: string;
  };
  description: {
    value: string;
  };
  image: {
    jsonValue: {
      value: {
        src: string;
        alt: string;
        width: string;
        height: string;
      };
    };
  };
}

interface AllNewsResponse {
  data: {
    search: {
      total: number;
      results: Partial<News>[];
    };
  };
}

Query の作成

前回動作した GraphQL のクエリに関して、パラメーターを渡して呼び出すために、以下のように作成をします。

TypeScript
const AllNewsQuery = (language: string, siteRootId: string, templateId: string) => {
  return `
      query {
        search(
          where: {
            name: "_templates"
            value: "${templateId}"
            operator: EQ
            AND: [
              {
                name: "_path"
                value: "${siteRootId}"
                operator: CONTAINS
              }
              { name: "_language", value: "${language}", operator: EQ }
            ]
          }
          orderBy: { direction: DESC, name: "publishDate" }
        ) {
          total
          results {
            id
            url {
              hostName
              path
            }
            title: field(name: "Title") {
              value
            }
            publishDate: field(name: "PublishDate") {
              value
            }
            description: field(name: "Description") {
              value
            }
            image: field(name: "Image") {
              jsonValue
            }
          }
        }
      }
    `;
};

これを呼び出す際には、言語、サイトのルートの ID および対象となるテンプレートの ID の値を利用して、必要となるデータを取得するためのクエリが生成されます。

データを取得する

これまで用意したコードを利用して、以下のようにデータを取得することができる関数を用意します。fetchGraphQL を呼び出しているため、import { fetchGraphQL } from 'src/utils'; のコードも追加してください。

TypeScript
async function getAllArticle(language: string, siteRootId: string, templateId: string) {
  const results: AllNewsResponse = (await fetchGraphQL(
    AllNewsQuery(language, siteRootId, templateId)
  )) as AllNewsResponse;

  const news: Partial<News>[] = [];

  results.data.search.results.forEach((post: Partial<News>) => {
    news.push({
      id: post.id,
      url: post.url,
      title: post.title,
      description: post.description,
      publishDate: post.publishDate,
      image: post.image,
    });
  });

  return news;
}

GET 関数の作成

最後に、リクエストされた際にデータを返す GET 関数を作成します。今回は Json のデータを取得できているのかを確認するために、以下のようなコードをまず用意しました。

TypeScript
export async function GET() {
  const posts = await getAllArticle(
    'en',
    'E66EE43B-398B-486E-9F7F-5FE36A4093D3',
    'B9453B23-0E09-4D98-99C0-EAA0F16DD6DA'
  );

  return new Response(JSON.stringify({ posts }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

この結果を確認するために、npm run start:connected のコマンドを実行したあと、http://localhost:3000/rss.xml にアクセスをするとデータが取れていることを確認できました。

xmcrss03.png

データを加工する

取得したデータを利用して RSS 形式のデータで結果を表示するように route.ts ファイルを変更していきます。今回はパッケージとして、以下のパッケージを利用します。

以下のコマンドを実行してパッケージを追加します。

PowerShell
npm install feed

まず RSS が利用する URL を以下のように定義します。

TypeScript
const baseUrl = "https://sitecoredemo.jp";

続いて GET の中で、feed で必要となる情報を設定していきます。

TypeScript
  const feed = new Feed({
    title: 'Sitecoredemo.jp RSS',
    description: 'This is RSS Feed about demo news',
    id: baseUrl,
    link: baseUrl,
    copyright: `${new Date().getFullYear()} Sitecoredemo.jp`,
    language: 'en',
    favicon: baseUrl + 'favicon.png',
    feedLinks: {
      rss2: baseUrl + 'rss.xml',
    },
    author: {
      name: 'Shinichi Haramizu',
      email: 'support@sitecoredemo.jp',
      link: baseUrl,
    },
  });

続いて、作成した feed に対して、取得したデータを追加していきます。

TypeScript
  posts.map((post) => {
    feed.addItem({
      title: post.title?.value || 'Title',
      id: post.id,
      link: baseUrl + post.url?.path,
      date: new Date(post.publishDate?.value || '2024-01-10'),
      description: post.description?.value || '',
      image: {
        url: post.image?.jsonValue.value.src || '/next.svg',
      },
    });
  });

最後に、生成された feed のデータをレスポンスとして返します。

TypeScript
  return new Response(feed.rss2(), {
    headers: {
      'Content-Type': 'application/atom+xml; charset=utf-8',
    },
  });

実際に Postman を利用して http://localhost:3000/rss.xml にアクセスをすると、以下のようにデータの取得ができています。

xmcrss04.png

日付の修正

Sitecore で管理している日付のデータは、以下のような文字列になっています。

  • 20240730T150000Z

この結果、日付に関しては Invalid Date という結果が表示されています。これを修正するために、日付のデータに関して正規表現で扱えるように変換をします。

TypeScript
    const publishDate = post.publishDate?.value || '20240110T00:00:00Z';

    const isoDateString = publishDate.replace(
      /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/,
      '$1-$2-$3T$4:$5:$6Z'
    );

データを渡す部分を以下のように書き換えます。

TypeScript
      date: new Date(isoDateString),

これで正しく日付も表示ができるようになりました。

Endpoint および API キーが無効の場合

これまでデータを利用して実行して、という形で問題なく動いていましたが、 GRAPH_QL_ENDPOINT および SITECORE_API_HOST が空の場合(初回 Build の状況)では Build エラーが発生してしまいます。これを回避するために、try - catch を追加して、Build の際にエラーが出ないように、処理が正しくできなかった場合はリダイレクト処理をするように、サンプルのコードを更新しました。

最終的なサンプルコードは以下の通りです。

まとめ

今回は RSS を実装するために、前編ではデータの取得、後編ではそのデータを利用して RSS のデータを加工する、という形で仕上げました。URL に関しては Next.js App Router を利用して作成しているため、今回はルートに rss.xml となっていますが、パスを切ったり、異なる RSS の場合はファイル名を変更することが可能です。ある程度汎用性を持たせるために、言語、テンプレートの ID そしてサイトのルート ID をパラメータとして呼び出すようにしてあります。

関連タグ