ローディングの作成
定数の定義
src/frontend/src/constants/styles.ts
/**
* スタイル関連の定数
*/
export const STYLES = {
...(略)...
// エラーページ関連
...(略)...
// ↓追加
// ローディング関連
LOADING_SPINNER: `h-12 w-12 animate-spin rounded-full border-t-4 border-indigo-600`,
LOADING_PAGE: `flex min-h-screen items-center justify-center`,
LOADING_INLINE: `flex items-center justify-center py-8`,
// ↑追加
...(略)...
} as const;
Loadingコンポーネントの作成
src/frontend/src/app/components/ui/Loading.tsx
import { STYLES } from "@/constants";
/**
* ローディングスピナー
*/
const Loading = () => {
return <div className={`${STYLES.LOADING_SPINNER}`}></div>;
};
export default Loading;
src/frontend/src/app/loading.tsx
import { STYLES } from "@/constants";
import Loading from "./components/ui/Loading";
const LoadingPage = () => {
return (
<div className={`${STYLES.LOADING_PAGE}`}>
<Loading />
</div>
);
};
export default LoadingPage;
layout.tsx にタグを追加
src/frontend/src/app/layout.tsx
import { Suspense } from 'react'; // <- 追加
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { STYLES, SETTINGS } from '@/constants';
import Header from './components/Header';
import Footer from './components/Footer';
import Loading from './loading'; // <- 追加
...(略)...
export default function RootLayout({ children }: LayoutProps<'/'>) {
return (
<html lang="ja" className={`${geistSans.variable} ${geistMono.variable} ${STYLES.HTML}`}>
<body className={`${STYLES.BODY}`}>
<Header />
<main className={`${STYLES.MAIN}`}>
<Suspense fallback={<Loading />}>{children}</Suspense> {/* <- Suspenseを追加 */}
</main>
<Footer />
</body>
</html>
);
}
コンポーネントの作成
src/frontend/src/app/components/ui/card/CardMovieDetail.tsx
...(略)...
import getMovie from "@/api/movies/getMovie";
import Loading from "../Loading"; // <- 追加
...(略)...
/**
* 映画(詳細)カードコンポーネント
*/
const CardMovieDetail = (props: Props) => {
...(略)...
/**
* 画像エラーハンドラ
* @param e - 画像エラーイベント
*/
const handleImageError = useCallback(
...(略)...
);
// ↓追加
if (loading) {
return (
<div className={`${STYLES.MOVIE_DETAIL_PAGE}`}>
<Loading />
</div>
);
}
// ↑追加
...(略)...
};
export default CardMovieDetail;
src/frontend/src/app/components/ui/list/ListMovieDaily.tsx
...(略)...
import ListMovie from "./ListMovie";
import Loading from "../Loading"; // <- 追加
/**
* 本日のおすすめ映画リストコンポーネント
*/
const ListMovieDaily = () => {
...(略)...
useEffect(() => {
...(略)...
}, []);
// ↓追加
if (loading) {
return (
<div className={`${STYLES.LOADING_INLINE}`}>
<Loading />
</div>
);
}
// ↑追加
...(略)...
};
export default ListMovieDaily;
ページ
src/frontend/src/app/myaccount/page.tsx
...(略)...
const MyAccountPage = () => {
...(略)...
if (isLoading || !isAuthenticated) {
// ↓修正
return (
<div className={`${STYLES.LOADING_PAGE}`}>
<div className={`${STYLES.LOADING_SPINNER}`}></div>
</div>
);
// ↑修正
}
...(略)...
};
export default MyAccountPage;
ローディングの確認
src/frontend/src/api/movies/getMovies.ts
...(略)...
/**
* 映画リスト取得API
* @returns movies - 映画リスト
*/
const getMovies = async (): Promise<{ movies: Movie[] }> => {
await new Promise((resolve) => setTimeout(resolve, 1000)); // <- 追加
...(略)...
};
export default getMovies;
ブラウザで下記 URL にアクセスすると、ページが読み込まれるまでの間、ローディングのアニメーションが表示されます。
確認できたら、追加した行は削除してください。
参考
- 【Next.js13】最新バージョンの Next.js13 をマイクロブログ構築しながら基礎と本質を学ぶ講座 | Udemy
- ローディング用ページを作成して遅延した際のローディングを実装してみよう
- Suspense – React
- Routing: Loading UI and Streaming | Next.js