How to Generate a PDF from a Vue.js Page
Export a rendered Vue page or component to PDF with html2pdf.js, handle pagination and assets, and choose a client-side, server-side, or API workflow.
To download an existing rendered Vue page or component as a PDF in the browser, select its DOM element and pass it to html2pdf.js. Wait for Vue to render the intended content and for fonts and images to load, then configure the filename, margins, page breaks, and page size. For Node.js generation or precise document layout, use a PDF document renderer instead: html2pdf.js explicitly does not run in Node.js. html2pdf.js documentation
Choose the right PDF approach
| Approach | Use it when | Tradeoff |
|---|---|---|
html2pdf.js |
The user wants to export a rendered element or page from the browser. | Convenient for DOM capture; it uses html2canvas and jsPDF and does not run in Node.js. Project documentation |
| Vue wrapper | You want Vue component props and events around the same DOM conversion workflow. | vue3-html2pdf wraps html2pdf.js, so the underlying browser rendering and layout constraints still apply. Check its current Vue compatibility before adopting it. Package documentation |
| Vue PDF document components | You are authoring a report or invoice as a PDF document rather than exporting the current DOM. | Requires document-oriented components, but Vue PDF documents a Node.js renderToFile flow as well as a browser API. Getting started · Browser API |
| Hosted HTML-to-PDF API | Generation should run through a backend or remote service. | Adds a service dependency; check privacy, pricing, and operational requirements. Example API documentation |
| Browser print dialog | Users can print or save as PDF and a simple workflow is sufficient. | Use print CSS and validate the result in the target browsers. |
This guide uses html2pdf.js for the live-DOM workflow. Its output depends on the html2canvas and jsPDF conversion pipeline, so check pagination and visual details with the actual content and browsers your app supports. Options documentation
Install html2pdf.js
Install the library in your Vue project:
npm install html2pdf.js
Import it only in client-side code. If your app uses server-side rendering, do not access window, the DOM, or the library during server rendering; load it from an event handler or a client-only component. The project documents html2pdf.js as a browser-only workflow. Project documentation
Export a Vue component with Vue 3
Put a template ref on the exact content to export. The example below waits for Vue’s next render, loads images and fonts in that region, and then starts the download.
<script setup>
import { nextTick, ref } from 'vue'
import html2pdf from 'html2pdf.js'
const report = ref(null)
const exporting = ref(false)
async function waitForImages(root) {
const images = [...root.querySelectorAll('img')]
await Promise.all(images.map((img) => {
if (img.complete) {
return img.decode?.().catch(() => {})
}
return new Promise((resolve) => {
img.addEventListener('load', resolve, { once: true })
img.addEventListener('error', resolve, { once: true })
})
}))
}
async function downloadPdf() {
await nextTick()
const element = report.value
if (!element || exporting.value) return
exporting.value = true
try {
if (document.fonts?.ready) await document.fonts.ready
await waitForImages(element)
await html2pdf().set({
margin: [12, 12, 12, 12],
filename: 'monthly-report.pdf',
image: { type: 'jpeg', quality: 0.95 },
pagebreak: { mode: ['css', 'legacy'] },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).from(element).save()
} finally {
exporting.value = false
}
}
</script>
<template>
<button type="button" :disabled="exporting" @click="downloadPdf">
{{ exporting ? 'Preparing PDF…' : 'Download PDF' }}
</button>
<article ref="report" class="report">
<h1>Monthly report</h1>
<p>This is the rendered Vue content included in the PDF.</p>
</article>
</template>
<style>
.report {
background: white;
color: #111;
padding: 16px;
}
@media print {
.page-break-before {
break-before: page;
}
.keep-together {
break-inside: avoid;
}
}
</style>
The example uses the html2pdf().set(options).from(element).save() workflow documented by the project. The library exposes margin, filename, image, page-break, html2canvas, and jsPDF options; consult the package docs for the option shape supported by the version you install. Project documentation · Package documentation
Vue 2 adaptation
Vue 2 uses a different component setup, but the essential steps are the same: wait for the DOM update with this.$nextTick(), get the element through a ref, then pass it to html2pdf.js.
import html2pdf from 'html2pdf.js'
export default {
data() {
return { exporting: false }
},
methods: {
downloadPdf() {
this.$nextTick(async () => {
const element = this.$refs.report
if (!element || this.exporting) return
this.exporting = true
try {
await html2pdf().set({
filename: 'report.pdf',
margin: 12,
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
pagebreak: { mode: ['css', 'legacy'] }
}).from(element).save()
} finally {
this.exporting = false
}
})
}
}
}
<button :disabled="exporting" @click="downloadPdf">Download PDF</button>
<section ref="report">...report content...</section>
For older Vue projects, verify that the chosen wrapper or package supports the project’s Vue version. A wrapper changes the Vue-facing API, not the HTML-to-canvas conversion underneath. vue3-html2pdf documentation
Control page size, margins, and page breaks
Use jsPDF options for paper format and orientation, and the top-level margin option for whitespace around the captured content. The documented html2pdf.js options also include image settings, link handling, html2canvas configuration, and page-break controls. Package documentation
| Need | Configuration direction |
|---|---|
| Standard paper | Set jsPDF.format to a supported format such as a4 or letter. |
| Landscape report | Set jsPDF.orientation to landscape. |
| Even margins | Use a number for the same margin on each side, or an array for individual sides as in the example. |
| Keep a section together | Add a class such as keep-together and use CSS break-inside: avoid; test whether the conversion pipeline honors the layout as expected. |
| Start a section on a new page | Add a page-break class and configure pagebreak.mode; validate the resulting pages. |
| Image format and quality | Set the image type and quality. JPEG is useful for photographic content; test image quality and file size with your content. |
Page-break CSS support is not a guarantee that every complex layout will paginate as intended. Long tables, large elements, flex and grid layouts, and content near page boundaries need inspection in the resulting PDF. Split exceptionally long reports into logical sections if page placement needs to be predictable.
Prepare Vue content and assets before capture
- Render the final state. Await
nextTick()after changing data, opening sections, or applying filters. Export only when asynchronous data has also finished loading. - Wait for fonts and images. Use
document.fonts.readyand wait for image load/decode before conversion. An image that fails to load should be handled deliberately rather than holding the export forever. - Use a stable export region. Put the ref around the report content, not the entire app shell. Keep buttons, navigation, and transient loading indicators outside it.
- Style for paper. Give the export region a solid background and explicit text colors. Avoid relying on viewport-specific positioning or content that appears only on hover.
- Check remote assets. Cross-origin images may be unavailable to canvas conversion unless the remote server permits access. The
useCORSoption can request CORS-enabled loading; it cannot grant permission when the asset server does not allow it.
Server-side PDFs and authored documents
Do not run html2pdf.js in Node.js: its project README says it does not support Node. Project documentation For a report that needs to be generated without an open browser, or whose layout is fundamentally a document, use a renderer designed for that execution model.
- Vue PDF: author the output using PDF document components. Its docs describe a Node.js
renderToFilefunction and a browser API. This means building PDF structure, not taking a picture of the live Vue DOM. Getting started · Browser API - Hosted HTML-to-PDF API: send a URL or HTML through a service when centralized generation fits your app. This adds an external dependency and requires a privacy and cost review. HTML2PDF.app documentation
- Print to PDF: provide print styles and let the user’s browser handle the save or print action. This is a low-setup choice when users can complete the interaction themselves.
Choose based on the input you need to preserve: a live DOM element, an authored PDF document, or HTML rendered by a remote service. These approaches are not interchangeable, so test the workflow with the real report content.
Or skip the browser setup
If you need a PDF of a public page by URL, ScreenshotNeo can return a PDF from one API request. It is a website screenshot and PDF API by ScreenshotNeo, and its options are documented at ScreenshotNeo docs. This captures a URL, rather than exporting a private, authenticated Vue component already rendered in a user’s browser.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/report \
-d format=pdf \
-o report.pdf
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com/report",
"format": "pdf",
},
timeout=90,
)
r.raise_for_status()
with open("report.pdf", "wb") as output:
output.write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/report',
format: 'pdf'
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`)
const bytes = Buffer.from(await res.arrayBuffer())
await import('node:fs/promises').then(fs => fs.writeFile('report.pdf', bytes))
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free for ScreenshotNeo.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Reference is null or the PDF is empty | The ref is not attached yet, the conditional content is absent, or Vue has not rendered updated data. | Check the ref, ensure the export region is rendered, await nextTick(), and wait for the data request that populates it. |
| Library fails during SSR or build | Browser-only code is being evaluated on the server. | Import or invoke html2pdf.js only from client-side code, such as the click handler or a client-only component. For Node generation, choose a Node-capable PDF approach. |
| Images are missing | Assets have not loaded, URLs are invalid, or cross-origin policy prevents canvas access. | Wait for image loading; inspect the network request; use assets that permit CORS where needed. useCORS does not bypass server policy. |
| Fonts differ from the page | Web fonts were not ready when capture began or could not be fetched. | Wait for document.fonts.ready, verify font requests, and provide a suitable fallback. |
| Text is blurry | The canvas capture scale is too low for the output size. | Try a higher html2canvas.scale, then inspect file size and memory use. Higher scale requires more browser memory. |
| Content is cut off or awkwardly split | A large element crosses a page boundary, or the layout does not translate cleanly to the PDF pages. | Adjust page-break rules, margins, or report structure; try a print-friendly layout and inspect every page. |
| Background colors are absent | The capture region relies on transparent or inherited backgrounds. | Set an explicit background and foreground color on the export region. |
| Download starts twice | Multiple clicks launched concurrent conversions. | Disable the button while exporting, as in the example. |
| Output is unexpectedly large or slow | Large canvas dimensions, high scale, or many images increase conversion work and output size. | Capture only the needed element, reduce scale or image quality where acceptable, and avoid oversized source images. |
Performance, reliability, and cost
- Browser resources: DOM-to-canvas conversion consumes client memory and processing time. Full-page reports and high-resolution scale settings increase that work; select only the content that belongs in the PDF.
- Layout reliability: Conversion recreates visible page content through a canvas-based pipeline. It is not a promise of identical output across every CSS feature or browser. Validate representative reports and target browsers, especially page breaks, fonts, and cross-origin images. html2pdf.js project
- Failure handling: Keep the export button state clear, catch errors for user feedback, and allow retry. Avoid changing report state during capture.
- Cost: html2pdf.js is a client-side package; this workflow does not make a hosted conversion request. Server renderers and hosted APIs have their own deployment or service costs, which should be checked for the selected option. No comparative price or speed figures are established by the cited documentation.
- Privacy: Browser-side conversion keeps this conversion in the user’s browser. A hosted API requires sending the page URL or HTML to that service, so review whether the content is appropriate to send and how authentication is handled.
FAQ
Can I export just one Vue component?
Yes. Put a ref on the component’s root export element and pass that element to html2pdf.js.
Can I make a PDF on the server from a Vue page?
Not with html2pdf.js in Node.js. Use a Node-capable PDF renderer such as the documented Vue PDF render-to-file flow, or a hosted HTML-to-PDF service.
Should I use a Vue wrapper?
Use one if its Vue props and events fit your integration, but it still uses the html2pdf.js conversion path. Verify package compatibility for your Vue version before installing.
Is a PDF from the DOM searchable?
The cited documentation does not establish a text-searchability guarantee. Inspect the produced file with your actual content and requirements before relying on searchable text or accessibility behavior.


