👍

【初GraphQLにもおすすめ】楽にJamstackなサイトを作ろう Headless CMS(Prismic)×Next.js

2023/11/17に公開

はじめに

シンクロンのエンジニアと申します。
本記事は個人開発(またはそれに近い状態)で楽して静的なサイトを作ることを趣旨としています。勉強目的でサクッと作ることが可能なため(2023年11月現在無料)、GraphQLやHeadless CMSが初めての方は参考にしていただけると幸いです。

採用アーキテクチャ

フレームワーク:Next.js
ホスティングサービス:Vercel
Headless CMS:Prismic
API:GraphQL

開発環境:Node.js18.12.1 Docker in WSL2(windows)

開発環境と今回のライブラリ

"@prismicio/client": "^7.2.0",
"@prismicio/next": "^1.3.6",
"@prismicio/react": "^2.7.3",
"graphql": "^16.8.1",
"graphql-request": "^6.1.0",
"next": "13.4.13",
"typescript": "5.1.6"

"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/client-preset": "4.1.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typescript-graphql-request": "^6.0.0",
"@graphql-codegen/typescript-operations": "^4.0.1",
"codegen-prismic-fetch": "^1.0.5",

採用経緯

とある日、「完全無料で新しめの技術スタックを使用したポートフォリオ用WEBアプリを作成したい!」と気まぐれに思いました。
GCPやAWSのCloudサービスは一部無料枠が設けられて良さげでしたが、Headless CMS(Headlessである必要もない)を使う方がもっと楽そう(管理面)でした。Headless CMSは使ったことがなく、新しい技術を知るという目的でこのアーキテクチャを採用しています。

採用した感想

PrismicはGUIを起動してデータの型を作成でき、レコード作成もGUI上で簡単に行えます。CMSは誰でも簡単にできそうなのが素晴らしいです。
API面に関しては、GraphQLのschemaをPrismicが提供しており、GraphQL Code Generatorを使うことで簡単にGrapQLのschemaを作成・利用可能です。また、自身のページからGraphQLのplaygroundでQueryを実行・確認できるため、容易?にコンテンツを取得できました。
REST APIも提供されているためそちらでも利用可能ですが、今回はポートフォリオとして後々公開する予定のため、モダンなGraphQLの方を採用しています。自身でバックエンドを作成しない場合には、GraphQLの方が自由度高くデータを取得しやすいですね。一点だけ注意点として、Mutation(書き込みAPI)が提供されていないので、アプリケーションから登録したい場合は採用を見送った方が良いかもしれません。GraphQLのschemaを自身で作る必要がないため、GraphQL初めて触る場合にもおすすめできると思います。

JamstackなWEBアプリでバックエンド(データ管理)をサボりたい楽したい方は参考にしてください。

Prismicの採用理由

想定していたアーキテクチャの良記事を見つけたので...
先人の記事、神とさせてください。
https://zenn.dev/mogami/articles/prismic_jamstack
https://zenn.dev/sterashima78/scraps/24184233423f4b

PrismicCMSは編集ユーザーが一人のプランの場合、無料でかなり制限が緩いので今回採用しました。

上記の記事にてこの文言を見かけてmicroCMSでなく、Prismicを優先しています(触るのは私のみのため)。公式にもNext.jsとの組み合わせ方載っていたので、楽そうに見えました。

Prismicのセットアップ

フレームワークにNext.jsを採用しており、create-next-app済みから始めます。

公式にNext.jsと組み合わせる手順が載っているので、公式通り進めます。
https://prismic.io/docs/setup-nextjs

以下のコマンド打つだけで自動でセットアップしてくれます。

npx @slicemachine/init@latest

2023年11月現在は以下のことが行われます。

  1. 新しい Prismic リポジトリを作成(または既存のリポジトリを指定)
  2. package.jsonにstart-slicemachineスクリプト追加
  3. Prismicリポジトリの名前とスライスライブラリの場所を含むslicemachine.config.json設定ファイルを作成
  4. プロジェクトのルートにprismicio.js|tsファイルを作成し、Prismicを設定
  5. api/previewと/api/exit-previewにルートを追加し、プレビューを有効にします(これらのルートがどのように機能するかについては、@prismicio/next Technical Referenceを参照してください)。
  6. Next.jsで必要な依存関係がインストールされる:prismicio/client、@prismicio/react、@prismicio/next、slice-machine-ui、@slicemachine/adapter-next
    スライスをGUIでいじるための[app|pages]/slice-simulator.js|jsx|tsxファイルが作成される

データの型を作成する

データの型作成についてドキュメント
セットアップによりpackage.jsonのscriptsにslicemachineが追加されているはずです。

以下コマンドでGUIをローカルで起動します。

npm run slicemachine

以下のようなGUIが起動し、custom typesタブからcreateすることでデータの型を作成できます。
各フィールド(プロパティ)の型は単純なkey value、選択式、画像、リッチテキストなどが定義可能です。

実データを作成する

slicemachineのGUI画面の左上部分に自身のPrismicリポジトリ名が表示されているはずです。
そのリンクを押下することで、自身のPrismicリポジトリへ遷移でき(prismicへのログインを要求されます←github連携が楽です)

遷移すると以下の画面が出現

すでに登録済みですが、Create newボタンを押下することで簡単に実データが作成可能です。
CMSなのでPrismicのサーバーで永続化してくれます。

GraphQLのschema(API定義)を利用する

公式にGraphQLでのAPI利用手段が用意されています。
https://prismic.io/docs/api#selective-fetching-and-the-graphql-api

自身のPrismicリポジトリ+ '/graphql'でプレイグラウンドへアクセス可能です。また、このURLがAPIをたたく際のエンドポイントにもなっています。
https://your-repo-name.cdn.prismic.io/graphql

  1. プレイグラウンド上でQuery文を作りましょう。ちなみに型を作成していないとschemaは生成されていません。(GraphQLのQueryについて、本記事での説明は割愛します)

  2. 実行可能なQueryが作成できた場合、プロジェクトのディレクトリ(Next.js)へ戻り、.gqlファイルを新規作成してQueryをコピペしましょう。

  3. GraphQL Code Generatorをインストールして、npx graphql-code-generator init

  4. codegen-prismic-fetchをインストールしましょう

  5. codegen.ts(GraphQL Code Generatorの設定ファイル)が作成されているはずなので、必要な設定を追加しましょう。以下参考です。

codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  overwrite: true,
  // urlは自身のPrismicリポジトリへ変更してください
  // 普通じゃschemaを提供していないので、customFetchで取得する
  schema: { 'https://your-repo-name.prismic.io/graphql': { customFetch: 'codegen-prismic-fetch' } },
  // GraphQLのQueryを記したファイルの拡張子へ修正してください
  documents: 'src/**/*.gql',
  generates: {
    'src/gql/': {
      preset: 'client',
      // pluginsはpresetと重複して生成されるので、presetに任せる
      // plugins: ['typescript-graphql-request'],
      plugins: [
        {
          // Custom Scalar の branded type 定義
          add: {
            content: `export type DateString = string & { readonly __brand: unique symbol }`,
          },
        },
      ],
      config: {
        // Scalerのany型が許されなくなる
        // strictScalars: true,
        useTypeImports: true,
        skipTypename: true,
        arrayInputCoercion: true,
        avoidOptionals: {
          field: true,
          inputValue: false,
          object: true,
          defaultValue: false,
        },
        scalars: {
          Date: 'DateString',
        },
        enumsAsTypes: true,
      },
    },
    './graphql.schema.json': {
      plugins: ['introspection'],
    },
  },
}

export default config

  1. すでにpackage.jsonへscriptが追加されているので、npm run codegenを実行してください。TypedDocumentNode(Query文をTypeScriptから簡単に投げれるように変換済みのやつ)やresponseの型を生成できます。gqlディレクトリのgraphql.tsの下の方にいるはずです。

実際にGraphQLをアプリから利用する

用途的に軽いライブラリを採用しましたが、お好みのGraphQLクライアントを利用してください。
graphql-request
@prismicio/clientで生成したクライアントを用いて、フェッチを行うようGraphQLクライアントへ設定します。
Queryを投げる際はGraphQLクライアントの引数にnpm run codegenで生成したTypedDocumentNodeを渡してください

以下はGraphQLクライアントの参考です。

client.ts
import * as prismic from '@prismicio/client'
import { GraphQLClient } from 'graphql-request'
// アプリ全体で共通のprismicClient
// The rest of the file...
const prismicClient = prismic.createClient('Prismicのリポジトリ名', {
  // If your repository is private, add an access token
  // accessTokenはここにも設定しないとinvalid access token になってしまいます
  accessToken: process.env.PRISMIC_ACCESS_TOKEN,

  // This defines how you will structure URL paths in your project.
  // Update the types to match the custom types in your project, and edit
  // the paths to match the routing in your project.
  //
  // If you are not using a router in your project, you can change this
  // to an empty array or remove the option entirely.
  routes: [
    {
      type: 'page',
      path: '/:uid',
    },
    {
      type: 'recipe',
      path: '/recipe',
    },
  ],
})

// アプリ全体で共通のprismicへのGraphQLClient
const client = new GraphQLClient(prismic.getGraphQLEndpoint('Prismicのリポジトリ名'), {
  // eslint-disable-next-line @typescript-eslint/unbound-method
  fetch: prismicClient.graphQLFetch as (
    input: RequestInfo | URL,
    init?: RequestInit | undefined,
  ) => Promise<Response>,
  method: 'get',
  // prismicで生成したapischemaのバージョン((https://your-repo-name.prismic.io/api/v2))
  // APIのtoken(https://your-repo-name.prismic.io/settings/apps/)
  // tokenはBearerで仕込む(https://prismic.io/docs/integration#add-an-authorization-token)
  headers: {
    'Prismic-Ref': process.env.PRISMIC_REF ?? '',
    Authorization: `Bearer ${process.env.PRISMIC_ACCESS_TOKEN}`,
  },
})

export default client

APIのセキュリティ対策

初期状態だと誰でもAPIを叩けるため、privateにしてトークンを必要とするように変更しましょう。
自身のPrismicリポジトリの設定画面から、Repository securityをPrivateへ修正しましょう。
そして、Generate an Access Tokenからトークンを生成して、アプリケーションからは環境変数等で利用するようにしましょう。

公式にセキュリティ対策した場合のAPI利用方法が載っています。(このドキュメントがなかなか見つからなくて苦労しました)
https://prismic.io/docs/integration#add-an-authorization-token

client.tsの一部
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  headers: {
    'Prismic-Ref': process.env.PRISMIC_REF ?? '',
    Authorization: `Bearer ${process.env.PRISMIC_ACCESS_TOKEN}`,
  },
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

おわりに

近年は色々なサービスを組み合わせることで、自分でサーバー立てる必要がないのは素晴らしいですね(しかも無料)。

Discussion