Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
NestJS

How to Generate and Download PDFs With Puppeteer in NestJS and React

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

Generate the PDF on the NestJS server, not in the browser: launch Puppeteer, load a stable HTML page or template, wait for data and fonts, call page.pdf(), and return the resulting bytes with Content-Type: application/pdf and a download disposition. In React, request the endpoint as binary data, create a Blob URL, click a temporary download link, and revoke the URL.

This arrangement keeps Chromium, credentials, layout decisions, and PDF generation under server control while giving the user a normal browser download.

The request flow

  1. React calls GET /reports/:id/pdf (use POST when the document depends on a submitted payload).
  2. NestJS authenticates the caller and validates the report identifier or payload.
  3. A PDF service launches Puppeteer, opens a page, loads the required data and fonts, and calls page.pdf().
  4. The controller returns the bytes as a StreamableFile, or forwards a stream for a large or already-persisted PDF.
  5. React receives the binary response and downloads it through a temporary Blob URL.

Build the NestJS PDF service

Install Puppeteer

Install Puppeteer in the NestJS application. The package downloads a compatible Chromium build unless your deployment is configured to use an existing browser.

npm install puppeteer

Render HTML into a PDF

page.pdf() returns a Promise<Uint8Array> and uses print CSS media by default. The service below waits for network activity to settle and for web fonts to finish loading, prints backgrounds, and lets the document’s @page rules control the paper size.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Epson EcoTank ET-2800 Wireless Color All-in-One Supertank Printer - Black
  • INNOVATIVE CARTRIDGE-FREE PRINTING — No more dealing with lots of tiny ink cartridges; With this wireless document and photo printer each ink bottle set is equivalent to about 90 individual cartridges²
  • LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; When you choose this combination printer, scanner and copier you can print up to 4,500 pages black/7,500 color³
  • COLOR PRINTING — Up to 2 years of ink in the box4 (and with every replacement ink set) for fewer out-of-ink frustrations
  • ZERO CARTRIDGE WASTE — By using an Epson EcoTank printer you can help reduce the amount of cartridge waste ending up in landfills
  • HOME PRINTER DESIGNED FOR RELIABILITY — The Epson EcoTank ET-2800 All-in-One Supertank Color Printer creates vivid, detailed prints and documents thanks to Micro Piezo Heat-Free Technology; Fire off 10 ISO pages per minute1 to easily finish large jobs
import puppeteer from 'puppeteer';

export async function renderPdf(html: string): Promise<Uint8Array> {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'networkidle0' });
    await page.evaluate(() => document.fonts.ready);

    return await page.pdf({
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: {
        top: '16mm',
        right: '14mm',
        bottom: '16mm',
        left: '14mm',
      },
    });
  } finally {
    await browser.close();
  }
}

In a real application, replace the html argument with a server-rendered report template or navigate to a dedicated report route. Keep the browser.close() call in finally; otherwise a failed render can leave Chromium processes running.

Navigate to an application route instead of injecting HTML

For a React report page, create a URL that renders the report deterministically, then use page.goto(). If that route requires authentication, pass a short-lived, least-privilege token through a secure mechanism or render a server-side template with data already authorized by NestJS. Do not put a user’s long-lived secret in page HTML.

await page.goto(`https://app.example.com/internal/reports/${reportId}`, {
  waitUntil: 'networkidle0',
});
await page.evaluate(() => document.fonts.ready);
const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  preferCSSPageSize: true,
});

Wait for an application-specific selector when data arrives after the initial network idle event. A fixed delay can be a fallback, but a selector or an explicit readiness flag is usually more reliable.

Return the PDF from a NestJS controller

NestJS’s StreamableFile accepts a byte array and lets you set the MIME type, attachment filename, and length. The filename should be generated from validated values so that quotes, line breaks, and path separators cannot reach the Content-Disposition header.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { Controller, Get, Param, StreamableFile } from '@nestjs/common';
import { renderPdf } from './pdf.service';

@Controller('reports')
export class ReportsController {
  @Get(':id/pdf')
  async download(@Param('id') id: string): Promise<StreamableFile> {
    // Validate id and authorize the current user before rendering.
    const html = await this.reportsService.renderHtml(id);
    const pdf = await renderPdf(html);
    const filename = `report-${id}.pdf`;

    return new StreamableFile(pdf, {
      type: 'application/pdf',
      disposition: `attachment; filename="${filename}"`,
      length: pdf.byteLength,
    });
  }
}

If you use an injected service, add it to the controller constructor and keep data lookup outside the Puppeteer page. A lookup or rendering error that occurs before headers are sent should become a clear 4xx or 5xx response rather than a successful-looking empty file.

Buffer or stream: choose deliberately

Approach Use it when Trade-off
Uint8Array with StreamableFile Ordinary reports generated on demand Simple API, but the complete PDF occupies application memory before the response starts
Readable stream Large documents or a PDF already available from storage or another service Lower buffering pressure; stream errors and response lifecycle need careful handling
Persisted file plus signed URL Repeated downloads, background generation, or long-lived archives Requires storage lifecycle, access checks, cache policy, and expiring links

NestJS documents forwarding an upstream stream by requesting it with responseType: 'stream' and wrapping the result in StreamableFile. For generated PDFs, queueing the render and storing the result can prevent a burst of Chromium launches from competing with normal API traffic.

Preserve React and CSS styling

Print media versus screen media

Puppeteer generates PDFs with the print CSS media type by default. That is correct for print-specific layouts, but it can remove screen-only rules. Call await page.emulateMediaType('screen') before page.pdf() when the PDF should match the screen design.

Rank #2
Sale
Epson EcoTank Photo ET-8550 Wireless Wide-Format All-in-One Tank Printer
  • CARTRIDGE-FREE PRINTING — Print lab-quality photos, graphics and creative projects; Get vibrant colors and sharp text with Epson's high-accuracy printhead and Claria ET Premium 6-color inks
  • INK BOTTLES — Save on photos1 and creative projects with affordable in-house printing; All-in-one printer allows you to print 4" x 6" photos for about 4 cents each vs. 40 cents with traditional ink cartridges1
  • LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; Printer, scanner and copier lets you print up to 6,200 color pages³
  • PRINT FOR LONGER — Up to 2 years of ink in the box² (and with every replacement ink set) for fewer out-of-ink frustrations with this wireless printer
  • ZERO CARTRIDGE WASTE — Epson EcoTank printer helps reduce the amount of cartridge waste ending up in landfills; Cartridge-free printer uses high-yield ink bottles; Each replacement ink bottle set is equivalent to about 100 individual ink cartridges⁴
await page.emulateMediaType('screen');
const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  preferCSSPageSize: true,
});

Control paper size, breaks, and colors in CSS

@page {
  size: A4;
  margin: 16mm 14mm;
}

.report {
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

.page-break {
  break-before: page;
}

.avoid-split {
  break-inside: avoid;
}

Use printBackground: true for colored panels, charts, and backgrounds. Use preferCSSPageSize: true when @page owns the dimensions; otherwise select a named format such as A4 in the PDF options. Headers, footers, and page ranges are also available PDF options when the report needs them.

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.

Wait for data, images, and fonts

  • Use waitUntil: 'networkidle0' only when the page can become idle; analytics or long polling may prevent it.
  • Wait for a report-specific selector, such as [data-pdf-ready], after client-side rendering.
  • Await document.fonts.ready so text does not reflow after the PDF is created.
  • Ensure images are reachable from the server and use stable dimensions to avoid layout shifts.

Download the file in React

Fetch the endpoint as a binary response. The example below preserves cookies for a same-origin authenticated API, checks the HTTP status, creates a Blob URL, and releases it after the click.

async function downloadReport(id: string) {
  const response = await fetch(`/api/reports/${id}/pdf`, {
    credentials: 'include',
  });

  if (!response.ok) {
    throw new Error(`PDF request failed: ${response.status}`);
  }

  const blob = await response.blob();
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = `report-${id}.pdf`;
  document.body.appendChild(anchor);
  anchor.click();
  anchor.remove();
  URL.revokeObjectURL(url);
}

If the API is on another origin, configure CORS for the exact frontend origin and decide explicitly whether credentials are allowed. Do not expose an arbitrary file URL without an authorization check. If you want the server-provided filename, expose Content-Disposition through CORS and parse it carefully; a fixed, validated client-side filename is often safer.

Handle large files and stored PDFs

For a generated document that can become large, move rendering to a queue worker and store the completed file. The request can return a job identifier, while a later authenticated request streams the stored PDF. Configure attachment disposition, cache-control, range support, and expiring signed URLs according to the storage system. For a proxied upstream PDF, request a stream and pass it through without first collecting the whole body.

const upstream = await this.httpService.axiosRef.get(pdfUrl, {
  responseType: 'stream',
  timeout: 90_000,
});

return new StreamableFile(upstream.data, {
  type: 'application/pdf',
  disposition: 'attachment; filename="external-report.pdf"',
});

Only proxy URLs that your server has validated. Otherwise the endpoint can become a server-side request forgery path.

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

Security and operational safeguards

  • Authorize the report before launching Chromium, and re-check ownership when loading persisted data.
  • Validate IDs, query parameters, and filenames. Never concatenate untrusted values into HTML, JavaScript, or response headers.
  • Keep browser pages isolated per request and close the browser in a finally block.
  • Set request and navigation timeouts appropriate to your deployment, and queue expensive renders so they cannot exhaust CPU or memory.
  • Do not let users submit arbitrary URLs to an unrestricted page.goto(); use an allowlist or render trusted templates.
  • Return an explicit error when data lookup, authentication, or rendering fails before headers are sent.

Troubleshooting common failures

The PDF is blank or missing report data

The page was captured before the React app finished rendering, or the route redirected to a login page. Confirm the URL and credentials, wait for a report-ready selector, and inspect the page content in a temporary diagnostic render.

Colors or backgrounds disappear

Print media rules may be intentionally removing them. Try emulateMediaType('screen'), set printBackground: true, and add print-color-adjust: exact (with the WebKit prefix for Chromium).

Rank #3
HP Smart Tank 5000 Wireless All-in-One Ink Tank Printer, Scanner, Copier with 2 Years of Ink Included, Best-for-Home, Cartridge-Free, Refillable and AI-Enabled. (5D1B6A)
  • SET IT UP ONCE AND PRINT WITH CONFIDENCE. No complicated maintenance. Just easy, reliable printing you can count on.
  • INK FOR YEARS. NOT MONTHS. Up to 2 years of ink included. Get thousands of pages of cartridge-free printing. More pages, less hassle
  • KEEPS PRINTING WELL AFTER COMPETITORS HAVE QUIT. No complex maintenance. Sharper text, richer colors.[2] Only with HP Smart Tank
  • PREMIUM SUPPORT - Strong technical expertise to solve issues faster
  • THE LAST PRINTER YOU'LL EVER NEED. Enjoy years of refillable, cartridge-free printing.

Fonts are wrong or text moves between pages

External fonts may not have loaded when page.pdf() runs. Make the font host reachable from the server, await document.fonts.ready, and avoid relying on a font that is available only on a developer workstation.

networkidle0 never resolves

Long polling, analytics, or a WebSocket can keep the network busy. Navigate with a less strict readiness condition, then wait for a specific selector or application flag instead.

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

The browser fails in production

Container images may lack Chromium’s shared libraries or may disallow the sandbox. Install the dependencies required by your chosen Puppeteer build and use launch flags only when your deployment’s security model requires them. The exact launch options depend on the environment.

The browser downloads a file but React reports an error

Inspect the status code and response headers before calling blob(). A JSON authentication or validation error can otherwise be saved with a .pdf filename. Also verify CORS and credential settings for cross-origin requests.

Performance, reliability, and cost considerations

Chromium startup is considerably more expensive than serializing an already-rendered file. Reuse a controlled browser process or a worker pool where your deployment permits it, but create a fresh page for each request and cap concurrency. Queueing protects the API when several users request reports simultaneously. Cache immutable reports or persist generated files when the same document is downloaded repeatedly.

Measure render duration, navigation failures, browser crashes, output size, and queue depth. Set a timeout and return a useful status to the caller rather than holding an HTTP connection indefinitely. There is no universal timeout or concurrency number: CPU, memory, page complexity, fonts, and deployment limits determine safe values.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 API and MCP server when you need a clean capture or PDF without operating Puppeteer yourself. One GET request returns a PNG, JPEG, WebP, or PDF; the API can load lazy images, wait for a selector, delay, or network idle, apply custom CSS and JavaScript, set cookies and headers, choose a device or viewport, and capture a full page or a CSS-selected element.

Rank #4
NDYIN Portable Printers Wireless for Travel, N80 Bluetooth Thermal Printer
  • Wireless Bluetooth Printer: Portable thermal printer compatible with iPhone, Android phones, iPad and tablet computers via Bluetooth. For smartphones, please download the "Nada Print" App. You can also connect to laptops and computers for printing using a USB-C cable. (Note: Laptops and computers can only be connected via USB and require the installation of a driver first. Bluetooth connection is not supported.)
  • No-ink printing: Only supports US Letter and A4 size thermal paper.(Doesn't support regular paper) The no-ink portable thermal printer uses direct thermal technology, requiring no ink, toner or ribbons, making it environmentally friendly, cost-effective and time-saving. The thermal printer package comes with a roll of US Letter thermal printing paper. Note: When installing the paper, remember to switch the paper size switch on APP
  • Clear Print: NDYIN N80 portable thermal printer adopts high-definition printing technology, with a 203DPI resolution to provide you with clear printing results. This mobile printer is compatible with roll paper, folded paper and tattoo transfer paper, supporting printing from your mobile phone PDF, Word, pictures and web pages anytime and anywhere. It is recommended to use our NDYIN thermal paper to achieve good printing quality
  • Portable wireless printer for travel: The thermal printer is equipped with a built-in 1500mAh rechargeable battery, which can print 160 sheets of 8.5" x 11" thermal paper after being fully charged. It weighs only 1.5 pounds and is compact in size. This ink-free portable printer can be easily carried in a backpack or briefcase! It is perfect for business travel, cars, small offices, construction sites, schools and homes. You can print documents, contracts, invoices and boarding passes anytime and anywhere
  • The N80 thermal printer has a wide range of uses. The package includes the N80 printer, a roll of US Letter paper(7m/roll), a user manual, a guide card, a type-C soft cable and a type C adapter. Note: The charging adapter is not included. Special thermal paper is required for use; ordinary paper cannot be used. This ink-free portable thermal printer is suitable for various scenarios such as home, school, travel, office, and outdoor, meeting the printing needs of different groups of people. This tattoo template printer is also compatible with tattoo transfer paper, making it an ideal choice for tattoo art

For a PDF or image capture, use the API call documented at https://screenshotneo.com/docs/:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Before capture it accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it.

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

FAQ

Can I use a POST endpoint instead of a report ID?

Yes. Accept the submitted, validated payload in NestJS, render from that trusted data, and keep authorization and size limits on the server before starting Chromium.

Should I send the PDF inline or as an attachment?

Use an attachment disposition for an automatic download. Use inline disposition when your product intentionally opens the PDF in a browser viewer; keep the MIME type application/pdf in either case.

Can one report contain several page sizes?

Chromium can follow CSS page rules and PDF options, but mixed-size layouts need deliberate testing because page breaks, headers, and margins interact with the selected paper configuration.

Frequently Asked Questions

Can I use a POST endpoint instead of a report ID?

Yes. Accept the submitted, validated payload in NestJS, render from that trusted data, and keep authorization and size limits on the server before starting Chromium.

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

Should I send the PDF inline or as an attachment?

Use an attachment disposition for an automatic download. Use inline disposition when your product intentionally opens the PDF in a browser viewer; keep the MIME type application/pdf in either case.

Can one report contain several page sizes?

Chromium can follow CSS page rules and PDF options, but mixed-size layouts need deliberate testing because page breaks, headers, and margins interact with the selected paper configuration.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.