Notion ぽい見た目のページをお手軽に公開するにはどうしたらいい？｜react-notion-x

本記事は、株式会社 HRBrain Advent Calendar 2023 の 15 日目の記事です。

https://qiita.com/advent-calendar/2023/hrbrain

## はじめに

こんにちは、@Mozu1206 です。
最近 Notion ぽい見た目のページをよく見ませんか？Notion をそのまま公開できれば色々良いことありそうですよね。

コーディングは最低限で抑えられそうだし、デザイン性も最低限の担保できそうだし、コンテンツの修正を Notion 上でできるなら非エンジニアでも作業しやすそうだし。

つまり、**「早くて安くて美味いを実現できる！？」** と思って調査を進めていきましたが、意外と考えることがあったので記事にまとめます。

## この記事で書くこと

- Notion を独自ドメインで公開できるサービスのうち 1 つを簡単に紹介
- [react-notion-x](https://github.com/NotionX/react-notion-x) というライブラリを用いて Notion ぽい見た目を表現するときの注意点について

メインで書くことは、2 つ目の react-notion-x についてです。

## なぜやることになったかをざっくりと

本記事を書くきっかけとなった Notion ページお手軽作成プロジェクトは、入社数日の人事の一言から始まりました。

「なんかうちの採用ページ、情報少ないっすよね〜。なるべくコストかけずに、まとまった正しい情報を提供できてる状態に持っていきたいんでいいアイデア欲しいっす！！」

その後、なんやかんやあって Notion を自社ドメインで公開できれば満たせる要件にまとまり、調査が始まりました。

## Notion ぽい見た目のページを作る選択肢

Notion ぽい見た目のページを作るには、いくつかの選択肢があります。

### 1. 外部サービスを使う方法

[Super](https://super.so/)などの、Notion を独自ドメインで公開するためのサービスを利用する方法があります。多少コストは掛かりますが、新規でサイトを作るのであればおそらく最も簡単に公開できる方法です。

懸念点は、Notion ではない外部サービスの利用になるためサービス側で不具合が発生したときに、ページが公開できなくなってしまう可能性があることです。弊社では、トラブル対応などを外部サービスに依存したくなかったためこちらの方法は取りませんでした。

:::note
個人でページを作成する場合には一番お手軽だと思いますので興味のある方はぜひ。料金は Personal site で月 16 ドルです。（2023 年 12 月 15 日現在）
:::
### 2. react-notion-x を使う方法

[react-notion-x](https://github.com/NotionX/react-notion-x)は、取得した Notion ページのデータを加工して、 Notion ぽい見た目にいい感じに整形してくれるライブラリです。リポジトリにサンプルコードを多く用意してくれているので、それを参考に必要なコードを持ってくるだけで既存のプロジェクトにも簡単に導入することができます。

:::note
特に、Next.js を使って新しく作る場合は[スターターキット](https://github.com/transitive-bullshit/nextjs-notion-starter-kit)を用意してくれているので、こちらを使うと良さそうです。
:::

## react-notion-x 利用時の Notion からのデータ取得方法について

Notion からデータを取得するライブラリはいくつかありますが、react-notion-x 利用に適したライブラリは下記 2 つです。

1. Notion がオフィシャルに提供している[Notion SDK for JavaScript](https://www.npmjs.com/package/@notionhq/client)を用いる方法
2. react-notion-x が提供している[notion-client](https://www.npmjs.com/package/notion-client)を用いる方法

どちらを使うべきか判断するために、簡単にそれぞれのメリット・デメリットを挙げます。

### Notion SDK for JavaScript を利用する方法のメリット・デメリット

#### メリット

- 公式が提供しているものなので Notion 側の変更がすぐに反映されやすい
- Notion がドキュメントを用意してくれている

#### デメリット

- react-notion-x で整形するには、[notion-compat](https://github.com/NotionX/react-notion-x/tree/master/packages/notion-compat)を使って、react-notion-x が想定している形にデータを加工する必要がある
- notion-compat がまだ Notion SDK for JavaScript に対応しきれていないため、いくつか再現できない Notion のブロックがある

### notion-client を利用する方法のメリット・デメリット

#### メリット

- react-notion-x が用意している、全ての Notion のブロックを再現できる
- react-notion-x の Readme はこちらの方法で書かれているため、すぐに導入しやすい

#### デメリット

- サードパーティーライブラリなので、Notion 側の変更があったときに使えなくなる可能性がある
- 埋め込んだアクセストークンが失効してしまうため、**Notion ページを外部公開設定にしておく必要がある**
  - 詳しくは、下記 Issueをご確認ください。

https://github.com/NotionX/react-notion-x/issues/354

### 比較結果

それぞれを比較した結果、notion-client を使うことにしました。

オフィシャルに提供されている Notion SDK for JavaScript を使いたかったのですが、今回の要望で必須要件だった DB のレイアウト再現がまだ notion-compat で対応されていなかったからです。（2023 年 12 月 15 日現在）
![スクリーンショット 2023-12-07 20.08.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1980374/a253d22e-4932-6cf9-c46b-8fddd68795d9.png)

## Next.js への導入

まず、Notion からデータを取得するメソッドを切り出します。

react-notion-x が用意してくれている[コード例](https://github.com/NotionX/react-notion-x/blob/master/examples/full/lib/notion.ts)がリッチだったので、必要な一部を抜き出しました。これを lib に入れておきます。

```ts
import { NotionAPI } from 'notion-client';
import { ExtendedRecordMap } from 'notion-types';

const notion = new NotionAPI()

export async function getPage(pageId: string): Promise<ExtendedRecordMap> {
  const recordMap = await notion.getPage(pageId)
  return recordMap
}
```

Next.js を使っている場合は、pages 層に下記記述をします。タイトルの追加などもしたかったので、react-notion-x が提供している別のライブラリもいくつか使用しております。適宜インストール or 該当記述を削除してください。

```tsx
import * as React from 'react'
import { GetStaticProps, NextPage } from 'next';
import Head from 'next/head'
import Link from 'next/link'

import * as notion from '@/lib/notion'

import { getPageTitle } from 'notion-utils'
import { NotionRenderer } from 'react-notion-x'
import { ExtendedRecordMap } from 'notion-types';
import { Collection } from 'react-notion-x/build/third-party/collection'
import 'react-notion-x/src/styles.css'

const NotionPage: NextPage<{ recordMap: ExtendedRecordMap }> = ({ recordMap }) => {
  if (!recordMap) {
    return null
  }

  const title = getPageTitle(recordMap)
  const socialDescription = ''
  const socialImage = ''
  return (
    <>
      <Head>
        {socialDescription && (
          <>
            <meta name='description' content={socialDescription} />
            <meta property='og:description' content={socialDescription} />
            <meta name='twitter:description' content={socialDescription} />
          </>
        )}

        {socialImage ? (
          <>
            <meta name='twitter:card' content='summary_large_image' />
            <meta name='twitter:image' content={socialImage} />
            <meta property='og:image' content={socialImage} />
          </>
        ) : (
          <meta name='twitter:card' content='summary' />
        )}

        <title>{title}</title>
        <meta property='og:title' content={title} />
        <meta name='twitter:title' content={title} />
      </Head>
      <NotionRenderer
        recordMap={recordMap}
        fullPage={false}
        darkMode={false}
        components={{
          nextLink: Link,
          Collection,
        }}
      />
    </>
  );
};

export default NotionPage;

export const getStaticProps: GetStaticProps = async () => {
  const recordMap = await notion.getPage(NOTION_PAGE_ID) //任意の Notion ページの ID を入れてください。`https://www.notion.so/XXX` の`XXX`部分（32文字の文字列）が ID です。

  return {
    props: { recordMap }
  };
};
```
### 実行結果
上記を実行することで、任意のNotionページをこんな感じに出力することができます！
![react-notion-x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1980374/b8d3dbff-8c7d-f175-a71d-78c0d93982ea.png)

今回出力したNotionページはこちらです。
Notionをそのまま公開する場合との差分が気になる方は見てみてください！

https://brainy-dash-18f.notion.site/react-notion-x-099963fd29974be39d813f3f1bc73cae

::: note warn
もしページが取得できなければ、Notion がプライベートページになっている可能性があるので、[Notion のドキュメント](https://www.notion.so/ja-jp/help/public-pages-and-web-publishing)をご確認の上、ページを Web に公開してください。
::: 
::: note
Web に公開したくない場合は、notion-client では対応できないため、notion-compat を使う方法になります。[こちらのコード例](https://github.com/NotionX/react-notion-x/blob/master/examples/full/lib/notion.ts)を参考に`useOfficialNotionAPI`を`true`にした上で`process.env.NOTION_TOKEN`にトークンを入れてください。
:::
## まとめ

調べる中で色々悩ましい部分も出てきましたが、1 ページまるまる作るにしては相当コストを抑えてページの公開までいけました。
後はビルドも自動で行ってくれるようにすれば、もっと良くなりそうですね。

アクセストークンの話などは日本語で調べてもあまり情報が出てこなかったので、今後どなたかの開発の手助けとなれば幸いです！
興味がある方はぜひやってみてください！

## PR
HRBrainでは一緒に働く仲間を募集しています！

現在の弊社採用サイトはこちらの技術で作られているので、興味のある方はぜひご確認ください！

https://www.hrbrain.co.jp/recruit