This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/document-authoring/deploy-and-production/nodejs-in-production.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Run Document Authoring in Node.js in production

This guide covers running the headless Node.js entry point on production servers: how work is scheduled, how to scale across CPU cores, and how to configure assets. Before you start, follow the Node.js getting started guide.

The scaling model

A DocAuthSystem holds the WebAssembly (WASM) document engine and loaded fonts. The model to follow on servers:

  • Create one system per process (or worker thread) at startup, and reuse it for every request. Creating a system loads and initializes the WASM engine, so don’t create one per request.
  • Process one job at a time in each worker. Use multiple workers when you need CPU parallelism.
  • Drop document references after each request. Documents don’t have a destroy() method. Holding many documents alive at once costs memory.
  • Call system.destroy() on shutdown. It releases the engine and the scheduler. A typical server registers it in a signal handler:
import { createDocAuthSystem } from '@nutrient-sdk/document-authoring/node';
const system = await createDocAuthSystem();
process.on('SIGTERM', () => {
system.destroy();
process.exit(0);
});

For CPU parallelism, run multiple processes or worker threads, each with its own system.

Parallel conversion with worker threads

The following example converts a queue of DOCX files to PDF across worker threads. Each worker boots one system at startup and processes one job at a time; the main thread distributes the queue.

convert-worker.mjs:

import { parentPort } from 'node:worker_threads';
import { readFile, writeFile } from 'node:fs/promises';
import { createDocAuthSystem } from '@nutrient-sdk/document-authoring/node';
// One system per worker, booted once at startup.
const system = await createDocAuthSystem();
parentPort.on('message', async (job) => {
if (job === 'shutdown') {
system.destroy();
parentPort.close();
return;
}
const document = await system.import(await readFile(job.input), { format: 'docx' });
await writeFile(job.output, new Uint8Array(await document.export({ format: 'pdf' })));
parentPort.postMessage({ done: job.output });
});

convert-main.mjs:

import { Worker } from 'node:worker_threads';
import { availableParallelism } from 'node:os';
const jobs = [
{ input: 'input.docx', output: 'converted-1.pdf' },
{ input: 'input.docx', output: 'converted-2.pdf' },
{ input: 'input.docx', output: 'converted-3.pdf' },
];
const workerCount = Math.min(availableParallelism(), jobs.length);
const queue = [...jobs];
await Promise.all(
Array.from({ length: workerCount }, () => {
const worker = new Worker(new URL('convert-worker.mjs', import.meta.url));
return new Promise((resolve, reject) => {
const dispatch = () => {
const job = queue.shift();
worker.postMessage(job ?? 'shutdown');
};
worker.on('message', ({ done }) => {
console.log(`Converted ${done}`);
dispatch();
});
worker.on('error', reject);
worker.on('exit', resolve);
dispatch();
});
}),
);
console.log('All conversions finished.');

The same pattern works with worker pool libraries; the invariant to keep is one long-lived system per worker.

Assets on servers

With no asset configuration, the engine and small support files load from the installed package. Built-in font files load lazily from Nutrient’s content delivery network (CDN) when a document needs them.

For an offline deployment, download the asset archive that matches the installed package while the machine has network access:

Terminal window
npx document-authoring download-assets --write-to ./document-authoring-assets

Transfer that directory unchanged to the server. Then pass its path when creating the system:

import path from 'node:path';
import { createDocAuthSystem } from '@nutrient-sdk/document-authoring/node';
const system = await createDocAuthSystem({
assets: {
base: path.resolve('document-authoring-assets'),
},
});

The system validates the local directory at startup and reports the first missing asset. Once validation succeeds, resources such as fonts and export templates load from this directory instead of Nutrient’s CDN, so document processing doesn’t require internet access.

You can also deploy the same browser asset archive on your own HTTP(S) server:

import { createDocAuthSystem } from '@nutrient-sdk/document-authoring/node';
const system = await createDocAuthSystem({
assets: {
base: 'https://assets.example.com/document-authoring/',
},
});

The asset archive must match the exact Document Authoring package version because its filenames are content-hashed. Configure your own fonts separately through fontConfig; see custom fonts.

Node.js support

Document Authoring supports Node.js 22 and 24. Edge runtimes (Cloudflare Workers, Vercel Edge Functions, and similar) and alternative Node-compatible runtimes aren’t supported; use a standard Node.js runtime.

Next steps

Node.js getting started
Install the package and process your first document.

Programmatic editing
Transactions and the document editing API.