Docs/Nuxt integration

Nuxt integration

Use the same document components in a client preview or a server endpoint. Keep browser-only rendering behind a client boundary.

Configure the Vue compiler

Forme components emit custom document elements. Tell the compiler that forme-* tags are custom elements.

nuxt.config.ts

export default defineNuxtConfig({
  vue: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('forme-'),
    },
  },
})

Create a client preview

Wrap the preview in ClientOnly or put it in a .client.vue component. Import the browser renderer inside a client lifecycle hook so WASM initialization never runs during SSR. The rendering guide shows how to create the PDF bytes.

components/PdfPreview.client.vue

<script setup lang="ts">
import InvoiceMinimal from '@/components/pdf/blocks/invoice-minimal/InvoiceMinimal.vue'
import { onBeforeUnmount, onMounted, ref } from 'vue'

const pdfUrl = ref<string | null>(null)
const error = ref<string | null>(null)
let disposed = false

onMounted(async () => {
  try {
    const [{ serialize }, { init, renderSerializedDoc }, { default: wasmUrl }]
      = await Promise.all([
        import('@formepdf/vue'),
        import('@formepdf/core/worker'),
        import('@formepdf/core/pkg-web/forme_bg.wasm?url'),
      ])
    await init(wasmUrl)
    const document = await serialize(InvoiceMinimal)
    const bytes = await renderSerializedDoc(document)
    if (disposed) return
    pdfUrl.value = URL.createObjectURL(new Blob([new Uint8Array(bytes)], { type: 'application/pdf' }))
  } catch {
    if (!disposed) error.value = 'The PDF could not be rendered.'
  }
})

onBeforeUnmount(() => {
  disposed = true
  if (pdfUrl.value) URL.revokeObjectURL(pdfUrl.value)
})
</script>

<template>
  <p v-if="error" role="alert">{{ error }}</p>
  <a v-else-if="pdfUrl" :href="pdfUrl" download="invoice.pdf">Download invoice</a>
  <p v-else role="status">Rendering PDF…</p>
</template>

Return a PDF from an API route

This example targets a Node deployment with the Forme renderer installed. Edge runtimes need their own WASM setup. Validate incoming invoice data at the request boundary before passing it to your document.

server/api/invoice.get.ts

import { renderSerializedDoc } from '@formepdf/core'
import { serialize } from '@formepdf/vue'
import InvoiceMinimal from '../../app/components/pdf/blocks/invoice-minimal/InvoiceMinimal.vue'

export default defineEventHandler(async (event) => {
  const document = await serialize(InvoiceMinimal)
  const bytes = await renderSerializedDoc(document)

  setHeader(event, 'Content-Type', 'application/pdf')
  setHeader(event, 'Content-Disposition', 'attachment; filename="invoice.pdf"')
  return bytes
})

Keep themes separate from site styles

Nuxt color mode controls your website. A PdfcnTheme controls your PDF. Export a theme from the theme builder and pass it to a block or PdfcnThemeProvider.

Edit the theme tokens

Try the theme builder