Comment faire un Upload avec Next.js en App Router
Aujourd'hui nous allons voir comment faire un upload très simple avec Next.js 14 en App Router, sans extension Formidable, Multer ou autre sorcellerie. Accrochez vous ça va aller vite !
Un formulaire d'Upload
Pour tout projet un tant soit peu ambitieux, nous avons besoin de gérer des uploads de fichiers. C'est pourquoi je vous propose aujourd'hui de mettre en place un formulaire d'upload de fichier, sans se prendre la tête avec des extensions et alourdir notre code pour rien. Du vrai Do It Yourself ! 😎
Nous allons utiliser Next.js 14, Typescript et React, mais je vous préviens, je n'ai pas fait de vérifications côté front ou back du contenu, des extensions de fichier autorisées, de la taille maximum, pas de design etc... Ce sont des choses que vous pourrez ajouter facilement par la suite.
Nous allons faire un upload où le serveur renverra le nom du fichier reçu, et nous nous assurerons que ce fichier existe bien côté serveur.
Dans ce cas, nous allons recevoir les uploads directement dans /public, afin de pouvoir récupérer les images côté front par la suite.
Côté serveur
Nous aurons besoin de définir au moins un environnement pour que le filesystem sache où écrire.
Je crée un fichier .env
ROOT_PATH=/var/www/node/next-upload/ # path vers votre projet ici
Je crée une route d'API dans src/app/api/upload/route.ts
import { NextRequest, NextResponse } from "next/server";
import path from "path";
import fs from "fs";
const UPLOAD_DIR = path.resolve(process.env.ROOT_PATH ?? "", "public/uploads");
export const POST = async (req: NextRequest) => {
const formData = await req.formData();
const body = Object.fromEntries(formData);
const file = (body.file as Blob) || null;
if (file) {
const buffer = Buffer.from(await file.arrayBuffer());
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR);
}
fs.writeFileSync(
path.resolve(UPLOAD_DIR, (body.file as File).name),
buffer
);
} else {
return NextResponse.json({
success: false,
});
}
return NextResponse.json({
success: true,
name: (body.file as File).name,
});
};
Côté front
Je vais créer un fichier src/components/form.tsx, nous allons déclencher un upload directement si l'état de l'input file change.
"use client";
export const Form = () => {
return (
<input
type="file"
name="file"
onChange={async (e) => {
if (e.target.files) {
const formData = new FormData();
Object.values(e.target.files).forEach((file) => {
formData.append("file", file);
});
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const result = await response.json();
if (result.success) {
alert("Upload ok : " + result.name);
} else {
alert("Upload failed");
}
}
}}
/>
);
};
Enfin dans src/app/page.tsx
import { Form } from "@/components/form";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Form />
</main>
);
}
Et voilà le résultat :
Je ne le dirais jamais assez, il ne faut pas s'encombrer de libs inutiles dans nos projets, si on peut faire sans, faisons sans, et augmentons la valeur de nos produits !
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.