Next.js + microCMS の型周りどうしてるか暴露する
弊社コーポレートサイトは Next.js + TypeScript + microCMS で作られています。getStaticProps
でデータフェッチするわけですが、microCMS から取得するデータの型定義を自動化したく試行錯誤したので私がやっているやり方をご紹介します。
まずはどんな手順なのか
- microCMS のスキーマをダウンロードする
- スキーマから型定義を生成する
- aspida の Methods を生成する
- aspida で axios の型定義済みメソッドを生成する
上記の順番で生成をします。とはいえ、3 についてはコマンドを叩けばできますし、2,4 については自動で生成されます。次に各手順を一つ一つ解説します。
ディレクトリ構成について
まずは前段階としてディレクトリの構成について解説をします。私の環境では Next.js のプロジェクトルートに src
ディレクトリを作成し、その中に cms
ディレクトリを配置して管理しています。
cms ディレクトリ配下の解説をします。
api ディレクトリ
aspida の型定義ファイルの生成物をここに格納します
schema ディレクトリ
microCMS のスキーマをここに格納します。
types ディレクトリ
スキーマから生成する各エンドポイントのレスポンス、リクエストパラメータの型定義をここに格納します。
utils ディレクトリ
実際に getStaticProps などで使うデータフェッチを行うメソッドをここで定義しています。
microCMS のスキーマをダウンロードする
API 設定 > API スキーマ > ページ下部の この設定をエクスポートする
をクリックします。
エクスポートした json データを見てみると下記のようになっています。
{
"apiFields":[
{
"idValue":"MNNbwT-Gyt",
"fieldId":"title",
"name":"タイトル",
"kind":"text",
"required":true,
"isUnique":false
},
{
"fieldId":"image",
"name":"アイキャッチ",
"kind":"media"
},
{
"fieldId":"body",
"name":"本文",
"kind":"repeater",
"required":false,
"customFieldCreatedAtList":[
"2021-07-12T05:51:14.785Z",
"2021-07-12T05:51:43.122Z"
]
},
{
"fieldId":"tags",
"name":"タグ",
"kind":"relationList"
}
],
"customFields":[
{
"createdAt":"2021-07-12T05:51:14.785Z",
"fieldId":"html",
"name":"HTML",
"fields":[
{
"idValue":"S_ECb4fI0Y",
"fieldId":"content",
"name":"HTML",
"kind":"textArea"
}
],
"position":[["S_ECb4fI0Y"]],
"updatedAt":"2021-07-12T05:51:14.785Z",
"viewerGroup":"LfC"
},
{
"createdAt":"2021-07-12T05:51:43.122Z",
"fieldId":"editor",
"name":"エディター",
"fields":[
{
"idValue":"5oQL7u3qSg",
"fieldId":"content",
"name":"Editor",
"kind":"richEditor"
}
],
"position":[["5oQL7u3qSg"]],
"updatedAt":"2021-07-12T05:51:43.122Z",
"viewerGroup":"LfC"
}
]
}
次に先ほど解説をしたディレクトリ構成のsrc/cms/schema
に json データを入れます。
スキーマから型定義を生成する
microcms-typescript というライブラリを使ってスキーマから型定義を生成します。
ライブラリをインストールします。
$ npm i microcms-typescript --save-dev
次に package.json の scripts に追記します。src/cms/schema
をもとにsrc/cms/types/response.ts
を生成します。
"scripts": {
・・・
"gen:types": "npx microcms-typescript src/cms/schema src/cms/types/response.ts"
・・・
},
これでレスポンスの型定義はできたのですが、リクエスト時の型定義ができていません。とは言っても microCMS のリクエストパラメータは全て同じでかつ決まったものなのでこれは手動で定義してしまいます。
src/cms/types/request.ts
export type ContentsQuery = {
draftKey?: string
offset?: number
limit?: number
orders?: string
q?: string
fields?: string
ids?: string
filters?: string
depth?: number
}
export type ContentQuery = {
draftKey?: string
fields?: string
depth?: number
}
aspida の Methods を生成する
aspida とは、axios などの HTTP クライアントにいい感じに型付けをしてくれるライブラリです。先ほどの request.ts と response.ts をもとに aspida を使って axios の型定義をしていきます。
ただ、aspida で使うそれぞれのエンドポイントのメソッドをまずは生成しなければいけません。例えば /blog
であれば下記のようになります。
import { ContentsQuery, EndPoints } from 'src/cms/types'
export type Methods = {
get: {
query?: ContentsQuery
resBody: EndPoints['gets']['blog']
}
}
ほぼほぼ同じような定義で済むのでこれについてはコマンド一発で生成されるようにします。その際に scaffdog と言ってマークダウンファイルをテンプレートにコマンドで該当ファイルを生成してくれるライブラリ使うことで手間を省くことができます。
ただし microCMS ではオブジェクト形式と配列形式があるのでテンプレートをそれぞれ用意します。
$ npm i --save-dev scaffdog
プロジェクトルート直下で npx scaffdog init
をすると 直下に /.scaffdog
が生成されていると思うのでその中にマークダウンファイルを作成します。
api-array.md
---
name: "api-array"
root: "src/cms/apis"
output: "."
ignore: []
questions:
value: "Please enter any text."
---
# `{{ inputs.value }}/index.ts`
```javascript
import { ContentsQuery, EndPoints } from 'src/cms/types'
export type Methods = {
get: {
query?: ContentsQuery
resBody: EndPoints['gets']['{{ inputs.value }}']
}
}
```
# `{{ inputs.value }}/_id@string/index.ts`
```javascript
import { ContentQuery, EndPoints } from 'src/cms/types'
export type Methods = {
get: {
query?: ContentQuery
resBody: EndPoints['get']['{{ inputs.value }}']
}
}
```
api-object.md
---
name: "api-object"
root: "src/cms/apis"
output: "."
ignore: []
questions:
value: "Please enter any text."
---
# `{{ inputs.value }}/index.ts`
```javascript
import { ContentQuery, EndPoints } from 'src/cms/types'
export type Methods = {
get: {
query?: ContentQuery
resBody: EndPoints['get']['{{ inputs.value }}']
}
}
```
さらにコマンドで生成できるようにします。
package.json
"dog:arr": "npx scaffdog generate api-array",
"dog:obj": "npx scaffdog generate api-object",
aspida で axios の型定義済みメソッドを生成する
インストール
npm i --save @aspida/axios aspida axios
scripts に追加
"dev": "run-p dev:*",
"dev:client": "next --port 9000",
"dev:aspida": "aspida --watch",
"build": "next build",
"start": "next start",
"gen": "run-p gen:*",
"gen:types": "npx microcms-typescript src/cms/schema src/cms/types/response.ts",
"gen:aspida": "aspida",
"dog:arr": "npx scaffdog generate api-array",
"dog:obj": "npx scaffdog generate api-object",
--watch
を付与することで監視をしてくれる。また、npm-run-all
というパッケージで複数の scripts を同時に起動できます。
実際に生成してみる
下記コマンドを叩いて生成されるのを確認しましょう。
$ npm run dev
aspida の watch が起動したことを確認したら、microCMS のスキーマをダウンロードして schema ディレクトリに入れます。次に型定義ファイルを生成します。
$ npm run gen
さらに aspida の各エンドポイントごとのレスポンス、リクエスト定義をします。
$ npm run dog:arr
// or $ npm run dog:obj
これで aspida の watch が走っているので、自動で api
ディレクトリに型定義ファイルが追加されていると思います。
ここまでが手順の解説です。では実際に使ってみます。とは言ってもまずは http クライアントの元を作らなければいけないので、先ほどのディレクトリの utils に index.ts を作ります。
import aspida from "@aspida/axios";
import axios from "axios";
import api from "../apis/$api";
const fetchConfig: Required<Parameters<typeof aspida>>[1] = {
baseURL: `${process.env.API_BASE_URL}`,
headers: { "X-MICROCMS-API-KEY": `${process.env.API_KEY}` },
};
export const client = api(aspida(axios, fetchConfig));
これで準備は全てできたので実際に使うとこのような感じになります。
export type BlogIndexProps = {
info: EndPoints["get"]["info"];
blog: EndPoints["gets"]["blog"];
};
export async function getStaticProps(): Promise<{ props: BlogIndexProps }> {
const info = await client.info.get();
const blog = await client.blog.get({ query: { limit: CONFIG.perPage } });
return {
props: {
info: info.body,
blog: blog.body,
},
};
}
export default IndexPage;
まとめ
いかがでしたでしょうか。生成した型定義をエンドポイントにも使えて、Component 側でも使えるのでめちゃくちゃ便利になりました。
もっと良い方法があれば乗り換えたいですが今のところはこれで満足してます笑
Discussion