Node API
renderToBuffer
Renders a PDF document and resolves with a Node Buffer. Use this when you need the bytes in memory — to upload them, attach them to an email, or hand them to another library.
Signature
ts
function renderToBuffer(
element: Component,
props?: Record<string, unknown>
): Promise<Buffer>Usage
ts
import { renderToBuffer } from '@vuepdf/renderer'
import MyDocument from './MyDocument.vue'
const buffer = await renderToBuffer(MyDocument)
// Write it, upload it, or attach it
await fs.promises.writeFile('out.pdf', buffer)Attaching to an Email
ts
import { renderToBuffer } from '@vuepdf/renderer'
import Invoice from './Invoice.vue'
const buffer = await renderToBuffer(Invoice)
await transporter.sendMail({
to: 'customer@example.com',
subject: 'Your invoice',
attachments: [{ filename: 'invoice.pdf', content: buffer }],
})Nuxt Server Route
Returning a Buffer from an event handler is enough — Nitro sends it as-is. Because you hold the whole document in memory you can set content-length and force a download:
server/api/invoice/[id].get.ts
// server/api/invoice/[id].get.ts
import { renderToBuffer } from '@vuepdf/renderer'
import InvoiceDocument from '../../../components/pdf/InvoiceDocument.vue'
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')!
const record = await useDb().findInvoice(id)
if (!record) {
throw createError({ statusCode: 404, statusMessage: 'Invoice not found' })
}
const buffer = await renderToBuffer(InvoiceDocument, {
id,
total: record.total,
})
setHeader(event, 'content-type', 'application/pdf')
setHeader(event, 'content-length', buffer.length)
setHeader(event, 'content-disposition', `attachment; filename="invoice-${id}.pdf"`)
return buffer
})components/pdf/InvoiceDocument.vue
<!-- components/pdf/InvoiceDocument.vue -->
<script setup lang="ts">
import { Document, Page, Text } from '@vuepdf/renderer/components'
const props = defineProps<{
id: string
total: string
}>()
</script>
<template>
<Document :title="`Invoice ${props.id}`">
<Page size="A4" :style="{ padding: 48, fontSize: 12 }">
<Text :style="{ fontSize: 22, marginBottom: 16 }">
Invoice {{ props.id }}
</Text>
<Text>
Total due: {{ props.total }}
</Text>
</Page>
</Document>
</template> Keep the PDF primitive imports in the document SFC, not the server route. In server routes, import the document component and pass props as the second argument to
renderToBuffer. Importing a .vue document from server/ requires the @vuepdf/nuxt module — see renderToStream for why. Caching the Result
Rendering is CPU-bound. If the document changes rarely, wrap it in defineCachedFunction so repeat requests serve stored bytes:
server/api/report.get.ts
// server/api/report.get.ts
import { renderToBuffer } from '@vuepdf/renderer'
import ReportDocument from '../../components/pdf/ReportDocument.vue'
// Nitro's cache stores JSON, which does not survive a Buffer — keep base64
// in the cache and rehydrate on the way out.
const buildPdf = defineCachedFunction(
async () => (await renderToBuffer(ReportDocument)).toString('base64'),
{ maxAge: 60 * 60, name: 'report-pdf', getKey: () => 'latest' },
)
export default defineEventHandler(async (event) => {
const buffer = Buffer.from(await buildPdf(), 'base64')
setHeader(event, 'content-type', 'application/pdf')
setHeader(event, 'content-length', buffer.length)
return buffer
}) Do not cache the
Buffer itself. Nitro's cache serializes to JSON, so a cached Buffer comes back as { type: 'Buffer', data: [...] } — the first request succeeds and every cache hit afterwards fails with ERR_HTTP_INVALID_HEADER_VALUE on content-length. Store base64 and rehydrate, as above. Choosing Between the Node APIs
| Prop | Type | Default | Description |
|---|---|---|---|
renderToBuffer | Promise<Buffer> | — | You need the bytes in memory — uploads, email attachments, further processing. |
renderToStream | Promise<ReadableStream> | — | You are piping to an HTTP response or a file and want to avoid buffering the whole document. |
renderToFile | Promise<void> | — | You just want the PDF written to a path on disk. |
This is a Node-only API. Calling it in a browser build throws — use pdf() and
toBlob() there instead.