Open5

Next.js(App Router) & View Transition API メモ

KeiKei

まず、View Transition API の仕様として:

  • startViewTransition に渡したコールバック(updateCallback)は非同期のタイミングで実行される
  • updateCallback が実行されたタイミングで偏移前の画面としてスクリーンショットが保存され、updateCallback から返された Promise が resolve されたタイミングで遷移後画面が完成しているとみなされる。なので、updateCallback はページ更新前に実行させる必要がある。
  • ページ更新処理は必ず updateCallback の関数内で行わなければならない、という訳ではない
KeiKei

ページの更新は updateCallback が実行され、返された Promise が resolve するまでの間に終わればよい。
なので Promise.withResolvers() 等から resolve 関数を抜き出した Promise だけを updateCallback 内で返し、ページの更新ができたら resolve() するだけで問題なく実行される。

KeiKei

updateCallback の実行タイミングについて

前述の通り実行は非同期のタイミングだが、ページ更新処理は Next.js が管理しているし同期的に行われる(っぽい)為、とりあえず無理矢理ページ更新が完了する前に割り込まなければならない。
そこで Suspense である。

ページコンポーネントがマウント(≠レンダー)した時に startViewTransition を実行して一度サスペンドさせて updateCallback の実行タイミングを作り、サスペンドされたページコンポーネントのマウントが再試行された時に updateCallback に渡した Promise を resolve する。
結構ややこしい。

KeiKei

しかし、このやり方だけではリンクでのページ遷移では良くてもブラウザの back/forward でのページ移動時に期待通り動作してもらえない。
一応対処法としては Next.js が監視する popstate イベントのバブリングを止めることで乗っ取り、そのイベントハンドラで router.replace() を使うことで期待通りの動作をさせることができる。
router.back()/forward() では期待通りの動作をしない。

ただ、たぶんこれはこれでどっかに悪影響がありそう。知らんけど。

KeiKei

Q: useTransition/startTransition は必要?
A: Next.js の router の内部で既に使われているのでこちら側で使う必要はないが、isPending が欲しければ router.push() や router.replace() をラップすることで取得できる。