Use a Nuxt server endpoint to launch a headless browser, open the rendered route, wait for the page to be ready, and call Puppeteer’s page.pdf(). Nuxt supplies the server route and response; Puppeteer supplies browser rendering and PDF output. This keeps the conversion on the server, where you can control authentication, timing, print CSS, paper size and download headers.
The example below is an implementation outline based on Nuxt and Puppeteer’s documented APIs. Treat browser launch flags, deployment limits and readiness selectors as runtime-specific settings that you must verify on your host.
Choose the rendering architecture first
A PDF conversion has two separate jobs:
- Rendering: Nuxt produces the HTML, CSS and client-side data for a route.
- Printing: Puppeteer opens that route in Chromium and turns the rendered page into PDF bytes.
A server endpoint coordinates both jobs. A browser-side library can work when the user’s current browser is the intended rendering environment, but it cannot provide the same server-controlled output, and it depends on browser APIs and user interaction.
| Approach | Use it when | Important constraint |
|---|---|---|
| Nuxt server endpoint + Puppeteer | You need a controlled, downloadable PDF from application routes | The runtime must be able to run a compatible browser process |
| Browser-side generation | The user should print the current browser view and browser-only APIs are required | Output varies with the user’s browser, permissions and loaded state |
| Separate PDF service | Your Nuxt host cannot run Chromium reliably | You must secure and monitor an additional service |
Prerequisites and hosting implications
Run Nuxt with server functionality
Create the endpoint under server/api and deploy Nuxt in a mode that includes its server runtime. Nuxt’s generate command pre-renders routes into plain HTML files; static output does not include server endpoints. Therefore a purely static deployment cannot execute the endpoint shown here. Keep the Nuxt server running, or send PDF jobs to a separate service.
#1 Best Overall
Check the Nuxt version lifecycle
The Nuxt 3 CLI documentation states that Nuxt 3 reached end of life on July 31, 2026, with no further bug fixes or security patches. That date is time-sensitive: verify the current support page before publishing or deploying, and consider Nuxt 4 for new work. The endpoint pattern is still the same conceptually, but package and runtime details can change between major versions.
Install Puppeteer
Install Puppeteer in the Nuxt project so the server route can import it:
npm install puppeteer
Puppeteer downloads or locates a Chromium build according to its version and environment. In containers and serverless runtimes, confirm that the browser binary is available, that required system libraries are installed, and that the platform permits child processes. The exact launch configuration is host-dependent.
Build a Nuxt PDF endpoint
1. Define a safe page target
Prefer a validated page identifier over accepting an arbitrary URL. A route such as /reports/[id] can load data using the same server-side authorization rules as the application. If you must accept a URL, allow-list its origin and reject private-network addresses to avoid turning the endpoint into a server-side request forgery proxy.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minute2. Add server/api/pdf.get.ts
The following code demonstrates the control flow. It is an integration outline rather than a tested copy-and-paste recipe; adapt Nuxt event helpers, browser launch options and authentication to your deployment.
Rank #2
import puppeteer from 'puppeteer'
import { getQuery, setHeader, createError } from 'h3'
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const id = typeof query.id === 'string' ? query.id : ''
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
throw createError({ statusCode: 400, statusMessage: 'Invalid report id' })
}
// Use your canonical, authenticated route—not an arbitrary user URL.
const target = `https://your-domain.example/reports/${encodeURIComponent(id)}`
const browser = await puppeteer.launch({
headless: true
// Add host-specific executablePath/args only when your runtime requires them.
})
try {
const page = await browser.newPage()
await page.goto(target, { waitUntil: 'networkidle0', timeout: 60000 })
// Have the page expose this flag after asynchronous data and images are ready.
await page.waitForSelector('[data-pdf-ready="true"]', { timeout: 30000 })
// page.pdf() uses print media by default. Remove this line to keep print media.
await page.emulateMediaType('screen')
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' },
preferCSSPageSize: true,
displayHeaderFooter: false
})
setHeader(event, 'Content-Type', 'application/pdf')
setHeader(event, 'Content-Disposition', `attachment; filename="report-${id}.pdf"`)
return pdf
} finally {
await browser.close()
}
})
Replace your-domain.example with the deployed origin. If the endpoint and page are on the same application, use a configured public origin rather than guessing from request headers. For protected pages, pass a short-lived token or establish a browser session explicitly; never expose privileged credentials in a client-supplied query string.
3. Mark the page ready
Network idle is not a universal indication that an application is complete. Add a marker only after the data needed in the PDF has rendered:
<div v-if="ready" data-pdf-ready="true"></div>
Set ready after your API calls, charts and other required assets finish. For a simpler page, waiting for a distinctive selector may be enough. Puppeteer’s guide shows navigation followed by page.pdf(); fonts are awaited by default, but your own asynchronous content still needs an explicit readiness strategy.
Recommended Free Tools
Control print layout deliberately
Print media versus screen media
Puppeteer’s page.pdf() uses print media by default. That allows print-specific CSS such as:
@media print {
.no-print { display: none !important; }
a { color: inherit; text-decoration: none; }
}
@page {
size: A4;
margin: 16mm 14mm;
}
.break-before { break-before: page; }
.avoid-break { break-inside: avoid; }
Call page.emulateMediaType('screen') before page.pdf() when the PDF should follow screen styles instead. Do not assume screen CSS is automatically better: test tables, charts, colors and page breaks in the intended output.
Rank #3
Paper size, margins and backgrounds
format: 'A4'selects a standard paper size; use explicitwidthandheightwhen your document has a custom canvas.marginreserves printable space. Coordinate it with@pagerules and any header or footer design.printBackground: truepreserves background colors and images that would otherwise be omitted.preferCSSPageSize: truelets a declared CSS page size take precedence where supported.- Headers and footers require Puppeteer’s header/footer options and print margins; leave them disabled unless you have designed and tested the templates.
Images, fonts and charts
Wait for application data and image elements that are populated after navigation. If a chart library animates, disable animation for the PDF state or wait for its completion. Keep fonts accessible from the server-rendered page and avoid expiring signed asset URLs before capture. A PDF that contains blank chart areas is usually a readiness or asset-loading problem, not a PDF API problem.
Return, cache and secure the result
Response headers
Return the bytes with Content-Type: application/pdf. Use Content-Disposition: attachment for a download or inline for browser viewing. Generate a filename from a validated identifier, not raw user input.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Authentication and authorization
Authorize the request before launching Chromium. The browser page must receive only the permissions required to render that report. Never allow arbitrary navigation to internal hostnames, cloud metadata addresses or local files.
Concurrency and cleanup
Always close the page and browser in a finally block. Launching a new browser for every request is simple but can be expensive under load. A production service may maintain a bounded browser or page pool, queue jobs and enforce per-request timeouts; those choices depend on your host and workload. Do not promise a universal throughput number without measuring your own pages and runtime.
Caching
Cache only when the document is safe to reuse and its inputs are part of the cache key. Include report ID, user or tenant scope, locale, date range and any revision that changes the output. Never serve one user’s authorized PDF from another user’s cache entry.
Deployment choices
| Deployment | PDF endpoint behavior | What to verify |
|---|---|---|
| Nuxt server on a VM or container | Usually the most direct model | Chromium dependencies, memory, process limits and graceful shutdown |
| Serverless function | Possible only when the platform supports the browser package and execution time | Bundle size, cold starts, timeout, temporary storage and concurrency limits |
| Static Nuxt hosting | Serves pre-rendered files but cannot run server/api/pdf.get.ts |
Move generation to a separate service or deploy Nuxt with a server runtime |
Troubleshooting
“Browser was not found” or launch failure
Cause: Chromium is absent, blocked, or missing operating-system libraries. Fix: install the browser dependencies required by your image, configure Puppeteer’s executable path when your host supplies one, and test the same container or runtime used in production.
The PDF is blank or missing data
Cause: the capture occurs before client-side data, charts or images finish. Fix: add a page-ready marker, wait for the specific selector, and inspect browser console and network errors. A fixed delay can be a fallback, but a deterministic readiness condition is safer.
Styles look different from the page
Cause: print media is the default, or print CSS hides elements. Fix: choose print intentionally, or call emulateMediaType('screen'); then adjust @media print, @page and background settings.
Navigation times out
Cause: a third-party request never settles, the route redirects to login, or the host is too slow. Fix: verify the target from the deployment environment, authenticate the browser session, set a bounded timeout, and wait for your own readiness selector instead of requiring global network idle when long-polling is present.
Fonts or images are missing
Cause: blocked asset requests, expired URLs, CORS or inaccessible private resources. Fix: make assets reachable to the server-side browser, inspect failed requests, and ensure the page uses the intended font-loading strategy.
Static deployment returns 404 for the API route
Cause: static generation does not include Nuxt server endpoints. Fix: deploy the Nuxt server or call a separate PDF service.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo provides a website screenshot and PDF API, so your application can request a rendered document without packaging Chromium into Nuxt. Its clean-shot workflow accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.
Request a PDF by changing the endpoint parameters documented at ScreenshotNeo’s API documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
For a PDF response, set the documented PDF output options for your request. The same API supports PNG, JPEG, WebP and PDF, plus full-page capture, CSS-selector element capture, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, timezone and geolocation. You can also use caching with a chosen TTL, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPython
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Sign up for the free ScreenshotNeo plan.
Operational checklist
- Run the endpoint in a Nuxt server deployment, not static-only hosting.
- Pin and regularly update Nuxt and Puppeteer versions after checking support status.
- Validate page identifiers and allow-list navigation targets.
- Implement an explicit page-ready signal for asynchronous content.
- Choose print or screen media deliberately and test paper size, margins, backgrounds and page breaks.
- Close browser resources on success, timeout and error.
- Measure memory, execution time and concurrency on your own workload.
- Return PDF content and download headers only after authorization succeeds.
Frequently Asked Questions
Can I generate a PDF from a Nuxt route in the browser without a server endpoint?
Yes, a browser-side approach is possible when the user’s browser should perform the print operation, but it does not provide the same server-controlled rendering and requires browser APIs. The server-endpoint pattern is preferable for consistent, automated downloads.
Why does Puppeteer print media by default?
Puppeteer’s PDF API uses print media by default. Call page.emulateMediaType('screen') before page.pdf() when the PDF must use screen media rules.
Does Nuxt static generation include PDF API routes?
No. Static generation writes pre-rendered HTML files; an endpoint under server/api requires a Nuxt server runtime or a separate service.
Free tools Windows power users keep installed
One-click scans. No signup required.
Is the Nuxt 3 support date permanent?
No. The July 31, 2026 end-of-life date comes from the Nuxt 3 CLI documentation available for this article and should be rechecked because lifecycle information can change.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

