こんにちは、編集長Kです。
SPAで認証周りを作っていると、画面のチラつき、気になりませんか?
ページをリロードした瞬間や、言語を切り替えた時。 一瞬だけ「未認証」のヘッダーが出て、すぐに直るアレです。
せっかくのSPAなのに、少しカッコ悪いですよね。
Cookieを使ったセキュアな認証(Sanctumなど)では、よく起きる現象です。 フロント側から「ログイン中か?」がすぐに分からないのが原因です。
そこで今回は、「Auth Hint(認証ヒント)」という技術を使います。 超有名SNSサービスなどでも採用しているベストプラクティスです。
この記事で解説する手順の全体像は、以下の通りです。
- 手順①:Auth Hint用の定数を準備する
- 手順②:Axiosのインターセプターを改修する
- 手順③:AuthContextをSWRで劇的進化させる
【前提条件】
- フロントエンド:Next.js (App Router)
- バックエンド:Laravel (SanctumによるCookie認証)
- データフェッチ:SWR導入済み
それでは、さっそく実装していきましょう!
手順①:Auth Hint用の定数を準備する
まずは、マジックストリング(文字列の直書き)を防ぎます。 タイポによるバグをなくすための、現場の鉄則ですね。
定数管理用のファイルに、下記を追加してください。 (例:lib/constants.ts)
// lib/constants.ts
export const AUTH_HINT_KEY = 'auth_hint';これでOKです。 今後キー名が変わっても、ここを直すだけで済みます。
手順②:Axiosのインターセプターを改修する
続いて、API通信の要であるAxiosを修正します。
セッション切れなどで、バックエンドから401エラーが返ってきたとします。 その時、フロントエンドの「ヒント」も確実に消す仕組みが必要です。
Axiosの設定ファイル(lib/axios.ts など)に、下記を追記してください。
import { AUTH_HINT_KEY } from './constants';
// レスポンスのエラーハンドリング部分
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const status = error.response.status;
// 認証エラー (401, 419) のハンドリング
if (status === 401 || status === 419) {
// サーバー側で認証切れの場合、ヒントも同期的に削除
if (typeof window !== 'undefined') {
localStorage.removeItem(AUTH_HINT_KEY);
}
// (※既存のリダイレクト処理などが続きます)
}
}
return Promise.reject(error);
}
);これで、サーバーとブラウザの認識ズレを防げます。 セキュリティ的にも安心ですね。
手順③:AuthContextをSWRで劇的進化させる
いよいよ本丸です。
useStateで管理していたユーザー情報を、SWRに置き換えます。 これにより、コンポーネントが再マウントされても一瞬で状態が復元されます。
まずは、AuthContext.tsxを開いて、下記のように修正してください。
"use client";
import useSWR from "swr";
import { AUTH_HINT_KEY } from "@/lib/constants";
// (その他のインポートは省略)
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
// 1. localStorageからヒントを同期的に取得
const getAuthHint = () => {
if (typeof window !== 'undefined') {
return localStorage.getItem(AUTH_HINT_KEY);
}
return null;
};
const authHint = getAuthHint();
// 2. SWRを使ってユーザー情報をフェッチ
// ヒントが無い場合は、無駄なAPI通信を一切行いません!
const { data: user, error, mutate } = useSWR(
authHint ? "/user" : null,
async (url) => {
const response = await api.get(url);
localStorage.setItem(AUTH_HINT_KEY, 'true'); // ヒントを確実にする
return response.data;
}
);
// 3. ローディング状態の厳密な判定
const loading = !!authHint && user === undefined && !error;
// 4. ログイン・ログアウト処理
const login = async (userData) => {
localStorage.setItem(AUTH_HINT_KEY, 'true');
await mutate(userData, false);
};
const logout = async () => {
localStorage.removeItem(AUTH_HINT_KEY);
await mutate(undefined, false);
// (ログアウトAPIの呼び出しとリダイレクト処理)
};
return (
<AuthContext.Provider value={{ user: user || null, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
};これで完成です! ブラウザで確認してみてください。画面のチラつきは完全に消え去ったはずです。
- Q実装したのに、ヘッダーが未認証のまま表示される?
- A
実装直後に、一番起きやすい現象です。 結論から言うと、コードのバグではありません。
あなたのブラウザに、古いセッション(Cookie)が残っているかも。 ヒント(localStorage)だけが空っぽ、という過渡期の状態です。
解決策:一度だけ、改めてログインし直してください。
再ログインすることで、Cookieとヒントが綺麗に同期します。 その後は、何度リロードしても滑らかな挙動になるはずです。
まとめ
お疲れ様でした!
「セキュリティの強さはそのままに、UXだけを最高水準に引き上げる。」 まさにシステムアーキテクトとしての腕の見せ所ですね。
無駄なAPI通信も減るので、サーバーのお財布にも優しいというわけです。
Trade Agencyでは、こうした現場のリアルな開発ノウハウをどんどん発信しています。 トレードシステムの裏側に興味がある方は、ぜひブックマークをお願いします!
それでは、また次回の記事でお会いしましょう。編集長Kでした!


コメント