View on GitHub

ローディングの作成

Home

ローディングの作成

定数の定義

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 にアクセスすると、ページが読み込まれるまでの間、ローディングのアニメーションが表示されます。

確認できたら、追加した行は削除してください。

参考

  1. 【Next.js13】最新バージョンの Next.js13 をマイクロブログ構築しながら基礎と本質を学ぶ講座 | Udemy
    • ローディング用ページを作成して遅延した際のローディングを実装してみよう
  2. Suspense – React
  3. Routing: Loading UI and Streaming | Next.js