✅ 本記事は 前編 の続きで、技術的内容が多めなのでここで投稿しています。
僕は2022年7月15日に Notion と Next.js を使った Udemyのコース を公開しました。
コース 👉 【NotionをCMSに】NotionAPI + Next.js + TypeScript でブログ開発〜デプロイまで
そのコース制作の中で発生した問題や、ポイント、伝えたいことなどを記事にしてみました。
この後編ではコース制作中の技術的な内容をお伝えします。苦戦したところやポイントなどです。
苦戦したところ – Notion API バージョン
一番苦戦したところは、制作中に大幅にAPIの使用が変更になったことです。
Notionはメジャーなアップデートとして 2022-02-22
というバージョンを出していて、僕はそのバージョンを前提に制作していました。
コースの内容をまとめた資料も作成し、実際に撮影をし始め、NotionAPIを使ってfetchをかけるところで異変に気付きました。
これまでとれていたプロパティが全くとれなくなってしまったのです。
元々の取得結果(ver 2022-02-22 @notionhq/client@1.0.4)
異変に気づいた時の取得結果(ver 2022-06-28 @notionhq/client@2.0.0)
上記2つ目の画像が新しいバージョンでの結果です。
propertiesを見ると、取得できている各プロパティには id の値しか取れていないことがわかります。
原因としては、NotionのJavascriptクライアントライブラリとして @notionhq/client
を使用していただのですが、そのバージョンが直前にアップデートされていました。
具体的には v1.0.4 から、APIの大幅変更を意味するメジャーアップデートにより v2.0.0 となっていたのです。
調べると v2.0.0 では Notion APIバージョン 2022-06-28
に対応しているようでした。
これまでのバージョンではNotionデータベースIDを渡して取得できる結果には、各レコードの全プロパティが含まれていました。
しかしながら、 2022-06-28
ではそのプロパティがとれてこないので、プロパティを取得するにはさらにfetchをかける必要があります。
コースとしては直近だったので、@notionhq/client はバージョン指定でインストールすることにしました。
yarn add -D @notionhq/client@1.0.4
ちなみにNotionコースを公開後、ツイッターでのご縁をきっかけに、Notion座談会(@NotionZadankai、運営:田村さん @RieTamura36 )に出演させていただくことになったのですが、
その中でNotion APIにも詳しい @hkob さんがこのプロパティが取得できない件に関してツイートをされていました。
見ると、みなさんもこの件には苦労しているようですね。
Notion APIを使用する場合、大体においてはプログラミング言語ごとにクライアントライブラリを使用すると思います。
そのライブラリ内で合わせてプロパティも取得して返す処理を作れれば、ライブラリを使う側としては以前までのバージョンと同じように使うことができると思います。
ライブラリ開発者の方たちもそれには気づいていると思うので、今後の実装に期待ですね!
苦戦したところ – デプロイ後だけ表示されるエラー
yarn dev
や yarn start
でサーバー起動してもエラーにはならないのですが、本番環境の場合だけ、下記画像のエラーが発生しました。
結構調べましたが、同じような内容の報告はあっても特定の原因とかは見つからず、エラーを報告しているだけでした。
リンクを飛ぶとHydration に関する記載もあるので、HTMLのネストの記載順序なども見直しましたが、特定できませんでした。
原因の直接の解決ではありませんが、
調べている中で、Reactのバージョンについて記載があったので、ダウングレードすると消えました。
同様なエラーが発生した方は、ダウングレードを試してみてください。
yarn upgrade react@17.0.2
yarn upgrade react-dom@17.0.2
fallback
動的ページにおいて getStaticPaths
を使った場合に fallback
を考える必要が出てきます。
fallback=false にしている場合
これは getStaticPaths
で返すパスのみが有効な build の対象となり、つまり新しいパスとしてのページを作ってアクセスしても404が返されることになります。表示したければ再度 build し直す必要があります。
fallback=true の場合
この場合に新しいページへのパスへアクセスした場合404にはなりません。そうすると何かを表示しなければならないのでNext.jsはデータが埋め込まれていないレンダリングページを表示します。
この初回のリクエストの背後では次回以降のデータが生成されているので、次回以降は問題ありません。
しかし初回にデータがないにもかかわらずレンダリングされることが問題です。
もしレンダリング直前にfallbackバージョンのレンダリングなのかを判定できないと、データがないことによるエラーが発生する可能性が出てきます。
事前に判定するには下記のようにrouter.isFallbackなどで処理する必要があります。
const router = useRouter()
if (router.isFallback) {
return <div>Loading...</div>
}
fallback=”blocking” の場合
blockingでは fallback=true
の問題点であるデータがない場合のレンダリングをブロックしてくれます。つまり新しいページのパスへアクセスした初回はSSRのように振る舞ってくれるので、上記のような条件分岐を記載する必要がなくなります。
この機能はとても便利ですし、記事のように定期的にページが追加される場合にはよく機能します。コースでもこちらの blocking に設定しています。
フォルダ構成
フォルダ構成については、基本的な pages と components に加え、型定義を管理する types フォルダ、さらに汎用関数や、その他全般というような感じで utils フォルダも用意して使用しました。
めちゃくちゃ大きいプロジェクトというわけでもないので、lib フォルダなどは作らず、 utils
で管理することにしました。
フォルダ構成については、基本的な pages と components に加え、型定義を管理する types フォルダ、さらに汎用関数や、その他全般というような感じで utils フォルダも用意して使用しました。
めちゃくちゃ大きいプロジェクトというわけでもないので、 lib フォルダなどは作らず、 utils で管理することにしました。
# プロジェクト直下のフォルダとファイル
プロジェクト
├── README.md
├── components
├── next-env.d.ts
├── next.config.js
├── node_modules
├── package.json
├── pages
├── postcss.config.js
├── public
├── site.config.ts
├── styles
├── tailwind.config.js
├── tsconfig.json
├── types
├── utils
└── yarn.lock
こちらの記事もフォルダ構成ができるだけ曖昧にならないように参考にさせていただきました。
https://zenn.dev/mongolyy/articles/01f0a4375edb2e
imgタグとImageコンポーネント
個人的にNextjsのImageコンポーネントを避けて(単にサイズを指定したくなかっただけ。。💧)、通常のimgタグを使ったりしてました。
ただVercelへデプロイすることと、パフォーマンスの観点から、コースではしっかりと公式の推奨通りImageコンポーネント使うことにしました。
Imageコンポーネントでは width
や height
を指定するか、そうでない場合は layout='fill'
を指定しなければいけません。サイズはデプロイ後にvercelとかがいい感じに最適化してくれるようなので、とりあえずはアスペクト比として指定していればOKだと思います。
https://nextjs.org/docs/api-reference/next/image
https://zenn.dev/nbr41to/articles/365e8105efa448
https://zenn.dev/yukishinonome/articles/da315b1be98a9c
もう一点重要なポイントは、今回のNotionAPIでは、Notionサービス内でアップロードなどしている画像はドメインがnotionになっています。外部ドメインへのリンクの場合は next.config.js へ追記しないとエラーになります。
https://nextjs.org/docs/api-reference/next/image#domains
images: {
domains: ["www.notion.so"],
},
switch-case文
完全カスタマイズ用に、いくつかブロックについても実装しました。その際、ブロックの種類ごとに生成されるHTMLタグが変わるようにするため、分岐する必要がありました。
仮の部分で条件も3つほどだったので if文でもよかったのですが、さらに分岐する場合に備えて switch-case文を使用しました。
TypeScript の型定義
TypeScriptの型定義では基本的に使い方にとどめています。
まず型宣言の時に interface派とtype派、またはハイブリッド派がいる(?)と思います。
僕自身、特にこだわりはありませんが、コースの中では typeのみの仕様にしました。
ほとんどはObject型なので、interfaceでも大丈夫ですし、拡張も簡単ですが、
typeでも &
で繋げれば拡張できる(インターセクション)ことと、Object以外も型定義できることを踏まえ、そのようにしました。
好みにもよると思うので、下記を参考に、多くの部分については interface へ書き換えることができると思います。
// typeの拡張の書き方
export type TagProps = IndexProps & {
tag: string
};
// interfaceの拡張の書き方
export interface TagProps extends IndexProps {
tag: string
};
全体的には直接的に定義する中で、一部Recordを使った記載も実装しました。
型定義については下記も大変わかりやすくまとめられています。
https://qiita.com/k-penguin-sato/items/e2791d7a57e96f6144e5
終わりに
今回のUdemyコースについての制作経緯の記事でした。
技術よりではないNotion+Next.jsコース制作の裏話①前編もよければどうぞ。
ご興味があればコースもぜひ。