Headless Oracle Content ManagementでNext.jsで最小のサイトを構築

イントロダクション

Next.jsは、サーバー側のレンダリングやReactベースのWebアプリケーション用の静的Webサイトの生成などの機能を可能にする、オープンソースのReactフロントエンド開発Webフレームワークです。

Next.jsアプリケーションでOracle Content Managementコンテンツを利用するために、GitHubのオープンソース・リポジトリとして使用可能なNext.js最小サンプルを使用できます。

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

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

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

前提条件

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

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

構築しているもの

Next.jsを最小限にすると、Oracle Content Managementリポジトリからイメージおよびその他のコンテンツを簡単に取得できます。

構築している内容をご覧いただくには、チュートリアルの終わりをご紹介します。この最終的な状況は、Oracle Content Managementからのコンテンツを消費するNext.jsの最小基本サイトです。

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

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

この図は、Next.js最小サイトのランディング・ページを示しています。

このチュートリアルの最後にお問合せページが表示されます。

このイメージは、Next.js最小サイトのContact usページを示しています。

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

タスク1: Oracle Content Managementの準備

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

このチュートリアルでは、コンテンツ・モデルを作成する必要があります。ダウンロード可能なアセット・パックが使用可能で、空のリポジトリにはコンテンツ・タイプと関連するコンテンツが入力されます。

Oracle Content Managementを準備するには:

  1. チャネルおよびアセット・リポジトリを作成します
  2. Oracle Content Managementサンプル資産パックのインポート

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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ファイルをコンピュータ上の場所に抽出します。抽出後、この場所にはOCEMinimal_data.zipというファイルが含まれます。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Oracle Content Management Sample Asset Packのインポート後、Next.jsでの最小サイトの作成を開始できます。

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

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

このチュートリアルでは、このサンプルのメイン・コンテンツ・タイプとして「MinimalMain」というコンテンツ・タイプを使用しています。このコンテンツ タイプは、ヘッダーおよびフッターのロゴと、ナビゲーションに含めるページのリストで構成されます。

この図は、「最小」サンプルのホームページを示しています。

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

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

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

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

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

表示名 フィールド・タイプ 必須 マシン名
headerLogo シングルバリュー・メディア・フィールド headerLogo
footerLogo シングルバリュー・メディア・フィールド footerLogo
ページ 複数値参照フィールド ページ

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

この図は、コンテンツ・タイプ'MinimalMain'の定義を示しています。これには、headerLogo、footerLogo、ページなどのデータ・フィールドが含まれます。

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

表示名 フィールド・タイプ 必須 マシン名
セクション 複数値参照フィールド セクション

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

この図は、コンテンツ・タイプ'MinimalPage'の定義を示しています。このデータ フィールドには、セクションが含まれます。

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

表示名 フィールド・タイプ 必須 マシン名
type 単一値テキスト・フィールド X type
ヘディング 単一値テキスト・フィールド ヘディング
本体 単一値の大型テキスト・フィールド 本体
画像 シングルバリュー・イメージ・フィールド イメージ
アクション 単一値の埋込みコンテンツ・フィールド アクション

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

この図は、コンテンツ・タイプ'MinimalSection'の定義を示しています。これには、タイプ、見出し、本文、イメージ、アクションなどのデータ・フィールドが含まれます。

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

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

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

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

この図は、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-minimal-sample.git
    cd oce-nextjs-minimal-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=ba0efff9c021422cb134c2fd5daf6015

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

Oracle Content Management Content SDKの使用

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

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

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

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

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

Content SDKでは、DeliveryClientオブジェクトを使用してエンドポイントを指定します。そのクライアント・オブジェクトを使用してすべてのリクエストを作成できます。

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ファイルには、このNext.js最小アプリケーションのデータを取得するための関数が含まれています。

fetchOceMinimalMain()メソッドは、最小限の余裕のあるコンテンツ・タイプMinimalMainを取得します。

export async function fetchOceMinimalMain() {
      const data = await getItem('minimalmain', 'fields.headerlogo,fields.footerlogo,fields.pages');
      if (!data.hasError) {
        const { fields } = data;
        const { headerlogo, footerlogo } = fields;
        // Extract the sourceset for the headerImage and footerImage and put it back in the data
        data.headerRenditionURLs = getSourceSet(headerlogo);
        data.footerRenditionURLs = getSourceSet(footerlogo);
      }
      return data;
    }

イメージをレンダリングするために、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;
    }

fetchPage()メソッドは、ページのスラグ値を使用してコンテンツ・タイプMinimalPageを取得します。

export async function fetchPage(pageslug) {
      // Get the page details
      const page = await getItem(pageslug, 'fields.sections');
      return page;
    }

getRenditionURLs()メソッドは、そのイメージのIDを使用してセクションに定義できるイメージのレンディションURLを取得します。

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

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

Next.jsコンポーネント

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

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

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

ページ・フォルダ

当社では、/pageという1つのルートを提供しています。リクエストは、page/[[..slug]].jsxファイルに定義されているメイン コンポーネントに転送されます。next.config.jsでリダイレクトを指定することで、ルート・パス・リクエストが/ページにリダイレクトされます。

  async redirects() {
        return [
          {
            source: '/',
            destination: '/page/',
            permanent: true,
          },
        ]
      }

すべてのページには、会社のロゴとリンクを含むヘッダーと、ロゴおよびソーシャル・メディア・アイコンを含むフッターがあります。ページは静的URLを介してアクセスされ、関連するデータを子コンポーネントに渡す前に必要なすべてのデータを取得します。

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

メイン・コンポーネント

すべてのページは、ページ/[[..slug]].jsxにあるメイン コンポーネントによってレンダリングされます。Next.jsのすべてのルートの捕捉は、二重カッコ([[...slug]])にパラメータを含めることでオプションです。「ページ」リストの最初のページにデフォルト設定されたルート・ページにはパラメータが渡されないため、スラグ・パラメータはオプションとして定義します。

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

import { fetchOceMinimalMain, fetchPage, getRenditionURLs } from '../scripts/services';

ページのURLは、ページ・スラグがパスとして含まれている動的URLです。たとえば、URLパスは次のようになります。

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

export async function getStaticPaths() {
      const appData = await fetchOceMinimalMain();
      const { fields } = appData;
    
      // Generate the paths we want to pre-render based on posts
      const paths = fields.pages.map((page) => ({
        params: { slug: [page.slug] },
      }));
      // Also add the path for the root /
      paths.push({
        params: { slug: [] },
      });
      return {
        paths,
        fallback: false,
      };
    }

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

export async function getStaticProps(context) {
      // fetch the minimal main data
      const appData = await fetchOceMinimalMain();
      // find the slug param from the context. If its null, default to the first page slug
      const { params } = context;
      let { slug } = params;
      if (slug == null) {
        slug = appData.fields.pages[0].slug;
      }
      // fetch the page corresponding to the slug
      const pageData = await fetchPage(slug);
      const { headerRenditionURLs, footerRenditionURLs, fields } = appData;
      const { sections } = pageData.fields;
    
      // for each section in the page, if a image is present, get the corresponding rendition urls
      // and insert it back into the section
      const promises = [];
      sections.forEach((section) => {
        // add a promise to the total list of promises to get any section rendition urls
        if (section.fields.image) {
          promises.push(
            getRenditionURLs(section.fields.image.id)
              .then((renditionURLs) => {
                // eslint-disable-next-line no-param-reassign
                section.renditionURLs = renditionURLs;
              }),
          );
        }
      });
    
      // execute all the promises and return all the data
      await Promise.all(promises);
      return {
        props: {
          headerRenditionURLs,
          footerRenditionURLs,
          pages: fields.pages,
          pageData,
        },
      };
    }

ページ・コンポーネント

ページ・コンポーネントは、ページに定義されているすべてのセクションのレンダリングを担当します。コンポーネント/Page.jsxにあるPageコンポーネントを開きます。メイン・コンポーネントから渡されたデータを使用するだけです。サーバーから追加データは取得されません。セクション データをレンダリングするには、セクション コンポーネントを使用します。

Page.propTypes = {
      pageData: PropTypes.shape().isRequired,
    };

コンポーネント/Header.jsxおよびフッター・コンポーネントにあるヘッダー・コンポーネントは、コンポーネント/Footer.jsxにあるため、メイン・コンポーネントから渡されるデータのみを使用します。サーバーから追加データは取得されません。

セクション・コンポーネント

コンポーネント/Section.jsxにあるセクション・コンポーネントは、ページ・コンポーネントによって使用され、MinimalSectionタイプのコンテンツ・アイテムのレンダリングに使用されます。

このコンポーネントには、ページ・コンポーネントから渡されるデータがあります。

エラーコンポーネント

コンポーネント/Error.jsxにあるErrorコンポーネントは、ページおよびメイン・コンポーネントで使用されます。サーバーへのコール中にエラーが発生した場合は、エラーがレンダリングされます。

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

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

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

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

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

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

npm run dev

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

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

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

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

npm run build

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

npm run start