Colas Mérand
23/08/2025
Next.js
Vercel
5 minutes
La génération et la conversion de documents sont des fonctionnalités essentielles pour de nombreuses applications web modernes. Que ce soit pour créer des factures, des rapports ou des contrats, la possibilité de produire des documents professionnels en différents formats est souvent un besoin critique pour les entreprises. Dans cet article, nous allons explorer les défis et solutions pour convertir des fichiers DOCX en PDF dans un environnement serverless, spécifiquement pour les applications Next.js hébergées sur Vercel.
Les applications hébergées sur des plateformes serverless comme Vercel présentent des contraintes spécifiques qui compliquent la conversion de documents. Contrairement aux serveurs traditionnels, vous ne pouvez pas simplement installer LibreOffice ou d'autres outils lourds qui sont couramment utilisés pour ces tâches.
Les principales difficultés incluent :
Ces contraintes nécessitent des approches spécifiques pour obtenir des résultats professionnels.
Après avoir travaillé sur plusieurs projets nécessitant ce type de fonctionnalité, notamment pour des plateformes comme Easop (gestion de stock options) et Astory (location d'œuvres d'art), nous avons identifié plusieurs approches viables :
Cette approche consiste à utiliser Puppeteer, un outil d'automatisation de navigateur, couplé à une version allégée de Chrome optimisée pour les environnements serverless.
// Exemple simplifié d'implémentation avec Puppeteer
import chromium from '@sparticuz/chromium';
import puppeteer from 'puppeteer-core';
import { readFile } from 'fs/promises';
import mammoth from 'mammoth';
export default async function handler(req, res) {
try {
const docxBuffer = req.body.docxFile;
// Convertir DOCX en HTML avec mammoth
const { value: html } = await mammoth.convertToHtml({ buffer: docxBuffer });
// Initialiser le navigateur
const browser = await puppeteer.launch({
args: chromium.args,
executablePath: await chromium.executablePath(),
headless: true,
});
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
// Générer le PDF
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
await browser.close();
// Renvoyer le PDF
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');
res.send(pdfBuffer);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Cette solution fonctionne bien pour des documents de complexité moyenne et offre un bon contrôle sur le rendu final.
Une autre approche consiste à utiliser des API spécialisées dans la conversion de documents :
// Exemple avec un service API tiers
export default async function handler(req, res) {
try {
const docxBuffer = req.body.docxFile;
const response = await fetch('https://api.conversion-service.com/convert', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/octet-stream'
},
body: docxBuffer
});
if (!response.ok) {
throw new Error('Conversion failed');
}
const pdfBuffer = await response.arrayBuffer();
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');
res.send(Buffer.from(pdfBuffer));
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Cette solution est souvent la plus fiable pour les documents complexes, mais elle introduit une dépendance externe et peut engendrer des coûts supplémentaires.
Pour les cas où la mise en page n'est pas extrêmement complexe, une approche hybride utilisant des bibliothèques comme docx-to-pdf peut être efficace :
import { convert } from 'docx-pdf';
import { writeFile, readFile } from 'fs/promises';
import { v4 as uuidv4 } from 'uuid';
import os from 'os';
import path from 'path';
export default async function handler(req, res) {
try {
const docxBuffer = req.body.docxFile;
// Créer des fichiers temporaires
const tempDir = os.tmpdir();
const docxPath = path.join(tempDir, `${uuidv4()}.docx`);
const pdfPath = path.join(tempDir, `${uuidv4()}.pdf`);
await writeFile(docxPath, docxBuffer);
// Convertir DOCX en PDF
await convert(docxPath, pdfPath);
// Lire le PDF généré
const pdfBuffer = await readFile(pdfPath);
// Nettoyer les fichiers temporaires
await Promise.all([
unlink(docxPath).catch(() => {}),
unlink(pdfPath).catch(() => {})
]);
// Renvoyer le PDF
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');
res.send(pdfBuffer);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Lors de l'implémentation d'une solution de conversion DOCX vers PDF sur Vercel, nous recommandons les pratiques suivantes :
Pour éviter de reconvertir les mêmes documents, implémentez un système de mise en cache :
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.REDIS_URL,
token: process.env.REDIS_TOKEN,
});
// Dans votre handler
const cacheKey = `pdf:${createHash('md5').update(docxBuffer).digest('hex')}`;
const cachedPdf = await redis.get(cacheKey);
if (cachedPdf) {
// Retourner le PDF depuis le cache
return res.send(Buffer.from(cachedPdf, 'base64'));
}
// Sinon, procéder à la conversion et mettre en cache
// ...
await redis.set(cacheKey, pdfBuffer.toString('base64'), { ex: 86400 }); // Expire après 24h
Les conversions de documents volumineux peuvent prendre du temps. Utilisez des stratégies pour gérer les timeouts :
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb',
},
responseLimit: '10mb',
},
};
// Utiliser une fonction d'aide pour surveiller le temps d'exécution
const withTimeout = (promise, timeoutMs) => {
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
reject(new Error('Operation timed out'));
}, timeoutMs);
});
return Promise.race([
promise,
timeoutPromise,
]).finally(() => {
clearTimeout(timeoutHandle);
});
};
// Dans votre handler
try {
const result = await withTimeout(convertDocument(docxBuffer), 25000); // 25 secondes
// ...
} catch (error) {
if (error.message === 'Operation timed out') {
// Rediriger vers un processus asynchrone ou informer l'utilisateur
}
// ...
}
Chez Platane, nous avons implémenté ces solutions pour plusieurs clients avec des besoins variés :
Pour Easop, nous avons développé un système de génération de contrats de stock-options qui nécessitait une conversion précise des documents juridiques du format DOCX vers PDF, tout en préservant la mise en page complexe incluant des tableaux et des signatures.
Pour Astory, nous avons créé un système de génération de certificats d'authenticité pour les œuvres d'art, où la qualité visuelle du document final était primordiale.
Dans ces deux cas, l'approche Puppeteer avec Chrome serverless s'est révélée être la solution la plus adaptée, offrant un bon équilibre entre fidélité de rendu et compatibilité avec l'environnement serverless de Vercel.
La conversion de documents DOCX vers PDF dans un environnement serverless comme Vercel présente des défis techniques spécifiques, mais plusieurs solutions viables existent. Le choix de l'approche dépendra de la complexité de vos documents, de vos contraintes budgétaires et de vos exigences en termes de fidélité de rendu.
Chez Platane, nous privilégions des solutions robustes qui allient performance et qualité, tout en respectant les contraintes techniques des environnements modernes comme Vercel. Notre expérience avec des projets comme Easop et Astory nous a permis de développer une expertise pointue dans ce domaine.
Vous avez un projet nécessitant une conversion de documents ou d'autres fonctionnalités techniques avancées pour votre application Next.js ? N'hésitez pas à prendre rendez-vous via notre formulaire de contact. Notre équipe d'experts se fera un plaisir d'échanger avec vous sur vos besoins spécifiques et de vous proposer des solutions sur mesure qui répondent parfaitement à vos objectifs. Collaborer avec Platane, c'est s'assurer d'une expertise technique de pointe au service de votre vision.
Vous préférez discuter de vive voix ? Nous aussi et c'est évidemment sans engagement !
Une question, un besoin de renseignements ? N'hésitez pas à nous contacter.