Closed4

Next.jsを10.2へアップデートしたらビルドできなくなったので調査

taka_htaka_h

ログを遡ってみると、以下のようなエラーが吐かれていた。

21:49:41.055  	Failed to compile.
21:49:41.055  	./src/lib/blog.ts:56:33
21:49:41.055  	Type error: Property 'draftKey' does not exist on type 'string | false | object'.
21:49:41.055  	  Property 'draftKey' does not exist on type 'string'.
21:49:41.055  	  54 | 
21:49:41.055  	  55 |   const id = toStringId(params.id);
21:49:41.055  	> 56 |   const draftKey = previewData?.draftKey
21:49:41.055  	     |                                 ^
21:49:41.055  	  57 |     ? { draftKey: previewData.draftKey }
21:49:41.055  	  58 |     : {};
21:49:41.056  	  59 | 

ビルドが失敗している原因はブログ記事を表示するロジックの部分だった。

ブログ記事を表示する際、それがプレビューモード によるものなのかどうかを、getStaticProps()で受け取れる引数contextpreviewDataが含まれているかどうかによって判定しているのだが、その部分で型エラーが発生していた。

今回ブログ投稿のロジックは一切触っていないはずなのにおかしいな…と思っていたら、Next.jsのgetStaticProps()contextの型定義が3週間前に変更されていた。

next/types/index.d.ts
+ export type PreviewData = string | false | object | undefined

  export type GetStaticPropsContext<Q extends ParsedUrlQuery = ParsedUrlQuery> = {
    params?: Q
    preview?: boolean
-   previewData?: any
+   previewData?: PreviewData
    locale?: string
    locales?: string[]
    defaultLocale?: string
  }

getStaticProps()で受け取れる引数contextの型は、上記のような感じで定義されている。

PreviewDataって、これまではany型だったのか…知らなかった。

taka_htaka_h

GetStaticPropsContextpreviewDataは、microCMSなどのヘッドレスCMSと連携してプレビューモード を実装する時に使っていて、これがanyからstring | false | object | undefinedに変わったことでコンパイルに失敗してしまっていた、ので対処する。

当初は、こんな感じでpreviewDataからdraftKeyを取得していた。

src/lib/blog.ts (修正前)
export const getPostData = async (
  context: GetStaticPropsContext<ParsedUrlQuery>,
) => {
const { params, previewData } = context;

const draftKey = previewData?.draftKey 
  ? { draftKey: previewData.draftKey }
  : {};
...

GetStaticPropsContextが持つpreviewDataanyだった時はこれでも問題はなかったが、string | false | object | undefinedに変更された後だと、

プロパティ 'draftKey' は型 'string | false | object' に存在しません

というエラーが表示される。

んー?でもpreviewDataobjectは含まれているし、optional chaining使っているし、previewDataObjectと見做してうまく推論してくれてもいいのになーと思っていたが、調べてみると、TypeScriptのObjectobjectは違うものであるらしい…

https://qiita.com/ma2saka/items/eb2d26236c20afb7f764

https://qiita.com/suin/items/928642e48959e864a74e

なので、最終的にType Guardを追加して解決した。

src/lib/blog.ts (修正後)
// 与えられた引数に`draftKey`値が存在するかチェックする
+ const isDraft = (item: any): item is { draftKey: string } =>
+   !!(item?.draftKey && typeof item.draftKey === 'string');
  ...
  export const getPostData = async (
    context: GetStaticPropsContext<ParsedUrlQuery>,
  ) => {
    const { params, previewData } = context;

-   const draftKey = previewData?.draftKey 
+   const draftKey = isDraft(previewData)
      ? { draftKey: previewData.draftKey }
      : {};
  ...

これでエラーが解消された。

taka_htaka_h

反省点

  1. パッケージをアップデートしたら、デプロイ前にちゃんと動作確認する。
  2. パッケージの型定義には最低限目を通し、型チェックはなるべく厳密に行う。
このスクラップは2021/05/12にクローズされました