はじめまして。プログラミング学習中の駆け出しフロントエンドエンジニアです。
今回は、NuxtとSupabaseを用いたWebアプリケーション開発中に直面した、あるエラーとその解決プロセスについてご紹介します。もし同様の状況でお困りの方がいらっしゃれば、こちらの投稿が問題解決のヒントとなれば幸いです。
開発中に遭遇した406 Not Acceptableエラー
私が開発しているアプリケーション(https://gigdig.vercel.app/)では、最初にユーザーが認証(Google or 匿名)を行い、その後にユーザー名を登録するというフローになっています。
この認証が成功し、ユーザー名登録画面へ遷移した際、ブラウザの開発者ツールのコンソールに以下のエラーメッセージが表示されました。
GET https://your-supabase-project.supabase.co/rest/v1/users?select=username&user_id=eq.xxxxxxxx-xxxx-... 406 (Not Acceptable)
publicのusersテーブルからデータを取得できないということです。
このとき、エラーレスポンスには以下のような詳細情報も含まれていました。
{
"code": "PGRST116",
"details": "The result contains 0 rows",
"hint": null,
"message": "JSON object requested, multiple (or no) rows returned"
}
アプリケーションの機能自体は動作していましたが、コンソールにエラーが表示され続けることは、開発を進める上で看過できない問題でした。
初期の調査と試行錯誤
エラーメッセージの「The result contains 0 rows」(結果が0行)という部分から、データベースに該当データが存在しないことが示唆されました。
認証を終えたばかりのユーザーは、まだpublicの users
テーブルにレコードが登録されていないため、データが0件であるのはこの時点では想定内の挙動です。
このエラーが表示されたのは、ユーザー名登録画面(register-username.vue
)で、既存のユーザー名を取得しようとする以下のコード部分でした(ユーザー名変更機能を追加することを想定してこちらの処理を実装しております)。
<script setup>
const supabase = useSupabaseClient();
const user = useSupabaseUser();
const { data: existingUserName, error } = await useAsyncData('user-name', async () => {
if (!user.value) return null;
try {
const { data, error } = await supabase
.from('users')
.select('username')
.eq('user_id', user.value.id)
.single(); //この部分
return data?.username || null;
} catch (err) {
// ...
}
});
</script>
調べたところ、この.single() メソッドは「データがちょうど1件だけ存在すること」を前提としていて、データが存在しなかったり、複数件あったりするとエラーになることがわかりました。
調査を進めたところ、このようなケースでは、.maybeSingle()
を使うとよいということがわかりました。
// 変更後
.maybeSingle(); // これでエラーは回避できるはず…
しかし、結果は変わりませんでした。コンソールには引き続き406エラーが表示され、ネットワークタブを確認すると、同じリクエストが複数回(具体的には3回)送信されており、最初の2回が406エラー、最後の1回だけ200 OKで成功していました。
真因の特定:middlewareとデータ同期のタイムラグ
.maybeSingle()
を使用してもエラーが解消されなかったことから、このエラーがコンポーネントのロードよりも前の段階で発生している可能性を検討し始めました。
筆者のアプリケーションには、認証状態をチェックし、ユーザー名が未登録のユーザーを登録画面へリダイレクトするためのロジックが middleware/auth.ts
に記述されておりました。ユーザー名登録画面でエラーが発生しているのだから、当然問題のある記述はregister-username.vue
の中にあるのだろうと決めつけてしまったせいで数時間を浪費してしまいました(middlewareの処理の詳細を忘れていたということもある)。
// middleware/auth.ts (エラーが発生していた部分)
export default defineNuxtRouteMiddleware(async (to) => {
const user = useSupabaseUser();
const client = useSupabaseClient();
try {
const { data: userData, error } = await client
.from('users')
.select('username')
.eq('user_id', user.value.id)
.single(); // ★ここでも single() を使用していたことを発見!
if (error || !userData?.username) {
return navigateTo('/register-username');
}
} catch (error) {
console.error('Error checking user data:', error);
return navigateTo('/register-username');
}
});
これでエラーが発生する理由が明確になりました。
-
匿名認証の完了
login.vue
で匿名認証が成功すると、auth.users
テーブルにユーザーレコードが作成されます。 -
ミドルウェアの実行
Nuxtのルーティングに伴い、ミドルウェアが即座に実行される。 -
データ同期のわずかな遅延
public.users
テーブルに情報が同期されるまでにごくわずかな時間差がある(少なくともSSRでサーバーサイドで実行された際にはまだデータが存在していないと思われる) -
.single()
によるエラー
このタイミングで.single()
が実行されると、まだpublic.users
テーブルにデータが存在していないことにより406エラーが返される。 -
リトライによる最終的な成功
Supabaseクライアント内部のfetch-retry.js
がリトライし、その間に同期が完了し成功する。
つまり、エラーの原因は「同期の遅延」および「.single()
の厳格さ」によるものでした。
解決策:データクエリ時の .maybeSingle()
の使用
原因が特定されたため、vueファイルと同様に、以下のように .single()
を .maybeSingle()
に変更しました。
// middleware/auth.ts (修正後)
export default defineNuxtRouteMiddleware(async (to) => {
const user = useSupabaseUser();
const client = useSupabaseClient();
try {
const { data: userData } = await client
.from('users')
.select('username')
.eq('user_id', user.value.id)
.maybeSingle(); // ここを maybeSingle() に変更!
if (!userData?.username) {
return navigateTo('/register-username');
}
} catch (err) {
console.error('Error checking user data in middleware:', err);
return navigateTo('/register-username');
}
});
この修正を適用したところ、コンソールから406 Not Acceptableエラーが一切表示されなくなりました。
まとめ
今回の経験から得られた学びをまとめます。
- 認証直後は、カスタムテーブルにユーザーレコードがまだ存在しない可能性を考慮すること。
- データが存在しない可能性があるクエリには
.single()
ではなく.maybeSingle()
を使う。 - middlewareが行う処理に注意を払う(そのタイミングはもちろんだし、そもそも忘れてはならない)。
今回のエラーを通じて、Supabaseでのデータ取得方法(.single()
と .maybeSingle()
の違い)について理解が深まっただけでなく、SSR(サーバーサイドレンダリング)では処理が「いつ」「どこで」実行されるのかに注意する必要があるということも強く実感しました。
画面が動いているように見えても、その裏で非同期処理やデータの取得がうまくいっていない場合もあるため、目に見えない処理にも気を配ることの大切さを学びました。
同じような状況で悩んでいる方にとって、本記事が少しでもヒントになればうれしいです。
最後までお読みいただき、ありがとうございました。
参考
以下、エラー解決にあたって参考になったページです。