DEPLOYMENT GUIDE
Convert DOCX to PDF in Next.js
Convert Word documents to PDF with a Next.js App Router handler. Add a React upload form and configure fonts and WebAssembly assets for Vercel.
To convert DOCX to PDF in Next.js, send a Word document to an App Router Route Handler, call exportPdf in the Node.js runtime, and return the PDF bytes. Conversion runs in your application without LibreOffice or a separate conversion container.
A deployment still needs the converter, its font files, and Core's WebAssembly assets. Validate the actual deployed bundle with representative documents before relying on it in production.
Use the Node.js runtime
Use a Next.js Route Handler with runtime = 'nodejs'. The current exporter uses Node.js dependencies and is not supported in the Edge runtime.
Install the converter in your Next.js project:
npm install @docx-editor.dev/docx-to-pdfKeep the PDF, Core, and font package versions compatible. See getting started for the EigenPal Pro License terms.
Convert DOCX to PDF in an App Router Route Handler
This example defines a handler in your own application. It is not an endpoint provided by docx-to-pdf.dev. Add your application's authentication and authorization before exposing it to users.
Create app/convert/route.ts:
import { exportPdf, PdfFidelityError } from '@docx-editor.dev/docx-to-pdf';
export const runtime = 'nodejs';
export const maxDuration = 60;
const MAX_INPUT_BYTES = 4 * 1024 * 1024;
export async function POST(request: Request) {
const contentType = request.headers.get('content-type')?.split(';')[0];
if (contentType !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
return new Response('Send DOCX bytes with the DOCX content type.', { status: 415 });
}
if (!request.body) return new Response('Document required.', { status: 400 });
// Bound the body while reading; do not trust Content-Length alone.
const reader = request.body.getReader();
const chunks: Uint8Array[] = [];
let size = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > MAX_INPUT_BYTES) {
await reader.cancel();
return new Response('Document is too large.', { status: 413 });
}
chunks.push(value);
}
} finally {
reader.releaseLock();
}
if (!size) return new Response('Document required.', { status: 400 });
const source = new Uint8Array(size);
let offset = 0;
for (const chunk of chunks) {
source.set(chunk, offset);
offset += chunk.byteLength;
}
try {
const result = await exportPdf(source, {
useSystemFonts: false,
fidelityPolicy: 'strict',
timeoutMs: 45_000,
maxOutputBytes: 4 * 1024 * 1024,
signal: request.signal,
});
return new Response(new Uint8Array(result.bytes), {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="document.pdf"',
'Cache-Control': 'no-store',
},
});
} catch (error) {
if (error instanceof PdfFidelityError) {
return Response.json(
{ error: 'fidelity', diagnostics: error.diagnostics },
{
status: 422,
headers: { 'Cache-Control': 'no-store' },
},
);
}
// Add specific handling for input, resource, and encoding errors.
throw error;
}
}The 4 MiB application limits above are examples, not published provider limits. Select limits that fit your hosting plan and document workload. Large outputs can be stored in object storage rather than returned through a function response.
Upload a Word document from React and download the PDF
Create app/upload/page.tsx. This Client Component sends the file to /convert in your own Next.js application. It sends raw DOCX bytes, matching the handler above, rather than a multipart form body.
'use client';
import { useEffect, useState, type FormEvent } from 'react';
const DOCX_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
export default function UploadPage() {
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [download, setDownload] = useState<{ url: string; name: string } | null>(null);
useEffect(() => {
return () => {
if (download) URL.revokeObjectURL(download.url);
};
}, [download]);
async function convert(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const file = new FormData(event.currentTarget).get('document');
if (!(file instanceof File) || !file.size) return;
setError('');
setDownload(null);
if (file.size > 4 * 1024 * 1024) {
setError('Choose a DOCX file smaller than 4 MiB.');
return;
}
setBusy(true);
try {
const response = await fetch('/convert', {
method: 'POST',
headers: { 'Content-Type': DOCX_TYPE },
body: file,
});
if (!response.ok) {
throw new Error(
response.status === 422
? 'This document needs a fidelity review before it can be exported.'
: `Conversion failed (${response.status}).`,
);
}
const pdf = await response.blob();
setDownload({
url: URL.createObjectURL(pdf),
name: file.name.replace(/\.docx$/i, '') + '.pdf',
});
} catch (error) {
setError(error instanceof Error ? error.message : 'Conversion failed.');
} finally {
setBusy(false);
}
}
return (
<main>
<h1>Convert DOCX to PDF</h1>
<form onSubmit={convert}>
<label htmlFor="document">Word document</label>
<input id="document" name="document" type="file" accept=".docx" required disabled={busy} />
<button type="submit" disabled={busy}>
{busy ? 'Converting…' : 'Convert to PDF'}
</button>
</form>
{error && <p role="alert">{error}</p>}
{download && (
<a href={download.url} download={download.name}>
Download PDF
</a>
)}
</main>
);
}Open /upload, select a .docx file, and submit it. The exporter belongs in the server handler; importing it into a Client Component would pull Node.js dependencies into the browser build. For conversion without a UI, use the Node.js JavaScript and TypeScript example.
Bundle fonts and WebAssembly for Vercel
Keep Node.js packages external and explicitly include assets that file tracing may miss. Start with this next.config.ts configuration, then inspect the resulting deployment:
import type { NextConfig } from 'next';
const config: NextConfig = {
serverExternalPackages: [
'@docx-editor.dev/docx-to-pdf',
'@docx-editor.dev/core',
'@docx-editor.dev/fonts',
'fontkit',
'pdf-lib',
],
outputFileTracingIncludes: {
'/convert': [
'./node_modules/@docx-editor.dev/docx-to-pdf/**/*',
'./node_modules/@docx-editor.dev/core/**/*',
'./node_modules/@docx-editor.dev/fonts/**/*',
],
},
};
export default config;This is a starting configuration, not a certified deployment recipe. Package layouts and workspace paths can differ. Check that the deployed function can resolve every font and WASM file it uses. Including entire packages may require reducing the asset set to fit your provider's bundle limit.
Test DOCX conversion after deployment
- Convert a small DOCX after a cold start and again with a warm instance.
- Inspect
fontResolution. Local system fonts must not be an accidental dependency. - Measure peak memory and duration with your largest expected documents.
- Check request, response, and deployment-size limits for your hosting plan.
- Bound concurrent conversions and test how timeouts and failures reach the caller.
For strict resource isolation, move conversion into a bounded worker or a dedicated Node.js worker service. The library's cancellation checks cannot interrupt every synchronous operation mid-call.
Refer to the Vercel Node.js runtime documentation, Vercel function limits, and Next.js file tracing documentation when configuring your deployment.
Troubleshoot Next.js conversion errors
| Symptom | What to check |
|---|---|
| A Node.js module cannot be resolved in the browser or Edge runtime | Import the exporter only in the server handler and set runtime = 'nodejs'. |
| A font or WASM file is missing in production | Inspect the traced function bundle. Include the assets from your installed package versions. |
| The PDF wraps differently after deployment | Use the same fonts locally and in production. Inspect fontResolution and compare with your Word reference. |
| The handler returns 415 | Send raw DOCX bytes with the DOCX content type. The example does not accept FormData or legacy .doc files. |
| Strict conversion returns 422 | Review the fidelity diagnostics. See fonts and page breaks before changing the conversion policy. |
| A function rejects a large request or times out | Check hosting limits, bound input and concurrency, and move larger jobs to a worker when necessary. |
For the differences between this deployment and a LibreOffice sidecar, see DOCX to PDF without LibreOffice.