🎉
Next.js: 細かいUI/UX 向上テクニックのハッカソンメモ
はじめに
こないだ参加したハッカソンで得た知見の備忘録です。
グローバルな UI フィードバック (nextjs-toploader)
ページ遷移やrouter.refresh()実行時に、画面上部にプログレスバーを表示してロード中であることをユーザーにフィードバックする。
Provider などを用いて children を無闇にクライアントコンポーネント化することなく導入できるnextjs-toploaderが有効。
// layout.tsx
import NextTopLoader from "nextjs-toploader";
export default function MainLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<NextTopLoader color="#EBC2AD" showSpinner={false} />
<Header />
<main className="pt-16">{children}</main>
</>
);
}
グローバルな状態管理とデータ同期 (Zustand)
コンポーネント間で状態を共有し、UI の一貫性を保つために Zustand を導入。
離れたコンポーネント間で情報を同期させるケースで有効。
ストアの作成 (stores/userStore.ts)
import { create } from "zustand";
export const useUserStore = create<UserState>((set) => ({
user: null,
isLoading: true,
fetchUser: async () => {
try {
const res = await fetch("/api/me");
if (res.ok) {
const data = await res.json();
set({ user: data, isLoading: false });
} else {
set({ user: null, isLoading: false });
}
} catch (e) {
set({ user: null, isLoading: false });
}
},
}));
コンポーネントからの呼び出し
// Client Component内
import { useUserStore } from "@/stores/userStore";
const { user, fetchUser } = useUserStore();
同期的な UI 更新とトランジション (useTransition)
Promise を返さない非同期処理と、それに続く状態更新を、ユーザー体験上は同時に発生しているように見せるためにuseTransitionを使用する。
startTransitionでラップされた処理は一つのまとまり(トランジション)として管理される。React は、その中で最も時間のかかる処理が完了するのを待ってから、すべての状態更新を一度に画面に反映する。
// Client Component内
const [isPending, startTransition] = useTransition();
const handleUpdate = () => {
// ... APIリクエストが成功した後 ...
startTransition(() => {
router.refresh(); // 時間のかかるUI更新
setOpen(false); // 即時実行される状態更新
});
// ユーザーには、refreshが完了した後にモーダルが閉じたように見える
};
Discussion