📖

Next.js + Redoc で1秒以内でレスポンスする API 仕様書をつくった

2023/10/23に公開

こんにちは zane です。

Collections というオープンソースのヘッドレス CMS をつくっています。今回パブリックな API 仕様書が必要になり Next.js(Nextra) + Redoc という組み合わせを試したら、なかなか良かったので手順としてまとめてみました。

OpenAPI(Swagger)などを使った API 仕様書はだいたいどのプロジェクトでも作成しますが、それを公開するとなると、機会はあまり多くはないかもしれません。が、大きく技術トレンドが変わる分野でもないので、覚えておくといずれ役に立つかもしれません!

完成品

api

公開されているページはこちら

コードはすべて GitHub で公開していますので、参考にしてみてください。

Redoc とは?

OpenAPI 仕様で記述された yaml ファイルから HTML ファイルを生成し、静的なドキュメントにしてくれるツールで、オープンソースとしても公開されています。

https://github.com/Redocly/redoc

Collections は、ドキュメント全体を Nextra (Next.jsベースの静的サイトジェネレーター) で作っていますので、これに統合してレンダリングさせています。

Nextra については、以前記事を書いていますので、参考にしてみてください。
https://zenn.dev/zane/articles/0cf5b21d49125b

React のコンポーネントとして Redoc をインストールする

それでは早速、公式のステップ に沿って進めていきましょう。まずは @redocly/cli と関連する依存関係をインストールします。

npm i react react-dom mobx styled-components core-js

yaml ファイルを準備

次に、OpenAPI 仕様をもとに yaml ファイルを作成します。
Collections の API 仕様書を貼っておきますので、参考してみてください。

https://github.com/collectionscms/collections/blob/main/docs/data/api.yaml

VSCode を使っている方は OpenAPI (Swagger) Editor を入れておくと、文法エラーなどワーニングを出してくれるのでサクサク作業が進められます。

https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi

コンポーネントに追加する

ファイルの準備ができたらページに Redoc のコンポーネントを埋めると、自動で HTML を生成して出力してくれます!

import { RedocStandalone } from 'redoc';

<RedocStandalone specUrl="url/to/your/spec"/>

ただし specUrlローカルファイルを参照させることはできません。これは GitHub のイシューでもやりとりされていますが、同一オリジンポリシーとセキュリティ上の都合によるものです。
https://github.com/Redocly/redoc/issues/149#issuecomment-359285569

この問題の対応策として S3 などに yaml を退避させる選択肢もありますが、手間は増えるしファイルをアップロードするために CI のジョブを追加したりと、あまりやりたくはありません。。。

Next.js の API で配信し SSG 化する

そのため Next.js のAPIを使って yaml をレスポンスさせ、それを渡すことでこの問題を解決しました。

openapi.ts
export default function handler(_req: NextApiRequest, res: NextApiResponse<ResponseData>) {
  const fullPath = path.join(dataDirectory, 'api.yaml');
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  res.status(200).json(YAML.parse(fileContents));
}

まぁ、これだけでも良かったのですが、都度リクエストが走ることでレンダリングが結構遅い。。。そのため、もう一工夫が必要です。Redoc にはspecという URL の代わりにオブジェクトを渡すプロパティもあるので

export const getStaticProps = ({ params }) => {
  return fetch(`https://collections.dev/api/openapi`)
    .then((res) => res.json())
    .then((repo) => ({
      props: {
        ssg: repo,
      },
    }));
};

export const Redoc = () => {
  const data = useData();
  return <RedocStandalone spec={data} />;
};

このように SSG で事前レンダリングしてオブジェクトを渡すことで、見事レスポンス面もクリアできました!

パフォーマンス

$ curl 'https://collections.dev/ja/reference/api/' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Connection: keep-alive' --compressed -o /dev/null -w  "%{time_starttransfer}\n" -s

0.239587
0.249859
0.255718
0.249946
0.253883

と、だいたい 300ms 以内でレスポンスされます。

speed

First Contentful Paint も1秒くらい。これ以上改善しても SEO が求められるページでもないので、合格点を出してもいいのではないでしょうか!

最後に

ドラッグ&ドロップで WordPress を API 化するオープンソースのヘッドレス CMS 【 Collections 】をつくっています!

Markdown(with GFM) やダークモードなど、書き心地の良さも持ち味です。 Live Demo も公開していますので、ぜひ気軽に触ってみてください 🙌🙌

それでは

Collections

Discussion