ポートフォリオとして制作中の自作アプリ「Hubpin」(Next.js 16 + Supabase)で、
同じ日に2回、同じ形の型エラーに当たりました。
型エラーが出ると、まず「今のコードのどこが壊れているのか」を探しにいきます。
ところがこの2件は、探しても壊れている場所が見つかりませんでした。動きます。
それでも赤線が出る。
最初はどちらも、型を黙らせる方向に手が動きました。
書き終わってから見直すと、エラーが指していたのは別のところでした。
TypeScript は「proxy が守っている」ことを知りません
/dashboard は proxy で保護しています。
Next.js 16 で middleware から改名されたファイルで、リクエストがページに届く前に走ります。
ここを通っている時点でログイン済みなので、中では素直にこう書けるはずでした。
const { data } = await supabase.auth.getClaims()
const email = data.claims.email // ← ここが型エラー
data が null になりうるので怒られます。
でも実行時に null になることはありません。その手前で proxy が弾いているからです。
TypeScript はそのことを知りません。
そして、知らないのが正しいと後から分かりました。
proxy がどのパスで動くかは matcher という設定が決めていて、これは1行の配列です。
この1行を書き換えたら、さっきのコードは本当に落ちます。
実際、同じ工程で matcher を絞りすぎてセッション更新が止まり、
1時間後にログアウトされる事故を踏んでいます。
守られている前提は、自分で思っているほど固定されていませんでした。
型が指していたのは、proxy が外れた未来のほう。
今このコードが動くかどうかは、はじめから見ていませんでした。
?. を付ける場所も間違えていました
最初はこう書いて逃げようとしました。
const email = (await supabase.auth.getClaims())?.data.claims.email
これは守る場所が違います。getClaims() は必ず { data, error } の形で返ってくるので、この位置は null になりません。null になるのは data のほうです。
const email = data?.claims.email
同じ記号でも、置く場所を1つずらすと何も守らなくなります。
このときは「エラーが消えたから合っている」で通しかけました。
戻り値を受け取る人がいませんでした
ログインフォームは3段階に分けて作っていました。
まず HTML のフォームと、中身が空の Server Action。
次に Supabase の認証を繋ぐ。最後にエラーを画面に出す。
型エラーが出たのは、2段階目を終えたところでした。
失敗したときにエラーメッセージを返す形に変えたら、<form> に渡せなくなりました。
Type '(formData: FormData) => Promise<{ message: string; }>'
is not assignable to type '(formData) => void | Promise<void>'
<form action={...}> が受け取れるのは、文字列か、戻り値を持たない関数だけです。{ message: string } を返す関数は、そこに入りません。
ここでも型を通す方向に手が動きました。戻り値を消せばエラーは消えます。
ただ、消したらエラーメッセージが画面に出せません。
返した値をどこかで受け取って表示する仕組みを、まだ作っていませんでした。
3段階目でやる予定だった useActionState を、そこで入れました。
戻り値を受け取って表示する経路ができます。
すると、Server Action 側を1文字も書き換えていないのにエラーが消えました。
TypeScript が言っていたのは「その戻り値、受け取る人がいませんよ」でした。
足りなかったのは型の知識ではなく、受け取り手のほう。
2件に共通していたもの
並べてみると、指していたものが違うだけで形は同じでした。
| エラーが指していたもの | |
|---|---|
proxy と getClaims |
将来の壊れ方(proxy を外したら落ちる) |
| Server Action の戻り値 | 足りない設計(戻り値の行き先がない) |
どちらも「今のコードの誤り」ではありませんでした。
そして2件とも、最初に手が動いたのは黙らせる方向でした。
?. を足す。戻り値を消す。どちらも赤線は消えます。
消えたあとに残るのは、proxy を外したら落ちるコードと、
エラーを画面に出せないフォームです。
おわりに
2件は別々の手順で出ています。
ログインフォームを作る手順が目安1.0時間に対して実績4.8時間、
proxy を作る手順が2.2時間でした。
丸写しなら、どちらももっと短く終わっていたと思います。
時間を使ったのは、赤線を消す前に「これは何を言っているのか」で止まった部分でした。
型エラーを「今の間違い」だと思って読むと、消す方向に手が動きます。
「将来こう壊れる」「この設計が足りない」と読めると、直し方が変わりました。
自分は2回とも、最初は消す方向に動いています。
次は、そもそもエラーが出ないまま間違っているケースの話を書きます。
今回のように赤線が出るのは、まだ親切なほうでした。
