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.tsx
next/image
Un 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}
any
MovieCard
isole la présentation et optimise next/image
remotePatterns
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
.