Crée un composant MovieCard avec Next/Image et gère l’optimisation des images.
Renseigne ton email pour débloquer immédiatement cette formation gratuite.
Dans cette leçon, tu vas apprendre à extraire l’affichage d’un film dans un composant réutilisable MovieCard et à optimiser les images avec next/image. On verra :
Lorsque tu récupères la liste des films via l’API The Movie Database, tu obtiens un tableau d’objets { id, title, poster_path, release_date }.
Si tu affiches directement ces données dans la page, tu te retrouves vite avec du JSX dupliqué pour chaque film.
En extrayant un MovieCard, tu gagnes :
MovieCard.tsxnext/imageUn composant isolé rend ton code DRY (« Don’t Repeat Yourself ») et facilite l’évolution
Avant d’afficher, on veut s’assurer que les données reçues correspondent bien à notre schéma.
Dans app/server/movies.ts, on a déjà :
1import { z } from 'zod';23export const GetMoviesSchema = z.object({4page: z.number(),5results: z.array(6z.object({7id: z.number(),8title: z.string(),9poster_path: z.string(),10release_date: z.string(),11})12),13});1415export async function getMovies() {16const data = await fetchMovieDb({ url: '/movie/popular' });17const { results } = GetMoviesSchema.parse(data);18return results;19}
On exporte un schéma et on parse la réponse avant de renvoyer
results.
Cela met fin aux erreurs de type et te permet de profiter de l’inférence TypeScript.
Maintenant, définis ton type Movie :
1import { z } from 'zod';2import { GetMoviesSchema } from '../server/movies';34export type Movie = z.infer<typeof GetMoviesSchema>['results'][number];
Crée un nouveau fichier dédié :
1import Image from 'next/image';2import Link from 'next/link';3import type { Movie } from '../types/movie';45export function MovieCard({ movie }: { movie: Movie }) {6return (7<div className="max-w-[180px] rounded-lg overflow-hidden shadow hover:scale-105 transition">8<div className="relative h-[270px] w-full">9<Image10src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}11alt={movie.title}12fill13className="object-cover"14sizes="(max-width: 768px) 100vw, 200px"15/>16</div>17<div className="p-2 bg-white dark:bg-gray-800">18<h3 className="font-semibold truncate">{movie.title}</h3>19<p className="text-sm text-gray-500">20{new Date(movie.release_date).getFullYear()}21</p>22<Link23href={`/movies/${movie.id}`}24className="inline-block mt-2 text-blue-600 hover:underline text-sm"25>26Voir plus27</Link>28</div>29</div>30);31}
Points importants :
fill, object-cover et sizes<Link> pour la navigation vers la page détailPar défaut, Next.js bloque les domaines externes.
Ouvre next.config.js et ajoute les remotePatterns :
1module.exports = {2images: {3remotePatterns: [4{5protocol: 'https',6hostname: 'image.tmdb.org',7port: '',8pathname: '/t/p/**',9},10],11},12};
Après ça, tu pourras charger les affiches sans erreur.
Modifie ta page d’accueil pour utiliser MovieCard :
1import { getMovies } from './server/movies';2import { MovieCard } from './components/MovieCard';34export default async function Home() {5const movies = await getMovies();6return (7<main className="p-8 grid grid-cols-fluid gap-6">8<h1 className="col-span-full text-4xl font-bold">Films populaires</h1>9{movies.map(movie => (10<MovieCard key={movie.id} movie={movie} />11))}12</main>13);14}
La grille CSS grid-cols-fluid peut être définie dans ton globals.css :
1@layer utilities {2.grid-cols-fluid {3grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));4}5}
anyMovieCard isole la présentation et optimise next/imageremotePatterns permet de charger des images TMDBsizes et fill réduit la bande passanteMovieCard, utilise <Image placeholder="blur" blurDataURL="..." />.release_date est dans les 30 derniers jours.MovieCard pour construire un SeriesCard avec first_air_date.