Création de MovieCard et affichage optimisé

Crée un composant MovieCard avec Next/Image et gère l’optimisation des images.

4 min read

Accéder gratuitement à cette formation

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 :

  • comment typer les données via Zod
  • comment créer un composant Server Component pour afficher un film
  • comment configurer Next.js pour charger les images depuis TMDB
  • comment intégrer ce composant dans la page d’accueil

1 Pourquoi isoler un composant ?

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 :

  • en lisibilité : un endroit unique pour la présentation
  • en maintenabilité : si tu veux changer le style ou ajouter un badge, c’est dans MovieCard.tsx
  • en optimisation : tu peux spécialiser la configuration de next/image

2 Définir et typer les données via Zod

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à :

app/server/movies.ts
1
import { z } from 'zod';
2
3
export const GetMoviesSchema = z.object({
4
page: z.number(),
5
results: z.array(
6
z.object({
7
id: z.number(),
8
title: z.string(),
9
poster_path: z.string(),
10
release_date: z.string(),
11
})
12
),
13
});
14
15
export async function getMovies() {
16
const data = await fetchMovieDb({ url: '/movie/popular' });
17
const { results } = GetMoviesSchema.parse(data);
18
return 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 :

app/types/movie.ts
1
import { z } from 'zod';
2
import { GetMoviesSchema } from '../server/movies';
3
4
export type Movie = z.infer<typeof GetMoviesSchema>['results'][number];

3 Implémenter le composant MovieCard

Crée un nouveau fichier dédié :

app/components/MovieCard.tsx
1
import Image from 'next/image';
2
import Link from 'next/link';
3
import type { Movie } from '../types/movie';
4
5
export function MovieCard({ movie }: { movie: Movie }) {
6
return (
7
<div className="max-w-[180px] rounded-lg overflow-hidden shadow hover:scale-105 transition">
8
<div className="relative h-[270px] w-full">
9
<Image
10
src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
11
alt={movie.title}
12
fill
13
className="object-cover"
14
sizes="(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
<Link
23
href={`/movies/${movie.id}`}
24
className="inline-block mt-2 text-blue-600 hover:underline text-sm"
25
>
26
Voir plus
27
</Link>
28
</div>
29
</div>
30
);
31
}

Points importants :

  1. Image optimisée avec fill, object-cover et sizes
  2. Border radius et shadow pour un rendu soigné
  3. Un lien <Link> pour la navigation vers la page détail

4 Configurer Next.js pour les images distantes

Par défaut, Next.js bloque les domaines externes.
Ouvre next.config.js et ajoute les remotePatterns :

next.config.js
1
module.exports = {
2
images: {
3
remotePatterns: [
4
{
5
protocol: 'https',
6
hostname: 'image.tmdb.org',
7
port: '',
8
pathname: '/t/p/**',
9
},
10
],
11
},
12
};

Après ça, tu pourras charger les affiches sans erreur.


5 Afficher la liste avec MovieCard

Modifie ta page d’accueil pour utiliser MovieCard :

app/page.tsx
1
import { getMovies } from './server/movies';
2
import { MovieCard } from './components/MovieCard';
3
4
export default async function Home() {
5
const movies = await getMovies();
6
return (
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 :

app/globals.css
1
@layer utilities {
2
.grid-cols-fluid {
3
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
4
}
5
}

Points clés

  • Zod garantit un typage solide et évite les any
  • MovieCard isole la présentation et optimise next/image
  • remotePatterns permet de charger des images TMDB
  • L’usage de sizes et fill réduit la bande passante

Exercices rapides

  1. Ajouter un placeholder : Dans MovieCard, utilise <Image placeholder="blur" blurDataURL="..." />.
  2. Badge “Nouvelle sortie” : Affiche un badge si release_date est dans les 30 derniers jours.
  3. Composant SeriesCard : Inspire-toi de MovieCard pour construire un SeriesCard avec first_air_date.