View on GitHub

映画詳細ページの作成

Home

映画詳細ページの作成

API

映画取得API

frontend/src/services/movies/getMovie.ts

import { ApiContext, Movie } from '@/types/data';
import { fetcher } from '@/utils';

const context: ApiContext = {
  apiRootUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
};

/**
 * 映画取得API
 * @param movieId - 映画ID
 * @returns 映画
 */
const getMovie = async (movieId: number): Promise<{ movie: Movie }> => {
  return await fetcher(`${context.apiRootUrl?.replace(/\/$/g, '')}/movies/${movieId}/`, {
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    cache: 'no-store',
  });
};

export default getMovie;

コンポーネント

映画詳細カードコンポーネント

frontend/src/app/components/movie/MovieCardDetail.tsx

import { Movie } from '@/types/data';
import Image from 'next/image';
import React from 'react';

type Props = {
  movie: Movie;
};

const MovieCardDetail = (props: Props) => {
  const img_url = '/img/dummy_poster.png';

  return (
    <>
      <article key={props.movie.id}>
        <div className="mx-4 my-4 flex">
          <div className="flex-shrink-0 md:block">
            <Image src={img_url} alt="" width={150} height={224} priority={true} />
          </div>
          <div className="ml-6">
            <div>
              <h3 className="text-3xl font-semibold text-gray-800">{props.movie.title}</h3>
            </div>
            <div className="my-2 flex">
              <div className="mx-1 my-1 rounded bg-gray-200 px-1 py-0.5 text-sm text-gray-800">
                {props.movie.year}
              </div>
            </div>
            <div className="my-2 flex">
              {props.movie.genres.map((genre) => (
                <div
                  className="mx-1 rounded bg-blue-500 px-1 py-0.5 text-xs text-white"
                  key={genre}
                >
                  {genre}
                </div>
              ))}
            </div>
          </div>
        </div>
      </article>
    </>
  );
};

export default MovieCardDetail;

ページ

映画詳細ページ

frontend/src/app/movies/[id]/page.tsx

import MovieCardDetail from '@/app/components/movie/MovieCardDetail';
import getMovie from '@/services/movies/getMovie';
import connectUser from '@/services/users/connectUser';

const Movie = async ({ params }: { params: Promise<{ id: number }> }) => {
  await connectUser();
  const { id } = await params;
  const movieId = id;
  const { movie } = await getMovie(movieId);

  return (
    <>
      <MovieCardDetail movie={movie} />
    </>
  );
};

export default Movie;

tsconfig.jsonの設定

frontend/tsconfig.json

{
...(略)...
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts", // <- ,」を追加
    ".next/types/app/movies/[id]/page.tsx" // <- 追加
  ],
  "exclude": ["node_modules"]
}

ブラウザで下記URLにアクセスすると、映画ID 1の詳細ページが表示されます。

映画リスト内の映画をクリックすることでも詳細ページが表示されます。

参考

  1. 【Next.js 15】動的ルーティング設定時に発生したエラーについて #AppRouter - Qiita
  2. 手島拓也,吉田健人,高林佳稀,『TypeScriptとReact/Next.jsでつくる 実践Webアプリケーション開発』,技術評論社,2022.
    • 6.9.5 商品詳細ページ