Skip to main content

Registry and render result

The typed #pdf server module, runtime dispatch, and completed render conversions.

Nuxt generates an inspectable #pdf module from the discovered templates. It is a server-only module for Nitro code and is typed from each SFC's props. Do not import it from a client component or other browser code.

ts
import { pdf, renderPdf, pdfTemplateKeys } from '#pdf'

pdf

pdf is an object keyed by template name. Property access infers each SFC's props. Every value has exactly three public members:

ts
interface PdfTemplate<Props extends object> {
  readonly key: string
  resolveMetadata(props: Props): {
    title?: string
    filename?: string
    language?: string
  }
  render(props: Props): Promise<PdfRenderResult>
}

The handle does not expose the compile-time definition, preview data, source file, or a second preview render method.

ts
await pdf.invoice.render({ invoice })
await pdf.report.render({ report })

Nested template paths keep their slash keys (for example pdf['reports/monthly']).

renderPdf

renderPdf(name, props) is the functional form. A literal template name infers its props:

ts
await renderPdf('invoice', { invoice })

A runtime string cannot be type-checked, so it requires the explicit unknown-props escape hatch:

ts
await renderPdf(templateName, untypedProps, { unsafe: true })
{ unsafe: true } does not validate the props; it only makes the loss of static typing visible at the call site. An unknown template name still fails with PDF_TEMPLATE_NOT_FOUND.

pdfTemplateKeys is the array of discovered template names.

getPdfTemplate(name) returns that typed three-member handle for a literal name, or undefined for an unknown name. Reach for it when you need resolved definePdf metadata without rendering:

ts
import { getPdfTemplate } from '#pdf'

const template = getPdfTemplate('invoice')
const { title, filename } = template.resolveMetadata(props)

resolveMetadata() evaluates the title and filename declared by definePdf, and returns its language. It does not mount the component, so an authored PdfDocument fallback is not part of this result.

NuxtPdfError and PDF_ERROR_CODES are also re-exported from #pdf, so server code can branch on render failures without extra imports. See Errors and limits.

The render result

Both pdf.name.render(props) and renderPdf(...) resolve to a PdfRenderResult. The document is rendered once; every conversion reuses that completed, immutable byte buffer:

ts
const result = await pdf.invoice.render({ invoice })

result.metadata        // frozen title, filename, and language for this render
await result.toUint8Array() // Promise<Uint8Array>
await result.toBuffer()     // Promise<Buffer>
await result.response()     // Promise<Response>

result.metadata is the frozen title, filename, and language resolved for this render: definePdf values win when present, otherwise authored PdfDocument fallbacks. title and language describe PDF Info fields; filename is response/download metadata and is not written into PDF Info. result.diagnostics is one frozen object shared with the development preview:

FieldMeaning
durationMsWall time for the whole public render, including metadata evaluation
byteLengthExact output byte count
pageCountNumber of rendered pages
passesLayout passes used (1 normally, more for a TOC)
layoutWarningsFrozen, structured warnings for layout that rendered but needs attention
registeredFontFacesFrozen family/weight/style facts; never source bytes or paths

Diagnostics contain no document content, props, or resource URLs. An application can send them to its metrics layer.

layoutWarnings is empty for a healthy layout. Nuxt PDF currently reports PDF_UNBREAKABLE_NODE_OVERFLOW when a non-fixed node cannot wrap and is taller than the page's usable content height. The warning identifies the page and primitive and includes both heights in points, without copying text or ids from the document. Allow the node to wrap or make it small enough to fit on one page.

Development and production boundary

The development preview keeps sampleData, named scenarios, and the relative source file outside the public template handle. It calls the same public render(props) method as application code and displays that completed result. There is no separate preview renderer or second document render.

Editing a template rebuilds its registry and refreshes the document with its diagnostics. The preview keeps the selected scenario.

In a production build the SFC transform reconstructs the render definition from only title, filename, language, and maxPasses. sampleData and scenarios are structurally omitted before Nitro bundles the template; they are not public handle properties and are not merely hidden behind runtime checks. The development preview data and routes are also absent. Packed-consumer checks verify this boundary and keep the renderer and template SFCs out of the client output.

Metadata is hoisted: inline values and imports are supported, while references to bindings declared locally in <script setup> are compile errors. Imported preview-data modules must be side-effect-free so their unused production imports can be eliminated.

response(init)

response() returns a Response with content-type: application/pdf, the exact content-length, and a sanitized, encoded-length-bounded content-disposition filename:

ts
await result.response({
  disposition: 'inline',      // 'attachment' (default) | 'inline'
  filename: 'invoice.pdf',    // sanitized before writing the header
  headers: { 'cache-control': 'no-store' },
})
FieldTypeDescription
disposition'attachment' | 'inline'content-disposition type; defaults to attachment
filenamestringDownload filename; sanitized before use; defaults to template metadata or document.pdf
headersHeadersInitExtra response headers merged in
status, statusTextnumber / stringStandard ResponseInit fields

Caller-provided content-type, content-length, and content-disposition headers are replaced with the truthful values from the completed result.

Regenerating the registry

The registry is written during module setup and regenerated whenever templates change in development. If #pdf imports appear untyped in your editor, run nuxt prepare once to refresh the generated types.