Oracle Content ManagementをHeadless CMSとして使用したWebサイトをGatsbyで開発する

# はじめに

この記事では

- Always Free(永年無償)で利用できる**Oracle Content Management - Starter Edition**
- Reactベースの静的サイトジェネレータである**Gatsby**

を利用し、Oracle Content Management(以降OCM)のアセットリポジトリで管理・公開されるアセットを**Gatsby**で開発したWebサイトに表示できるようにします

# 実現したいこと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/6eb4171b-1d1d-14a6-97a9-f6d122d76ec1.png)

- OCMをHeadless CMSとして利用できるように構成
- Gatsbyで作成したWebサイトに、OCMで作成・公開したアセットを表示
- Gatsby → OCMのアセット情報の取得は、**GraphQL**を利用
- ページ構成は、ニュース記事のアセットを新着順に一覧表示するインデックスページと、ニュース記事の詳細表示するページ、のシンプルな構成

# ツール/サービスとそのバージョン

- Oracle Content Management - Starter Edition: 21.7.1
- Git: 2.30.0
- Node.js: v14.17.5
- Gatsby CLI: 3.12.0


# 準備

## OCM - Starter Edition 環境の準備

#### インスタンス作成とアセット登録・公開
OCM - Starter Edition とは、**Oracle Cloud Always Free（無償かつ期間の制限無し）**で利用できる**OCMの機能限定版**です。

以下の記事を参考に、OCM - Starter Edition環境を作成します。

https://qiita.com/nakasato310/items/9457a61d9d27db6ce4f0

最終的には、以下のような形でアセット・リポジトリ内に2件のアセット（画像とコンテンツ・アイテム）が登録された状態になります
![image.png](https://camo.qiitausercontent.com/f048d95722cc32310a89f210aeec8508a4e2c7f2/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132303131302f34396435363766322d613038382d663836662d643139362d6463376139316464346265352e706e67)

コンテンツ・アイテムは、`sampleNewsType`というコンテンツ・タイプより作成されます。sampleNewsTypeは、`タイトル(title)`、`メイン画像(image)`、`ニュース本文(body)`の3つのデータ・フィールド(属性)から構成されます。
![image](https://oracle-japan.github.io/ocitutorials/content-management/41_asset_headless/020.jpg)

#### チャネル・トークンの確認

このアセットリポジトリには、`sampleChannel` という公開チャネルが設定されます。Gatsbyからアセットを参照する際に、この`sampleChannel`の**チャネル・トークン**を利用します

チャネル・トークンは、OCMのUIから`ADMINISTRATION:コンテンツ`→`チャネルのパブリッシュ`→`sampleChannel`→`右上のサイドパネルボタン`→`API`より確認できます。この**チャネル・トークン**をメモします

![image.png](https://camo.qiitausercontent.com/969bcfa200f491793a26e929186144b590142f12/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132303131302f35343935373935382d633734392d383336622d383466332d3838366430373034366335342e706e67)


## Gatsby 環境の準備

Gatsbyの環境を準備します。[Gatsbyの公式チュートリアル](https://www.gatsbyjs.com/docs/tutorial/)もあわせてご確認ください


#### 事前準備

- Node.js と git をインストールします

- `gatsby-cli`をインストールします

```shell
npm install -g gatsby-cli
```

#### Gatsbyプロジェクトの作成と起動

- Gastbyプロジェクトを作成します。ここではプロジェクト名を`my-gatsby-site`とします。また、テンプレートはデフォルトの[gatsby-starter-default](https://github.com/gatsbyjs/gatsby-starter-default.git)が適用されます


```shell
gatsby new my-gatsby-site
```

- 作成されたプロジェクトフォルダに移動し、開発サーバを起動します

```shell
cd my-gatsby-site
gatsby develop
```

- 以下のメッセージが表示されることを確認します

```
You can now view gatsby-starter-default in the browser.
⠀
  http://localhost:8000/
⠀
View GraphiQL, an in-browser IDE, to explore your site's data and schema
⠀
  http://localhost:8000/___graphql
```

- ブラウザで`http://localhost:8000/`を開きます。gatsby-starter-defaultで作成されたサイトを確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/3d2152ef-84d1-0728-f54b-c0ebf385a2ac.png)


## OCMプラグイン

OCMプラグインは以下サイトで提供されています。

https://www.gatsbyjs.com/plugins/@oracle/gatsby-source-oce/

#### OCMプラグインのインストール

- my-gatsby-siteのプロジェクトフォルダで、以下コマンドを実行します

```shell
npm install @oracle/gatsby-source-oce
```

#### gastby-config.jsの編集

インストールされたOCMプラグイン(gatsby-source-oce)を利用するには、`gastby-config.js`のプラグインリストに`gatsby-source-oce`を追加する必要があります

- エディタ等で`gastby-config.js`を開きます

- plugins:[ ]に `gatsby-source-oce`の記述を追加します。以下は追加例です

```config:gastby-config.js
module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`,
    siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },

    ========== (※途中省略) ==========

    `gatsby-plugin-gatsby-cloud`,
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
    {
      resolve: `@oracle/gatsby-source-oce`,
      options: {
        name: `oce`,
        contentServer: `https://snakaocmse-orasejapan.cec.ocp.oraclecloud.com`,
        channelToken: `e38f35f1fece4b309350b97907c08811`,
        proxyUrl: ``,
        items: {
          limit: 100,
          query: ``,
        },
        renditions: `all`,
        preview: false,
        auth: ``,
        staticAssetDownload: `true`,
        staticAssetRootDir: `asset`,
      },
    },
  ],
}
```

#### [説明]構成オプションについて

構成オプションについて簡単に説明します。詳細は[こちらのサイト](https://www.gatsbyjs.com/plugins/@oracle/gatsby-source-oce/)でご確認ください

- **contentServer**(必須)
    - アセットを取得するOCMインスタンスのURLを指定
    - 形式は`https://<インスタンス名>-<テナンシー名>.cec.ocp.oraclecloud.com`
- **channelToken**(必須)
    - アセットを取得する**公開チャネルのチャネルトークン**を指定
    - 準備段階でメモしたチャネルトークンを指定
- **renditions**(任意) 
    - ビルド実行時に、デジタルアセットの自動生成されるレンディション（システムレンディション）を公開サイトのディレクトリにダウンロードするか？（しないか？）を指定
    - 以下3つが指定できます
        - `custom`(デフォルト): カスタムレンディションのみダウンロードする
        - `none`: レンディションはダウンロードしない
        - `all`: システムレンディション(Large/Medium/Small/Thumbnail)とカスタムレンディションを含むすべてのレンディションをダウンロードする
    - 今回は**all**を指定
- **staticAssetDownload**(任意)
    - ビルド実行時に、デジタルアセットのバイナリファイルを公開サイトのディレクトリにダウンロードするか？（しないか？）を指定
    - `true`または`false`(デフォルト) を指定
    - 今回は**true**を指定
- **staticAssetRootDir**(任意)
    - デジタルアセットのダウンロード先のディレクトリを指定
    - `staticAssetDownload`が`true`の場合のみ有効
    - デフォルトは`asset`
    - 今回はデフォルト値(**asset**)をそのまま利用

#### [説明]デジタルアセットのダウンロード設定について

今回の設定は以下のとおりです

- renditions = **all**
- staticAssetDownload = **true**
- staticAssetRootDir = **asset**

デジタルアセット`SampleImage1.jpg`をGatsbyサイトで利用した場合、`SampleImage1.jpg`は以下のように公開サイトのディレクトリにダウンロードされます

- `/asset/SampleImage1.jpg`           (オリジナルファイル/原本ファイル)
- `/asset/Large/SampleImage1.jpg`     (Largeレンディション)
- `/asset/Medium/SampleImage1.jpg`    (Mediumレンディション)
- `/asset/Small/SampleImage1.jpg`     (Smallレンディション)
- `/asset/Thumbnail/SampleImage1.jpg` (Thumbnailレンディション)

例えは、Webページ上でThumbnailレンディションを表示する場合は、`<img src="/asset/Thumbnail/SampleImage1.jpg" />`と指定します


:::note warn
renditionsにallを設定すると、Gatsbyはオリジナルの画像とシステムレンディション（Thumbnail,Smallなど）の両方を、すべての公開済のデジタル・アセットに対して取得・生成します。これにより、ビルドプロセスに多くの処理時間とネットワーク負荷が発生する可能性があります。自動生成されるレンディションを明示的に使用する場合を除いて、noneまたはcustomオプションの使用を推奨します
:::



## 確認

開発環境を起動し、レンディションがダウンロードされているか？を確認します、また、ブラウザ上でクエリを実行できるGraphiQLを使ってアセットが取得できるか？を確認します

#### レンディションのダウンロードを確認

- 開発環境を起動（起動中の場合はControl-Cで停止→起動）します

```shell
gatsby develop
```

- my-gatsby-siteのプロジェクトの`puclic`フォルダ配下に`asset`が作成されます。その中にオリジナルファイルと各レンディションファイルがダウンロードされます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/998db6ea-3dc5-e3ca-d171-49bde0a6a88c.png)

- ブラウザで `http://localhost:8000/asset/Thumbnail/SampleImage1.jpg`を開きます。サムネイルサイズの画像が表示できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/4f53913c-e939-0271-7a18-a7272c941e6c.png)


#### GraphiQLでOCMのアセットを取得する

- ブラウザで`http://localhost:8000/___graphql`を開きます。左側の`Explorer`に**allOceAsset**や**oceAsset**が表示されることを確認します 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/6cd14379-ea2b-adbc-9891-b87e366e2d4b.png)

- `allOceAsset`をクリックすると、クエリを展開してくれます。今回は**sampleNewsType**アセットタイプの`id`、`name`、`oceId`、`slug`を取得します

-  `filter > oceType` を開き、`eq:`を選択します

-  `eq:` の隣の `" "` の中に`sampleNewsType`を入力し、**実行**（画面上の横向き三角ボタン）をクリックします
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/6a40b359-19a7-65a9-87e3-7ca208fa5f84.png)

-  結果が右ペインに表示されます。`edges>node>id`が自動的に追加されます。この`edges>node`配下にアセットの基本情報や属性情報があります
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/90c8acf0-218d-4367-dd6b-414334a5c995.png)

-  `edges>node`配下の`id`、`name`、`oceId`、`slug`の4項目を選択し、クエリを実行します。指定したアセット・タイプのコンテンツ・アイテムの一覧が取得できます（今回の場合、2件のコンテンツ・アイテムを取得）
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/c23d5d01-8c5a-1eaf-1d9d-3c407296c29c.png)

-  コンテンツ・アイテムの属性情報も取得する場合は、`edges>node>oceFields`を展開します。ここでは`title`と`body`を追加で選択し、クエリと実行します。コンテンツ・アイテムの**title(タイトル)**と**body(本文)**が取得できます
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/c2019949-46b7-a6af-f461-938868b7d397.png)

- クエリで取得した情報をソートする場合は、`sort`を展開し、ソート対象の`fields`と`order`を指定します。今回は、`fields`に**"updatedDate"**、`order`に**"DESC"**を指定します。実行結果は以下のとおりです
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/c1a0e1f6-e21e-75ad-508f-f8bf3fa6e196.png)


# Webページの編集

GatsbyサイトのWebページで、OCMから取得したニュースコンテンツを一覧表示できるようにします

#### ニュース一覧の表示

GatsbyサイトのWebページにOCMから取得した`sampleNewsType`のニュース・コンテンツの一覧を新着順で表示します

- my-gatsby-siteプロジェクトの`src/pages/index.js`を開き、以下の通りに編集します

```javascript:src/pages/index.js
//index.js
import * as React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"
import Seo from "../components/seo"

const IndexPage = ({data}) => {

  const oceposts = data.allOceAsset.nodes

  return(
    <Layout>
      <Seo title="Home" />
      <h1>ニュース一覧</h1>

      {oceposts.map(post => {

        console.log(post)

        return (
          <div key={post.id}>
              <h3>{post.oceFields.title}</h3>
              <img src={"/asset/Thumbnail/" + post.oceFields.image.name} alt={post.oceFields.image.name} />
          </div>
        )
      })}
    </Layout>
  )
}

export const query = graphql`
  {
    allOceAsset(
      filter: {oceType: {eq: "sampleNewsType"}}
      sort: {order: DESC, fields: updatedDate}
    ) {
      nodes {
        id
        oceId
        name
        slug
        oceFields {
          title
          image {
            name
          }
        }
      }
    }
  }
`

export default IndexPage

```

- `index.js`を保存します

- 開発環境が起動されていることを確認し、`http://localhot:8000/`を開きます。ニュース・コンテンツの**title(タイトル)**と**image(メイン画像)のサムネイルサイズの画像**が表示されることを確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/2e40494b-79fd-8e9d-fa27-b015d1632eb6.png)

#### 説明(index.js)

- `import { graphql } from "gatsby"`でgraphQLをインポート

- `const IndexPage = ({data}) => {`の `data`に、後述のgraphqlで取得したデータが受け渡される
- graphqlの`allOceAsset()`を利用して、ニュース一覧を表示する際に必要な情報を取得
    - `filter: {oceType: {eq: "sampleNewsType"}}`で取得するアセット・タイプを指定
    - `sort: {order: DESC, fields: updatedDate}`でアセットの更新日時順（新→旧）にソート
    - `oceId`、`slug`はニュースの詳細ページを表示する際に利用
    - ニュース画像の`image`はファイル名(`image.name`)のみを取得し、ビルド時に公開環境に自動生成されるサムネイル画像をWebページ上で表示（OCMの公開アセットへの直リンクURLは利用しない）


# ビルド

- 開発環境が起動されている場合は、Control-Cで停止します

- Buildを実行します

```shell
gatsby build
```

- Buildした内容をローカル環境で確認します

```shell
gatsby serve
```

- 以下のメッセージが表示されるのを確認し、ブラウザで`http://localhoat:9000/`を開きます

```
You can now view gatsby-starter-default in the browser.
⠀
  http://localhost:9000/
```

- 作成したWebサイトが表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/efdf6bd3-ce1c-0f9b-75c3-29da78fb1364.png)




# ニュースの詳細表示ページ

前の手順で作成したニュース一覧ページ(index.js)は、ニュース一覧を表示するだけでした。
ここでは、ニュース一覧ページでニュースタイトルや画像をクリックすると、**ニュースの詳細ページ**が表示されるようにします

#### ニュース詳細ページの作成

`sampleNewsType`のニュース・コンテンツを詳細表示するテンプレートを作成します

- `my-gastby-site/src/`の下に`templates`フォルダを作成し、`news-post.js`ファイルを作成します

- `news-post.js`を以下の通りに編集し、保存します

```javascript:src/templates/news-post.js
// news-post.js
import * as React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"
import Seo from "../components/seo"

const NewsPostTemplate = ({ data }) => {
  const post = data.oceAsset

  console.log(post)

  return (
    <Layout>
    <Seo title={post.oceFields.title} />

      <article>
        <header>
          <h1 itemProp="headline">{post.oceFields.title}</h1>
        </header>

        <img src={"/asset/" + post.oceFields.image.name} alt={post.oceFields.image.name} />

        <section
          dangerouslySetInnerHTML={{ __html: post.oceFields.body }}
          itemProp="articleBody"
        />
      </article>
      <hr />
    </Layout>
  )
}

export default NewsPostTemplate

export const query = graphql`
  query newsquery(
    $id: String
  ){
    oceAsset(oceId: {eq: $id}) {
      id
      slug
      oceId
      oceFields {
        title
        image {
          name
          }
        body
      }
    }
  }
`
```

#### 説明(news-post.js)

- graphqlで`oceAsset(oceId: {eq: $id})`の`$id`はアセットを一意に識別するコンテンツIDを指定

- 属性フィールドは、`title(タイトル)`、`body(本文)`、`imageのname(メイン画像の名前)`を取得し、それぞれをページ上にそのまま表示します

- `<img src={"/asset/" + post.oceFields.image.name} alt={post.oceFields.image.name} />`でimageのオリジナルファイルを表示します

- `dangerouslySetInnerHTML={{ __html: post.oceFields.body }}`で、WYSIWYGエディタで作成した本文がそのままWebページ上に表示されます


#### gatsby-node.js の編集

my-gatsby-siteフォルダ配下の`gatsby-node.js`を編集し、動的なページ生成を実装します。この場合は、ニュース一覧ページからリンクされるニュースの個別詳細ページを、取得したコンテンツ・アイテムの数だけ生成します

- `my-gatsby-site/gatsby-node.js`を開きます。最初は空ページですが、ここに以下のコードを入力します

```javascript:gatsby-node.js
//gatsby-node.js
const path = require("path");

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions;

  const result = await graphql(`
      query {
        allOceAsset(filter: {oceType: {eq: "sampleNewsType"}}) {
          nodes {
            oceId
            slug
            id
          }
        }
      }
  `);

  if (result.errors) {
    throw result.errors;
  }

  const posts = result.data.allOceAsset.nodes;

  posts.forEach((post) => {
    createPage({
      path: `/news/${post.slug}`,
      component: path.resolve(`src/templates/news-post.js`),
      context: {
        id: post.oceId
      },
    });
  });
};

```

- `gatsby-node.js`を保存します

#### 説明(gatsby-node.js)

- `createPages`のAPIを利用してニュースの詳細ページを生成します。詳細は[こちらの公式ドキュメント](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/)を参照

- GraphQLでsampleNewsTypeのコンテンツ・アイテムの`oceId`、`slug`、`id`を取得
    - 詳細ページへのパス(URL)は `/news/${post.slug}`を指定
    - 詳細ページのテンプレートは前の手順で作成した`./src/templates/news-post.js`を指定
    - 詳細ページへの引数は`context: {id: post.oceId}`で`oceId(コンテンツID)`を指定


#### ニュース一覧(index.js)の編集

ニュース一覧ページ(`index.js`)に詳細ページへのリンクを追加します

- `index.js` を開き、以下の通りに編集します

```javascript:src/pages/index.js
//index.js
import * as React from "react"
import { graphql } from "gatsby"

import { Link } from "gatsby"

import Layout from "../components/layout"
import Seo from "../components/seo"

const IndexPage = ({data}) => {

  const oceposts = data.allOceAsset.nodes

  return(
    <Layout>
      <Seo title="Home" />
      <h1>ニュース一覧</h1>

      {oceposts.map(post => {

        console.log(post)

        return (
          <div key={post.id}>
            <Link to={"/news/" + post.slug} itemProp="url">
              <h3>{post.oceFields.title}</h3>
              <img src={"/asset/Thumbnail/" + post.oceFields.image.name} alt={post.oceFields.image.name} />
            </Link>
          </div>
        )
      })}
    </Layout>
  )
}

export const query = graphql`
  {
    allOceAsset(
      filter: {oceType: {eq: "sampleNewsType"}}
      sort: {order: DESC, fields: updatedDate}
    ) {
      nodes {
        id
        oceId
        name
        slug
        oceFields {
          title
          image {
            name
          }
        }
      }
    }
  }
`

export default IndexPage

```

#### 説明(index.js)

変更点は以下3点

- 4行目に`import { Link } from "gatsby"`を追加
- 24行目に`<Link to={"/news/" + post.slug} itemProp="url">`。リンク先はgatsby-node.jsのパスで指定した通り、`/news/(コンテンツ・アイテムのslug)`を指定
- 27行目に`</Link>`を追加
    - Link APIは[こちらの公式ドキュメント](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-link/)を参照

# 確認

- 開発環境を起動します

```shell
gatsby develop
```

- `http://localhost:8000/`をブラウザで開きます。以下のように各ニュースに詳細ページへのリンクが設定されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/081ec605-3446-14db-0719-20a515675b6e.png)

- ニュース（ここでは「002ニュースのタイトル」）をクリックします。ニュースの詳細ページが表示されます。また、URLも `/news/<slug>`となっていることを確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120110/fe72812f-0e1b-5add-83f0-8eae6867c8ae.png)


- 開発環境を停止します。buildし、ローカル環境で実行します

```shell
gatsby build
gatsby serve
```

- `http://localhost:9000/`を開き、動作を確認します



# おわりに

Oracle Content Management(OCM)のアセットリポジトリで管理・公開されるアセットをGatsbyで作ったWebページに表示する流れを紹介しました。Gatsbyのコーディングは、もっとスマートな方法があると思います。改善点などあれば、お知らせいただけると幸いです

この記事を作成する際に参考にした記事を最後にリンクします（他のHeadless CMS + Gatsbyの実装サンプル方法を参考にしました）

- [About gatsby-source-oce](https://www.gatsbyjs.com/plugins/@oracle/gatsby-source-oce/)
- [How to query data with GraphQL in Oracle Content Management](https://preston.so/writing/how-to-query-data-with-graphql-in-oracle-content-management)
- [Gatsby＋microCMSで手を打つ](https://qiita.com/atomyah/items/759823f6c1e86ce44b23)
- [GatsbyJS + microCMSでJamstackなオウンドメディアを作ろう](https://blog.microcms.io/gatsby-microcms-media/)