Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Paper size, margins and backgrounds

  • format: 'A4' selects a standard paper size; use explicit width and height when your document has a custom canvas.
  • margin reserves printable space. Coordinate it with @page rules and any header or footer design.
  • printBackground: true preserves background colors and images that would otherwise be omitted.
  • preferCSSPageSize: true lets 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Python

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.