NotionのページをNext.js(React)で取得して表示するまで

commits5 min read読了の目安(約4800字

公式APIで取得したページを、Reactで表示する手順。

[2021-6-6 17:40] タイトル取得を追加しました。

完成品

すぐデプロイできます。

Deploy with Vercel

https://github.com/sasigume/notion-to-next-single-page

準備: Integrationをページに招待する

https://www.notion.com/my-integrations

まず新しいIntegrationを作る。

今回は試しに 「My Portfolio」 というページを作った。Integrationをページに招待する。

後になって気づいたんですが、ページをネストしている場合、親ページを招待してください。
どうやらブロックは個別に取得できるんですが、「ページ」のデータはネスト元の権限がないと取得できないようです。

この場合、URLのMy-Portfolio-XXXXXXXXXXXXXXXXXXX部分がページID である。

Next.jsのセットアップ

env

NOTION_TOKEN=(NOTIONのAPIトークン)
NOTION_PAGE_ID=(さっきのページID)

config

next.config.js
module.exports = {
  env: {
    NOTION_TOKEN: process.env.NOTION_TOKEN,
    NOTION_PAGE_ID: process.env.NOTION_PAGE_ID,
  },
};

型をつける

公式SDKが型を提供してくれるようになったら、ここは不要です。

models/index.d.ts
import { ReactNode } from 'react';
import { NotionPage } from './notion';

export interface LayoutProps {
  children?: ReactNode;
  notionPage: NotionPage;
}

models/notion/index.d.ts
import { Blocks } from './blocks';
import { Page } from './page';

export interface NotionPage {
  page: Page;
  blocks: Blocks;

  // added because page data is too nested
  id: string;
  title: string;
}

models/notion/page.d.ts
// https://api.notion.com/v1/pages/page_id

export interface Page {
  id: string;
  properties: {
    title: {
      id: string;
      type: string;
      title: [
        {
          plain_text: string;
        }
      ];
    };
  };
}

models/notion/blocks.d.ts
// https://api.notion.com/v1/blocks/block_id/children

export interface Blocks {
  object: string;
  results: Result[];
  next_cursor: any;
  has_more: boolean;
}

Resultの型書いてないですが、描画ライブラリに渡すだけなのでここは大丈夫。

ページとブロックを取得

https://developers.notion.com/reference/get-block-children

ページを「ブロック」として見れば、その中身は当然 「ブロックの子」 である。ということで、ページの中身を取得するならblocks/block_id/childrenを使わないといけない。

https://developers.notion.com/reference/get-page

なお、タイトルなどのメタデータはpages/page_idエンドポイントを使えばいい。

クライアント

公式のクライアントを使う。

yarn add @notionhq/client
lib/notion/client.ts
const { Client } = require('@notionhq/client');
const notion = new Client({
  auth: process.env.NOTION_TOKEN,
});

export default notion;

記事の取得

lib/notion/get-page.ts
import { NotionPage } from 'models/notion';
import notion from './client';

export async function getPage(id: string): Promise<NotionPage> {
  const page = (await notion.pages.retrieve({
    page_id: id,
  })) as NotionPage['page'];
  const blocks = (await notion.blocks.children.list({
    block_id: id,

    // Max is 100
    page_size: 100,
  })) as NotionPage['blocks'];

  return {
    page,
    blocks,
    id: page.id,
    title: page.properties.title.title[0].plain_text,
  };
}

レイアウトを作って描画する

公式APIに対応した描画用ライブラリを探した結果、今回は@9gustin/react-notion-renderを使うことにした。

yarn add @9gustin/react-notion-render
components/layout.tsx
import Head from 'next/head';
import { render } from '@9gustin/react-notion-render';
import { LayoutProps } from 'models';

const Layout = ({ notionPage }: LayoutProps) => (
  <div className="container mx-auto max-w-xl flex flex-col gap-4 py-8 px-3">
    <Head>
      <title>{(notionPage && notionPage.title) ?? 'No title'}</title>
      <meta charSet="utf-8" />
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <main area-label="Main content">
      <article>
        <h1 className="text-4xl font-bold mt-6">
          {(notionPage && notionPage.title) ?? 'No title'}
        </h1>
        {notionPage && render(notionPage.blocks.results, true)}
      </article>
    </main>
    <hr className="my-8" />
    <footer area-label="Footer">
      <span>Made with Notion</span>
    </footer>
  </div>
);

export default Layout;

ページを作成

あとはGetStaticPropsを使えばいい。

index.ts
import Layout from '../components/Layout';
import { LayoutProps } from 'models';
import { getPage } from '@/lib/notion/get-page';
import { GetStaticProps } from 'next';
const IndexPage = (props: LayoutProps) => <Layout {...props}></Layout>;

export default IndexPage;

export const getStaticProps: GetStaticProps<LayoutProps> = async () => {
  const id = process.env.NOTION_PAGE_ID;
  if (id) {
    const notionPage = await getPage(id);
    return {
      props: {
        notionPage,
      },
      revalidate: 30,
    };
  } else {
    return {
      notFound: true,
    };
  }
};

最後に、お好みでCSSを付ければ完成。

参考

https://zenn.dev/5t111111/articles/785fec8d21d82b

https://dev.to/geobrodas/how-to-use-notion-api-with-nextjs-5940