Skip to main content

Render outside Nitro

Compile PDF templates once and render the generated registry in a Node worker.

Use the standalone build when a Node worker owns document rendering outside Nitro. Keep the Nuxt module for authoring and preview. Both paths use the same compiler, resource admission, registry, and PDF engine.

Build the registry

Keep trusted templates in pdfs/ at the application root. Import application helpers explicitly with relative paths. The standalone build does not read Nuxt configuration, aliases, auto-imports, or layer configuration. PDF primitives, definePdf, and usePdfPageNumbers remain available in templates. Package imports must provide compiled JavaScript. Do not import source-only packages or add runtime filesystem reads to templates or their helpers.

scripts/build-pdfs.mjs
import { buildPdfRegistry } from '@lupinum/nuxt-pdf/build'

await buildPdfRegistry({
  rootDir: process.cwd(),
  outDir: './generated/pdfs',
  fonts: [{ family: 'Invoice Sans', src: 'Roboto-Regular.ttf' }],
  limits: { maxPages: 100, maxOutputBytes: 8_000_000 },
})

Put the configured font in pdfs/fonts/. Put local images in pdfs/assets/. Remove the fonts option if the templates use only built-in fonts.

Run this script before the backend build:

bash
node scripts/build-pdfs.mjs

The output directory contains index.mjs, index.d.mts, a types/ declaration tree, and an ownership marker. Deploy the generated runtime and its package dependencies. Keep the declarations for backend type checking. No Vue source, test loader, or build compiler is needed to execute this generated registry. The build tools remain package dependencies but are not imported by the runtime.

Use a dedicated output directory inside the application. Never put hand-written files in it. Successful rebuilds replace its generated contents. Compilation failures preserve the previous output. An existing nonempty directory without the ownership marker is rejected. Choose another directory after this error. Do not run concurrent builds into one output directory.

Render validated data

The generated pdf, renderPdf, getPdfTemplate, and pdfTemplateKeys exports have the same behavior as the Nitro registry. Import the .mjs path. Ordinary TypeScript resolves its adjacent declarations.

workers/render-invoice.ts
import { pdf } from '../generated/pdfs/index.mjs'

export async function renderInvoice() {
  const result = await pdf.invoice.render({
    customer: 'Example customer',
    number: '2026-001',
    lines: [],
  })
  return result.toUint8Array()
}

Match the props to your own invoice template. Validate request data and check authorization before rendering. Props are statically typed, not a runtime validation schema. Never accept template source, filesystem paths, build options, or unrestricted image URLs from a caller.

Local assets and configured fonts are validated and embedded during the build. Remote images remain denied unless the build supplies an explicit remote policy. Existing resource limits and error codes apply to each render. Fix the named resource or lower input size after an admission or limit error.

The application owns jobs, snapshots, storage, document issuance, and delivery. For an issued document, store and reuse completed bytes. Two fresh renders are not promised to be byte-identical.

Verify the deployment

Standalone Node rendering is tested locally with embedded fonts and images, Unicode text, pagination, and concurrent requests. That is not a deployed Convex or other serverless-runtime certification. Edge workers remain unsupported.

Before adoption, measure your largest expected document on the target runtime. Check cold and warm duration, memory use, output limits, failed asset requests, and concurrent isolation. Keep the previously tested Node route available for new renders until this proof passes. Do not regenerate issued PDFs during a runtime migration.

See deployment guidance for the runtime matrix and operational limits.