Uploader un fichier avec Next.js 15

Uploader un fichier avec Next.js 15
Alexandre P. dans Dev - mis à jour le 27-04-2025

Découvrez comment implémenter facilement l'upload de fichiers dans Next.js 15 avec React-Hook-Form et React-Query grâce à un hook réutilisable et prêt à l'emploi.

Next.js est un framework vraiment pratique et qui a changé beaucoup de choses pour les développeurs React. Désormais il est simple de concevoir des sites entiers avec du render front et back.

Mais souvent, dans nos projets nous avons besoin de gérer l'upload, l'envoie de fichier est une des fonctionnalités d'interaction les plus communes.

Comment uploader un fichier avec Next.js ?

Pourtant, même si c'est un besoin qui revient souvent, il n'y a aucun outil spécifique qui est mis à disposition pour faire cela de la manière la plus simple.

Je vous propose de vous montrer un exemple de comment je fais un upload avec Next.js 15 en utilisant React-Hook-Form et React-Query.

Je vais vous fournir un hook que j'utilise et que vous pourrez copier coller facilement dans vos projets.

Installation des dépendances

Pour faire ce petit projet, on va installer React-Hook-Form et React-Query.

Pour info j'utilise node 22 ou bun.

npm add react-hook-form
npm add @tanstack/react-query

# ou

bun add react-hook-form
bun add @tanstack/react-query

Arborescence

Nous allons créer les fichiers de manière à obtenir cet arborescence lorsque nous aurons créé tous nos scripts:

project-root/
├── src/
│   ├── app/
│   │   ├── page.tsx             # Page d'accueil avec le formulaire
│   │   └── api/
│   │       └── upload/
│   │           └── route.ts     # Route API pour l'upload
│   ├── components/
│   │   ├── wrapper.tsx          # Wrapper pour React-Query
│   │   └── uploadForm.tsx       # Formulaire d'upload
│   └── hooks/
│       └── useUpload.tsx        # Hook pour gérer l'upload
├── uploads/                     # Dossier où sont stockés les fichiers uploadés
│   └── [fichiers uploadés]
├── node_modules/
├── package.json
└── package-lock.json           # ou bun.lockb si vous utilisez Bun

Gestion de l'upload en frontend

Tout d'abord mettons en place nos vues et tout ce que nous allons utiliser pour mettre en place l'upload sur nos vues React.

1 - Activation de React-Query

Pour que React-Query fonctionne, il faut wrapper nos éléments dans un QueryProvider, c'est ce que j'ai fait rapidement dans un composant src/components/wrapper.tsx:

// src/components/wrapper.tsx

"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

export const Wrapper = ({ children }: { children: React.ReactNode }) => {
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

Je l'ai importé ensuite dans ma page Home, src/app/page.tsx, vous pouvez très bien l'importer dans votre layout.tsx:

// src/app/page.tsx

import UploadForm from "@/components/uploadForm";
import { Wrapper } from "@/components/wrapper";

export default function Home() {
  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <Wrapper>
        <UploadForm />
      </Wrapper>
    </div>
  );
}

2 - Création du hook

Je crée un hook qui va me permettre d'appeler le backend facilement et de gérer les état:

  • est en train d'uploader ⏳
  • upload ok, le fichier est envoyé ✅
  • upload ko, l'upload a échoué ❌

Je crée un hook useUpload dans un fichier src/hooks/useUpload.tsx:

// src/hooks/useUpload.tsx

"use client";
import { useMutation } from "@tanstack/react-query";

export const useUpload = () => {
  const uploadFn = async (file: File) => {
    const formData = new FormData();
    formData.append("file", file);

    const response = await fetch("/api/upload", {
      method: "POST",
      body: formData,
    });

    return response;
  };

  return useMutation({
    mutationFn: uploadFn,
  });
};

3 - Création du formulaire

Je me crée un formulaire d'upload dans un composant src/components/uploadForm.tsx.

Je fais en sorte de créer des actions en cas de succès et d'échec:

  • Si le fichier est bien uploadé, j'affiche un alert message Uploaded
  • Si l'upload a échoué, j'affiche un message Error

Vous pourrez facilement personnaliser ces actions avec un toaster ou une redirection.

// src/components/uploadForm.tsx

"use client";
import { useForm } from "react-hook-form";
import { useUpload } from "@/hooks/useUpload";

type UploadData = {
  file: FileList;
};

export default function UploadForm() {
  const { register, handleSubmit } = useForm<UploadData>();
  const { mutate: upload, isPending } = useUpload();

  const onSubmit = (data: UploadData) => {
    if (!data.file?.[0]) return;
    upload(data.file[0], {
      onSuccess: (response) => {
        if (response.status === 200) {
          alert("Uploaded");
        } else {
          alert("Error");
        }
      },
      onError: () => {
        alert("Error");
      },
    });
  };

  return isPending ? (
    <div>Uploading...</div>
  ) : (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="flex flex-col gap-2 mb-4">
        <label htmlFor="file">Picture</label>
        <input
          {...register("file")}
          type="file"
          accept="image/*"
          className="border border-gray-300 p-2 bg-gray-100 rounded-md cursor-pointer"
        />
      </div>
      <button type="submit" className="bg-blue-500 text-white p-2 rounded-md">
        Upload
      </button>
    </form>
  );
}

Gestion de l'upload en backend

Il ne nous reste plus qu'à créer notre route et à traiter le fichier après l'upload.

Dans la plupart des framework, lorsqu'un fichier est uploadé, il est envoyé dans un dossier temporaire de votre système, c'est à vous de l'écrire ailleurs.

Ici nous allons procéder à une écriture dans un dossier uploads/ à la racine du projet.

Je crée un endpoint api/upload en créant un fichier src/app/api/upload/route.ts:

// src/app/api/upload/route.ts

import fs from "fs";
import path from "path";

const DIR_PATH = path.resolve(`./uploads`);

if (!fs.existsSync(DIR_PATH)) {
  fs.mkdirSync(DIR_PATH, { recursive: true });
}

export async function POST(req: Request) {
  try {
    const formData = await req.formData();
    const file = formData.get("file") as File;

    if (!file) {
      return new Response(
        JSON.stringify({ error: "Aucun fichier n'a été fourni" }),
        {
          status: 400,
          headers: { "Content-Type": "application/json" },
        }
      );
    }

    if (!(file instanceof Blob)) {
      return new Response(
        JSON.stringify({ error: "Format de fichier invalide" }),
        {
          status: 400,
          headers: { "Content-Type": "application/json" },
        }
      );
    }

    // Récupération des informations du fichier
    const filename = file.name;
    const fileType = file.type;
    const fileSize = file.size;

    // Conversion du fichier en ArrayBuffer, puis en Buffer pour la manipulation
    const fileArrayBuffer = await file.arrayBuffer();
    const buffer = Buffer.from(fileArrayBuffer);

    // Enregistrement
    const filePath = path.resolve(DIR_PATH, filename);

    await fs.writeFileSync(filePath, buffer);

    return new Response(
      JSON.stringify({
        success: true,
        filename,
        fileType,
        fileSize,
        destination: filePath,
      }),
      {
        status: 200,
        headers: { "Content-Type": "application/json" },
      }
    );
  } catch (error) {
    console.error("Erreur lors du traitement du fichier:", error);
    return new Response(
      JSON.stringify({
        error: "Erreur lors du traitement du fichier",
        details: error instanceof Error ? error.message : String(error),
      }),
      {
        status: 500,
        headers: { "Content-Type": "application/json" },
      }
    );
  }
}

Tests

Lorsque je me rends sur mon url de homepage, j'ai désormais mon composant d'upload qui s'affiche:

Upload form

J'upload un fichier et j'ai bien mon message d'alert.

Upload alert

Vérification dans mes fichiers si j'ai bien tout reçu:

Uploaded File

Le fichier a bien été uploadé et notre composant fonctionne, vous pouvez copier coller cette approche pour l'utiliser facilement dans vos projets.

Bon code.

#nextjs#typescript#uploader un fichier#uploader un fichier nextjs 15

user picture

Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.


Votre vie privée

Nous utilisons des cookies pour améliorer votre expérience sur notre site, analyser notre trafic et personnaliser les publicités. En cliquant sur "Accepter", vous consentez à l'utilisation de tous les cookies. Vous pouvez également choisir de refuser en cliquant sur le bouton "Refuser".