ヘッドレスなOracle Content ManagementでNext.jsでブログを作成

イントロダクション

Next.jsは、サーバー側のレンダリングやReactベースのWebアプリケーション用の静的Webサイトの生成などの機能を可能にする、オープンソースのReactフロントエンド開発Webフレームワークです。しかし、すべてのコンテンツに対応するためにコンテンツ管理システム(CMS)が必要な場合にはどうなりますか?幸いにも、豊富なヘッドレスCMS機能を備えたOracle Content Managementは、コンテンツ管理およびガバナンスのニーズに最適なソリューションです。

このチュートリアルでは、Oracle Content ManagementをヘッドレスCMSとして活用し、JavaScriptのコンテンツ配信用のソフトウェア開発キット(SDK)を利用して、Next.jsで単純なブログを作成します。このNext.jsサンプルは、GitHubで入手できます。

チュートリアルは、次の3つのタスクで構成されています。

  1. Oracle Content Managementの準備
  2. Next.jsでのブログの作成
  3. デプロイのためのアプリケーションの準備

前提条件

このチュートリアルを続行する前に、次の情報を先にお読みください。

このチュートリアルに従うには、次のものが必要です。

構築しているもの

このブログは、訪問者がトピックに整理されたブログ記事を閲覧できる3ページのサイトで構成されています。最初のページであるホームページは、ブランド(会社名とロゴ)、一部のリンク、およびブログ・トピックのリストで構成されます。

オラクルの構築している内容を確認するには、チュートリアル(Oracle Content Managementからコンテンツを消費する基本的なNext.jsブログ)を終了してください。

https://headless.mycontentdemo.com/samples/oce-nextjs-blog-sample

このチュートリアルの最後には、ホーム・ページが表示されます。

この図は、使用可能なトピックのリストがあるCafe Supremoデモ・サイトのホーム・ページを示しています。

2ページ目のトピック・ページには、トピックに属する各ブログ記事のプレビューが表示されます。個々のトピック・ページの外観は次のとおりです。

この図は、トピックに使用可能な記事のリストを含む「受信者」というトピック・ページを示しています。

最後に、記事ページは、ブログの作成者に関する情報を含む最終的なブログ記事をレンダリングします。個々の記事ページの外観は次のとおりです。

この図は、コンテンツと作成者参照を含む個々の記事ページを示しています。

続行するには、Oracle Content Managementのアクティブなサブスクリプションを持ち、コンテンツ管理者ロールでログインする必要があります。

タスク1: Oracle Content Managementの準備

Oracle Content Managementインスタンスがまだない場合は、クイック・スタートを参照して、Oracle Cloudへの登録、Oracle Content Managementインスタンスのプロビジョニング、ヘッドレスCMSとしてのOracle Content Managementの構成方法を学んでください。

このチュートリアルでは、2つの方法のいずれかでコンテンツ・モデルを作成する必要があります。ダウンロード可能なアセット・パックが使用可能で、空のリポジトリにコンテンツ・タイプと関連付けられたコンテンツを入力するか、独自のコンテンツ・モデルおよびコンテンツを作成できます。

Oracle Content Managementを準備するには:

  1. チャネルおよびアセット・リポジトリを作成します
  2. コンテンツ・モデルを作成するには、次の2つの方法のいずれかを使用します。

チャネルおよびアセット・リポジトリの作成

まず、コンテンツを公開できるように、Oracle Content Managementでチャネルおよびアセット・リポジトリを作成する必要があります。

Oracle Content Managementでチャネルおよびアセット・リポジトリを作成するには:

  1. 管理者としてOracle Content Management Webインタフェースにログインします。

  2. 左側のナビゲーション・メニューで「コンテンツ」を選択し、ページ・ヘッダーの選択リストから「チャネルの公開」を選択します。

    この図は、「コンテンツ」ページ・ヘッダーのドロップダウン・メニューで選択された「チャネルの公開」オプションを示しています。

  3. 右上隅にある「Create」をクリックして新しいチャネルを作成します。このチュートリアルの目的でチャネル'OCEGettingStartedChannel'に名前を付け、アクセスを公開したままにします。「保存」をクリックしてチャネルを作成します。

    この図は、チャネル名フィールドにOCEGettingStartedChannelがある公開チャネル定義パネルを示しています。

  4. 左側のナビゲーション・メニューで「コンテンツ」を選択し、ページ・ヘッダーの選択リストから「リポジトリ」を選択します。

    この図は、「コンテンツ」ページ・ヘッダーのドロップダウン・メニューで選択された「リポジトリ」オプションを示しています。

  5. 右上隅にある「Create」をクリックして、新しいアセット・リポジトリを作成します。このチュートリアルの目的で、アセット・リポジトリにOCEGettingStartedRepositoryという名前を付けます。

    この図は、リポジトリ名フィールドにOCEGettingStartedRepositoryがあるリポジトリ定義パネルを示しています。

  6. 「チャネルの公開」フィールドで、OCEGettingStartedChannelチャネルを選択して、OCEGettingStartedRepositoryリポジトリのコンテンツをOCEGettingStartedChannelチャネルに公開できることをOracle Content Managementに示します。終了したら、「Save」をクリックします。

    この図は、「公開チャネル」フィールドにOCEGettingStartedChannelがあるリポジトリ定義パネルを示しています。

コンテンツ・モデルの作成

次のタスクは、コンテンツ・モデルを作成することです。次のいずれかのメソッドを使用できます。

Oracle Content Managementサンプル資産パックのインポート

このチュートリアルに必要なすべてのコンテンツ・タイプおよびアセットを含む、事前構成されたOracle Content Managementサンプル・アセット・パックをダウンロードできます。必要に応じて、サンプル資産パックをダウンロードするのではなく、独自のコンテンツ・モデルを作成することもできます。

このチュートリアルで使用しているコンテンツのコピーは、Oracle Content Management Sample Asset Packからアップロードできます。これにより、コンテンツ・タイプを試行し、コンテンツを変更できます。Oracle Content Managementサンプル資産パックをインポートする場合は、アセット・パック・アーカイブOCESamplesAssetPack.zipをダウンロードし、選択したディレクトリに抽出できます。

  1. Oracle Content Managementのダウンロード・ページから、Oracle Content Managementサンプル資産パック(OCESamplesAssetPack.zip)をダウンロードします。ダウンロードしたzipファイルをコンピュータ上の場所に抽出します。抽出後、この場所にはOCEGettingStarted_data.zipというファイルが含まれます。

  2. 管理者としてOracle Content Management Webインタフェースにログインします。

  3. 左側のナビゲーション・メニューで「コンテンツ」を選択し、ページ・ヘッダーの選択リストから「リポジトリ」を選択します。次に、OCEGettingStartedRepositoryを選択し、上部のアクション・バーの「コンテンツのインポート」ボタンをクリックします。

    この図は、OCEGettingStartedRepository項目が選択された「リポジトリ」ページを示しています。

  4. OCEGettingStarted_data.zipをローカル・コンピュータから「Documents」フォルダにアップロードします。

    この図は、OCEGettingStarted_data.zipファイルのアップロード確認画面を示しています。

  5. アップロード後、「OCEGettingStarted_data.zip」を選択し、「OK」をクリックしてコンテンツをアセット・リポジトリにインポートします。

    この図は、「OK」ボタンが有効になっている選択したOCEGettingStarted_data.zipファイルを示しています。

  6. コンテンツが正常にインポートされたら、「アセット」ページに移動し、OCEGettingStartedRepositoryリポジトリを開きます。すべての関連イメージおよびコンテンツ・アイテムがアセット・リポジトリに追加されていることがわかります。

    この図は、インポートされたすべてのアセットを含むOCEGettingStartedRepositoryリポジトリを示しています。

  7. 左上の「すべて選択」「公開」の順にクリックして、インポートされたすべてのアセットを、前に作成した公開チャネル(OCEGettingStartedChannel)に追加します。

    この図は、すべてのアセットが選択され、アクション・バーの「公開」オプションが表示されているOCEGettingStartedRepositoryリポジトリを示しています。

  8. 公開する前に、すべてのアセットを検証する必要があります。まず、選択したチャネルとしてOCEGettingStartedChannelを追加し、「検証」ボタンをクリックします。

    この図は、「チャネル」フィールドにOCEGettingStartedChannelチャネルが追加され、検証されるすべてのアセットおよび「検証」ボタンが有効化された「検証結果」ページを示しています。

  9. アセットが検証されたら、右上隅にある「公開」ボタンをクリックして、選択したチャネルにすべてのアセットを公開できます。

    この図は、「チャネル」フィールドにOCEGettingStartedChannelチャネルが追加され、すべてのアセットが検証され、「公開」ボタンが有効になっている「検証結果」ページを示しています。

これが完了すると、「アセット」ページで、すべてのアセットが公開されていることを確認できます。(アセット名の上のアイコンで確認できます。)

この図は、すべてのアセットがpubishedになっている「アセット」ページを示しています。

Oracle Content Management Sample Asset Packのインポート後、Next.jsでのブログの構築を開始できます。

独自のコンテンツ・モデルの作成

Oracle Content Management Sample Asset Packのインポートではなく、独自のコンテンツ・モデルを作成することもできます。

このチュートリアルでは、ブログ用のホームページを作成するために、「OCEGettingStartedHomePage」というコンテンツ・タイプを使用しています。このホームページは、ブランディング(会社名とロゴ)、リンクのURL、およびページに含めるべきブログ・トピックのリストで構成されます。

この図は、Cafe Supremoデモ・サイトのホーム・ページを示しています。

コンテンツ・モデルのコンテンツ・タイプを作成するには:

  1. 管理者としてOracle Content Management Webインタフェースにログインします。
  2. 左側のナビゲーション・メニューで「コンテンツ」を選択し、ページ・ヘッダーの選択リストから「アセット・タイプ」を選択します。
  3. 右上隅の「作成」をクリックします。
  4. コンテンツ・タイプ(デジタル・アセット・タイプではない)の作成を選択します。すべての必須コンテンツ・タイプについて、これを繰り返します。

この図は、Oracle Content Management Webインタフェースの「アセット・タイプの作成」ダイアログを示しています。

4つのコンテンツ・タイプを作成し、それぞれに独自のフィールド・セットを作成します。

最初のコンテンツ・タイプOCEGettingStartedHomePageには、次のフィールドが必要です。

表示名 フィールド・タイプ 必須 マシン名
会社名 単一値テキスト・フィールド X company_name
会社ロゴ 単一値テキスト・フィールド X company_logo
トピック 複数値参照フィールド X トピック
連絡先URL 単一値テキスト・フィールド X contact_url
URLについて 単一値テキスト・フィールド X about_url

OCEGettingStartedHomePageコンテンツ・タイプ定義は次のようになります。

この図は、コンテンツ・タイプ'OCEGettingStartedHomePage'の定義を示しています。データ フィールドには、[会社名]、[会社ロゴ]、[トピック]、[担当者URL]および[URLについて]があります。

2つ目のコンテンツ・タイプOCEGettingStartedTopicには、次のフィールドが必要です。

表示名 フィールド・タイプ 必須 マシン名
サムネイル シングルバリュー・イメージ・フィールド X サムネイル

OCEGettingStartedTopicコンテンツ・タイプは次のようになります。

この図は、コンテンツ・タイプ'OCEGettingStartedTopic'の定義を示しています。このデータ・フィールド「サムネイル」が含まれています。

3つ目のコンテンツ・タイプOCEGettingStartedAuthorには、次のフィールドが必要です。

表示名 フィールド・タイプ 必須 マシン名
アバター シングルバリュー・イメージ・フィールド X アバター

OCEGettingStartedAuthorコンテンツ・タイプは次のようになります。

この図は、コンテンツ・タイプ'OCEGettingStartedAuthor'の定義を示しています。このデータ・フィールド: Avatarが含まれます。

4番目および最後のコンテンツ・タイプOCEGettingStartedArticleには、次のフィールドが必要です。

表示名 フィールド・タイプ 必須 マシン名
公開日 単一起算日フィールド X published_name
作成者 単一値参照フィールド X 文書作成
イメージ シングルバリュー・イメージ・フィールド X イメージ
イメージ・キャプション 単一値テキスト・フィールド X image_caption
記事コンテンツ 単一値の大型テキスト・フィールド X article_content
トピック 単一値参照フィールド X topic

OCEGettingStartedArticleコンテンツ・タイプは次のようになります。

この図は、コンテンツ・タイプ'OCEGettingStartedArticlePage'の定義を示しています。データ・フィールドには、「公開日」、「作成者」、「イメージ」、「イメージ・キャプション」、「記事コンテンツ」および「トピック」が含まれます。

コンテンツ・タイプを作成したら、次のコンテンツ・タイプを前に作成したリポジトリOCEGettingStartedRepositoryに追加できます。

  1. 管理者としてOracle Content Management Webインタフェースにログインします。
  2. OCEGettingStartedRepositoryに移動します。
  3. リポジトリを編集し、「アセット・タイプ」で、新しく作成された4つのコンテンツ・タイプをすべて指定します。「保存」ボタンをクリックして変更を保存します。

この図は、OCEGettingStartedRepositoryリポジトリに関連付けられた、新しく作成された4つのコンテンツ・タイプを持つOracle Content Managementの「リポジトリの編集」ページを示しています。

コンテンツ・タイプをリポジトリに追加した後、「アセット」ページでOCEGettingStartedRepositoryリポジトリを開き、すべてのコンテンツ・タイプのコンテンツ項目の作成を開始できます。

この図は、Oracle Content Management Webインタフェースの「アセット」ページのコンテンツ項目を示しています。左側にはコレクション、チャネル、言語、タイプ、コンテンツ項目選択およびステータスのオプションがあります。

タスク2: Next.jsでのブログの作成

サーバー側のレンダリングされたNext.jsアプリケーションでOracle Content Managementコンテンツを利用するには、GitHubのオープンソース・リポジトリとして利用できるNext.jsブログ・サンプルを使用します。

ノート: Next.jsサンプルの使用はオプションであり、このチュートリアルで使用してすぐに開始できるようにしてください。独自のNext.jsアプリケーションを構築することもできます。

Next.jsでブログを作成するには:

  1. サンプル・リポジトリのクローニングと依存関係のインストール
  2. Next.jsアプリケーションの構成
  3. Oracle Content Management Content SDKの使用
  4. コンテンツSDKを使用したコンテンツのフェッチ

サンプル・リポジトリのクローニングとインストールの依存性

Next.jsブログのサンプルは、GitHubのオープンソース・リポジトリとして使用できます。

まず、サンプルをGitHubからローカル・コンピュータにクローニングし、ディレクトリをリポジトリ・ルートに変更する必要があります。

git clone https://github.com/oracle/oce-nextjs-blog-sample.git
    cd oce-nextjs-blog-sample

コード・ベースがあるので、アプリケーションの依存関係をダウンロードする必要があります。rootディレクトリから次のコマンドを実行します。

npm install

Next.jsアプリケーションの構成

このNext.jsのブログ・サンプルでは、Oracle Content Management Content SDK (およびその他のリクエスト)が適切なチャネル・トークンで正しいインスタンスURLおよびAPIバージョンをターゲット設定できるように、いくつかの情報を構成する必要があります。これらの値は、Script/server-config-utils.jsで新規配信クライアントをインスタンス化するために使用されます。

このアプリケーションは、Next.jsによって読み取られ、process.envを使用してアプリケーション内のコードで使用できるようにする.env.localファイルを使用します。

テキスト・エディタで.env.localファイルを開きます。次の情報が表示されます。

# The connection details for the Oracle Content Management server to be used for this application
    SERVER_URL=https://samples.mycontentdemo.com
    API_VERSION=v1.1
    CHANNEL_TOKEN=47c9fb78774d4485bc7090bf7b955632

各キーと値のペアを変更して、インスタンスURL、ターゲットとするAPIバージョン、および公開チャネルに関連付けられたチャネル・トークンを反映します。このチュートリアルのチャネルはOCEGettingStartedChannelです。

Oracle Content Management Content SDKの使用

Oracle Content Managementには、アプリケーションのコンテンツの検出および使用に役立つSDKが用意されています。SDKはNPMモジュールとして公開され、プロジェクトはGitHubでホストされます。

SDKの詳細は、ここを参照してください。

SDKは、package.jsonファイルのこのプロジェクトの実行時依存性として登録されました。

コンテンツSDKを使用したコンテンツのフェッチ

Content SDKを活用してコンテンツをフェッチし、Next.jsアプリケーションでレンダリングできるようになりました。

scriptフォルダには、Content SDKを使用してOracle Content Managementからデータを取得するためのコードが含まれます。

script/server-config-utils.jsファイルは、Content SDKをインポートし、.env.localで指定されている構成を使用して配信クライアントを作成します。

次のコマンドは、SDKをインポートします。

import { createDeliveryClient, createPreviewClient } from '@oracle/content-management-sdk';

次のコマンドは、配信クライアントを作成します。

return createDeliveryClient(serverconfig);

script/services.jsファイルには、アプリケーションのデータを取得するためのすべてのコードが含まれます。アプリケーションのページ・コンポーネントごとに、そのページのすべてのデータを取得するためのメイン機能が1つあります。

イメージをレンダリングするために、service.jsには、アセットのレンディションから構築されたアセットのソース・セットを取得するヘルパー・メソッドが用意されています。

function getSourceSet(asset) {
      const urls = {};
      urls.srcset = '';
      urls.jpgSrcset = '';
      if (asset.fields && asset.fields.renditions) {
        asset.fields.renditions.forEach((rendition) => {
          addRendition(urls, rendition, 'jpg');
          addRendition(urls, rendition, 'webp');
        });
      }
      // add the native rendition to the srcset as well
      urls.srcset += `${asset.fields.native.links[0].href} ${asset.fields.metadata.width}w`;
      urls.native = asset.fields.native.links[0].href;
      urls.width = asset.fields.metadata.width;
      urls.height = asset.fields.metadata.height;
      return urls;
    }

ホームページデータ

ホーム・ページには、すべてのデータを取得するために複数のデータ・コールが必要です。

  1. まず、.env.localで指定されているチャネル内の項目を検索します。
  2. 各トピック項目について、詳細をフェッチします。

script/services.jsを開き、ホーム・ページのすべてのデータを取得するgetTopicsListPageData()ファンクションを検索します。

export function getTopicsListPageData() {
      const client = getDeliveryClient();
      return fetchHomePage(client)
        .then((data) => (
          getRenditionURLs(client, data.logoID)
            .then((renditionUrls) => {
              data.companyThumbnailRenditionUrls = renditionUrls;
              return data;
            })
        ));
    }

getTopicsListPageData()関数によってコールされるfetchHomePage()関数は、チャネル内のすべての項目を取得します。これにより、ロゴID、会社名、関連URLおよび連絡先URL、およびトピックのリストが表示されます。

function fetchHomePage(client) {
      return client.queryItems({
        q: '(type eq "OCEGettingStartedHomePage" AND name eq "HomePage")',
      }).then((data) => {
        const logoID = data.items[0].fields.company_logo.id;
        const title = data.items[0].fields.company_name;
        const aboutUrl = data.items[0].fields.about_url;
        const contactUrl = data.items[0].fields.contact_url;
    
        const { topics } = data.items[0].fields;
        const promises = [];
    
        topics.forEach((origTopic) => {
          // add a promise to the total list of promises to get the full topic details
          promises.push(
            fetchTopic(client, origTopic.id)
              .then((topic) => topic),
          );
        });
    
        // execute all the promises returning a single dimension array of all
        // of the topics and the other home page data
        return Promise.all(promises)
          .then((allTopics) => (
            {
              logoID,
              companyTitle: title,
              aboutUrl,
              contactUrl,
              topics: flattenArray(allTopics),
            }
          )).catch((error) => logError('Fetching topics failed', error));
      }).catch((error) => logError('Fetching home page data failed', error));
    }

次に、各トピックIDに対してfetchTopic()ファンクションが呼び出され、トピックの詳細がすべて取得されます。

function fetchTopic(client, topicId) {
      return client.getItem({
        id: topicId,
        expand: 'fields.thumbnail',
      }).then((topic) => {
        topic.renditionUrls = getSourceSet(topic.fields.thumbnail);
        return topic;
      }).catch((error) => logError('Fetching topic failed', error));
    }

getTopicsListPageData()は、レンダリングするイメージのURLを取得するためにgetRenditionURLs()もコールします。

function getRenditionURLs(client, identifier) {
      return client.getItem({
        id: identifier,
        expand: 'fields.renditions',
      }).then((asset) => getSourceSet(asset))
        .catch((error) => logError('Fetching Rendition URLs failed', error));
    }

トピック・ページ・データ

Next.jsでは、静的なサイト生成を使用して、アプリケーションの各ページを事前にレンダリングします。各トピック・ページのパスには、トピックIDが含まれます。script/services.jsを開き、すべてのトピックIDの取得に使用されるfetchTopicIds()ファンクションを検索します。

export function fetchTopicIds() {
      const client = getDeliveryClient();
    
      return client.queryItems({
        q: '(type eq "OCEGettingStartedHomePage" AND name eq "HomePage")',
      }).then((data) => {
        const { topics } = data.items[0].fields;
        const topicIds = topics.map(
          (topic) => topic.id,
        );
        return topicIds;
      }).catch((error) => logError('Fetching topic ids failed', error));
    }

トピック・ページでは、トピックIDを受信し、すべてのデータを取得するために複数のデータ・コールが必要です。

  1. トピックの名前を取得します
  2. 指定されたトピックのすべての記事を取得します。
  3. 各記事について、その翻訳URLを取得します。

トピックの名前を取得するために使用されるfetchTopicName(topicId)ファンクションを検索します。

export function fetchTopicName(topicId) {
      const client = getDeliveryClient();
      return client.getItem({
        id: topicId,
      }).then((topic) => topic.name)
        .catch((error) => logError('Fetcvhing topic name failed', error));
    }

トピック・ページの記事データを取得するfetchTopicArticles(topicId)ファンクションを検索します。

export function fetchTopicArticles(topicId) {
      const client = getDeliveryClient();
      return client.queryItems({
        q: `(type eq "OCEGettingStartedArticle" AND fields.topic eq "${topicId}")`,
        orderBy: 'fields.published_date:desc',
      }).then((data) => {
        const promises = [];
        const articles = data.items;
    
        articles.forEach((article) => {
          // add a promise to the total list of promises to get the article url
          promises.push(
            getRenditionURLs(client, article.fields.image.id)
              .then((renditionUrls) => {
                article.renditionUrls = renditionUrls;
                // Note: the spread operator is used here so that we return a top level
                // object, rather than a value which contains the object
                // i.e we return
                //   {
                //     field1: 'value', field2 : "value", etc
                //   },
                // rather than
                //   {
                //     name: {
                //             field1: 'value', field2 : "value", etc
                //           }
                //    }
                return {
                  ...article,
                };
              }),
          );
        });
    
        // execute all the promises and return all the data
        return Promise.all(promises)
          .then((allArticles) => ({
            topicId,
            articles: flattenArray(allArticles),
          }));
      }).catch((error) => logError('Fetching topic articles failed', error));
    }

fetchTopicArticles()メソッドは、前述のgetRenditionURLs()を使用して記事のイメージを取得します。

記事ページ・データ

各記事ページのパスには、記事IDが含まれています。script/services.jsを開き、各記事ページを事前にレンダリングするときに使用されるすべての記事IDを取得するために使用するfetchAllArticlesSimple()ファンクションを見つけます。

export function fetchAllArticlesSimple() {
      const client = getDeliveryClient();
      return fetchTopicIds()
        .then((topicIds) => {
          const promises = [];
          topicIds.forEach((topicId) => {
            // add a promise to the total list of promises to get the article url
            promises.push(
              fetchTopicArticlesSimple(client, topicId)
                .then((data) => {
                  const { items } = data;
                  const articles = items.map((item) => (
                    {
                      id: item.id,
                      name: item.name,
                    }
                  ));
                  return articles;
                }),
            );
          });
    
          // execute all the promises and return all the data
          return Promise.all(promises)
            .then((allArticles) => flattenArray(allArticles));
        }).catch((error) => logError('Fetching basic information for all articles failed', error));
    }

fetchAllArticlesSimple()は、fetchTopicArticlesSimple()を呼び出して記事の簡易リストを取得します。

function fetchTopicArticlesSimple(client, topicId) {
      return client.queryItems({
        q: `(type eq "OCEGettingStartedArticle" AND fields.topic eq "${topicId}")`,
        orderBy: 'fields.published_date:desc',
      }).then((data) => data)
        .catch((error) => logError('Fetching basic information for topic articles failed', error));
    }

記事ページでは、記事IDを受信し、すべてのデータを取得するためにいくつかのデータ・コールが必要です。

  1. 指定された記事の記事の詳細を取得します。
  2. 各記事について、その翻訳URLを取得します。

記事ページのデータを取得するfetchArticleDetails(articleId)ファンクションを検索します。このメソッドは、getRenditionURL()関数を使用します。この関数は、記事のイメージを取得し、getMediumRenditionURL()関数を使用して作成者のイメージを取得します。

export function fetchArticleDetails(articleId) {
      const client = getDeliveryClient();
      return client.getItem({
        id: articleId,
        expand: 'fields.author,fields.image',
      }).then((article) => {
        const { fields } = article;
        const title = fields.author.name;
        const date = fields.published_date;
        const content = fields.article_content;
        const imageCaption = fields.image_caption;
        const { topic } = fields;
        const { name } = article;
        const renditionUrls = getSourceSet(article.fields.image);
        const avatarID = article.fields.author.fields.avatar.id;
        // Get the author's avatar image
        return getRenditionURLs(client, avatarID)
          .then((authorRenditionUrls) => (
            // return an object with just the data needed
            {
              id: articleId,
              name,
              title,
              date,
              content,
              imageCaption,
              renditionUrls,
              authorRenditionUrls,
              topicId: topic.id,
              topicName: topic.name,
            }
          ));
      }).catch((error) => logError('Fetching article details failed', error));
    }

fetchArticleDetails()メソッドは、前述のようにgetRenditionURLs()を使用してアバター・イメージを取得します。

データ問合せがあるため、Next.jsコンポーネントでレスポンスをレンダリングできます。

Next.jsコンポーネント

Next.jsはReactとReactに基づいており、JavaScriptのHTMLに似た構文拡張であるJSXと呼ばれるテクノロジーを使用してコンテンツをレンダリングしています。Oracle Content Managementからデータをレンダリングするために純粋なJavaScriptを記述できますが、JSXを使用することを強くお薦めします。

ブログ・アプリケーションは、各ページを多数の小さなコンポーネントに分割します。

次の数セクションでは、各コンポーネントでNext.jsによってアプリケーションをレンダリングする方法の概要を説明します。

ページ・フォルダ

このサイトでは、次の3つのルートを提供します。

ページ・ディレクトリ内の次のページは、アプリケーションのルートとして処理されます。

索引コンポーネント

ホーム・ページは、個々のトピックで構成されるリストで構成されます。これは、ページ/index.jsxにあるIndexコンポーネントによってレンダリングされます。

コンポーネントは、service.jsファイルからデータを取得するためにAPIをインポートします。

import { getTopicsListPageData } from '../scripts/services';

getStaticProps()では、ビルド時にコールされ、コンポーネントはホーム・ページのレンダリングに必要なすべてのデータを取得します。

export async function getStaticProps() {
      const data = await getTopicsListPageData();
      return { props: { data } };
    }

ヘッダー・コンポーネント

索引コンポーネントは、ヘッダー・コンポーネントを使用して、会社のタイトル、会社のロゴおよび「問合せ先/情報」リンクを表示します。

これはsrc/components/Header.jsxにあり、すべてのデータをプロパティとして受信します。サーバーから追加データは取得されません。

TopicsListItemコンポーネント

索引コンポーネントは、TopicsListItemコンポーネントを使用して、リストの個々のトピックを表示します。

src/components/TopicListItem.jsxにあるTopicsListItemコンポーネントは、すべてのデータをプロパティとして受信します。サーバーから追加データは取得されません。

ArticleListPageコンポーネント

トピック・ページには、URLのコンポーネントにIDが渡されるトピック内の記事が表示されます。これは、ページ/articles/[id].jsxにあるArticleListPageコンポーネントによってレンダリングされます。

コンポーネントは、service.jsファイルからデータを取得するためにAPIをインポートします。

import { fetchTopicIds, fetchTopicName, fetchTopicArticles } from '../../scripts/services';

トピック・ページのURLは、トピックIDをパスとして含む動的URLです。たとえば、URLパスは次のとおりです。

Next.jsが動的URLを持つページに静的サイト生成を使用する場合、getStaticPaths()を呼び出してそのページのすべてのパスを取得します。

export async function getStaticPaths() {
      const topicIds = await fetchTopicIds();
    
      // Generate the paths we want to pre-render based on posts
      const paths = topicIds.map((topicId) => ({
        params: { id: topicId },
      }));
    
      return {
        paths,
        fallback: false,
      };
    }

getStaticProps()ファンクションは、トピック・ページの単一インスタンスのデータを取得するために使用されます。トピックIDは、メソッドに渡されるパラメータから取得されます。トピックIDは、このページに必要なすべてのデータを取得するために使用されます。

export async function getStaticProps(context) {
      const { params } = context;
      const { id } = params;
      const [data, topicName] = await Promise.all([fetchTopicArticles(id), fetchTopicName(id)]);
    
      return {
        props: {
          topicId: id,
          topicName,
          data,
        },
      };
    }

ArticesListItemコンポーネント

ArticleListPageコンポーネントは、ArticlesListItemコンポーネントを使用して、リスト内の個々の記事を表示します。

ArticlesListItemコンポーネントはsrc/components/ArticlesListItem.jsxにあり、すべてのデータをプロパティとして受信します。サーバーから追加データは取得されません。

ArticleDetailsPageコンポーネント

「記事」ページには、URLでIDが渡された記事の詳細が表示されます。これは、ページ/article/[id].jsxにあるArticleDetailsPageコンポーネントによってレンダリングされます。

コンポーネントは、service.jsファイルからデータを取得するためにAPIをインポートします。

import { fetchAllArticlesSimple, fetchArticleDetails } from '../../scripts/services';

記事ページのURLは、記事IDをパスとして含む動的URLです。たとえば、URLパスは次のとおりです。

Next.jsが動的URLを持つページに静的サイト生成を使用する場合、getStaticPaths()を呼び出してそのページのすべてのパスを取得します。

export async function getStaticPaths() {
      const articles = await fetchAllArticlesSimple();
    
      // Generate the paths we want to pre-render based on posts
      return {
        paths: articles.map((article) => ({
          params: { id: article.id },
        })),
        fallback: true,
      };
    }

getStaticProps()関数は、記事ページの単一インスタンスのデータを取得するために使用されます。記事IDは、メソッドに渡されるパラメータから取得されます。記事IDは、このページに必要なすべてのデータを取得するために使用されます。

export async function getStaticProps(context) {
      const { params } = context;
      const { id } = params;
      const data = await fetchArticleDetails(id);
      return {
        props: {
          data,
        },
      };
    }

索引コンポーネントとArticleDetailsPageコンポーネントの両方で、ブレッドクラムおよびブレッドクラム・コンポーネントを使用して、ページの上部にブレッドクラムを表示し、ユーザーがトピック・ページまたはホーム・ページに戻ることができるようにします。どちらのコンポーネントも、すべてのデータをプロパティーとして受け取り、サーバーから追加データは取得しません。

タスク3:デプロイのためのアプリケーションの準備

Next.jsのブログ・サイトが構築されたので、問題のデバッグやアプリケーションのプレビューを行う前に、ローカル開発サーバーでこのサイトを確認する必要があります。

デプロイメント用のアプリケーションを2つのステップで準備します。

  1. ローカル開発サーバーをスピン・アップ
  2. スクリプトを使用した開発と本番でのアプリケーションの構築と実行

ローカル開発サーバーのスピン・アップ

次のコマンドを実行して開発サーバーをローカルで起動できます。

npm run dev

次に、ブラウザをhttp://localhost:3000に開き、実際のサイトを表示します。

ノート:ページは事前にレンダリングされません。ページを事前にレンダリングするには、次のセクションを参照してください。

スクリプトを使用した開発と本番でのアプリケーションの構築および実行

本番の場合、ビルド・スクリプトはサイトの静的に生成するために使用されます。

npm run build

startスクリプトは、静的に生成されたページを提供するNode.jsサーバーを起動するために使用されます。

npm run start