Render the Rails view to an HTML string, load that markup in a Puppeteer page, call page.pdf(), and return the resulting bytes with Rails send_data. Use render_to_string inside a controller; use ApplicationController.renderer.render in a job or other code outside a controller action. The main integration detail is making sure the rendered document can resolve its assets and is ready before printing.
Choose where Rails renders the HTML
The browser needs HTML, not a Rails template path or Ruby object. Rails can produce that HTML string in either a request controller or a separate execution context.
Inside a controller action: render_to_string
Use render_to_string when the PDF action already has a request, current user, and controller context. It accepts the same rendering options as render, but returns the output instead of writing a normal response. See the Rails rendering guide.
html = render_to_string(
template: "invoices/show",
formats: [:html],
layout: "pdf"
)
Choose a PDF-specific layout if the ordinary page includes navigation, buttons, or other screen-only interface. To render without a layout, set layout: false. The template and layout should include the content and styling intended for print.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
- 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
Outside a controller action: ApplicationController.renderer
For a background job or another non-controller context, use Rails’ renderer API to create the string. A standalone renderer uses a mocked Rack request, so supply host and HTTPS settings when the view generates absolute route or asset URLs.
html = ApplicationController.renderer.new(
http_host: "example.com",
https: true
).render(
template: "invoices/show",
formats: [:html],
layout: "pdf",
assigns: { invoice: invoice }
)
Replace example.com and invoice with the host and record appropriate to your app. Renderer options and environment details are documented in the ActionController::Renderer API.
Keep HTML construction safe
Prefer normal templates and Rails helpers for structured content. Rails’ html: render option escapes entities unless the value is composed with HTML-safe APIs. Do not mark arbitrary user-provided strings as html_safe just to make them appear in the PDF; escape or render them through the usual safe template mechanisms.
Load the HTML and generate the PDF
There are two useful browser flows. Set the page content directly when Rails has already rendered the exact document. Navigate to a URL instead when Chromium should request a complete app route and let Rails build the page there.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Complete controller example using HTML content
This example assembles the Rails and Puppeteer APIs into one pattern. It is not a version-specific tested integration: confirm the package interface, Chromium installation, and runtime behavior against the Rails and Puppeteer versions in your deployment.
Rank #2
- 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⁴
# app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def pdf
invoice = current_user.invoices.find(params[:id])
html = render_to_string(
template: "invoices/show",
formats: [:html],
layout: "pdf",
assigns: { invoice: invoice }
)
pdf_bytes = PdfRenderer.call(html)
send_data pdf_bytes,
filename: "invoice-#{invoice.id}.pdf",
type: "application/pdf",
disposition: "attachment"
end
end
# app/services/pdf_renderer.rb
require "open3"
require "json"
class PdfRenderer
SCRIPT = <<~JS
const puppeteer = require("puppeteer");
let input = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", chunk => input += chunk);
process.stdin.on("end", async () => {
let browser;
try {
browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const { html } = JSON.parse(input);
await page.setContent(html, { waitUntil: "networkidle2" });
const pdf = await page.pdf({
format: "A4",
printBackground: true
});
process.stdout.write(Buffer.from(pdf).toString("base64"));
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
if (browser) await browser.close();
}
});
JS
def self.call(html)
stdin, stdout, stderr, wait = Open3.popen3("node", "-e", SCRIPT)
stdin.write(JSON.generate(html: html))
stdin.close
output = stdout.read
error = stderr.read
raise "PDF rendering failed: #{error}" unless wait.value.success?
require "base64"
Base64.decode64(output)
end
end
The Ruby wrapper uses a child Node process so the example makes the language boundary explicit. In a production app, use a deliberate process-management approach: bound execution time and concurrent jobs, capture errors, and ensure the browser closes on failures. Install Puppeteer and a compatible Chromium browser in the environment where this code runs; an installed local development browser does not guarantee the production worker can launch it.
The sample waits for networkidle2 after setting content. That is not a universal readiness guarantee for every template or external resource. Add a page-specific readiness condition where your own scripts or assets require it, and verify that condition using the actual document. Puppeteer documents that fonts are awaited by default during PDF generation; this does not mean every image, stylesheet, or application script is necessarily ready. See the Puppeteer PDF generation guide.
Load an app route instead of injecting markup
If the browser should make a normal request to your app, use page.goto(url, ...) rather than page.setContent(html, ...). This can be convenient when Rails should run the route and load assets under the app’s own origin. It also means the browser must be able to reach that URL, and the route must authorize the request appropriately. Do not put a sensitive or client-controlled URL into a browser navigation without validating the design and access controls.
await page.goto("https://example.com/invoices/123/print", {
waitUntil: "networkidle2"
});
const pdf = await page.pdf({ format: "A4", printBackground: true });
Puppeteer’s PDF guide uses a URL navigation example with networkidle2. The correct condition depends on the page: network activity can continue, or settle before an app-specific render is complete. Prefer an explicit selector or app readiness signal if the printed output depends on one.
Make asset URLs and print styling work
Absolute URLs need the right Rails context
When rendering outside a request, relative links may not have enough context for Chromium to fetch them. Set the renderer’s host and HTTPS environment as appropriate, and inspect the resulting HTML for generated asset and route URLs. For a page loaded with setContent, check whether its CSS, fonts, and images resolve from the browser’s location; use suitable absolute URLs or a page base URL strategy for the app.
Rank #3
- 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.
PDF output uses print media
page.pdf() uses print CSS media by default. Define print-specific page breaks, margins, and visibility rules in the PDF stylesheet. If the desired output specifically needs screen media styles, call await page.emulateMediaType('screen') before page.pdf(). Puppeteer may adjust colors for printing; CSS -webkit-print-color-adjust: exact can request exact colors where appropriate, though final output should still be checked in the target PDF viewer.
Wait for what the document actually needs
Fonts are awaited by default by Puppeteer’s PDF generation. Images, external stylesheets, and asynchronous application rendering can still need their own readiness strategy. A fixed delay may be simple but can waste time or remain too short under load; a selector or application-defined completion condition is generally more specific. Test with slow or unavailable assets as well as the normal path, because the generated PDF may otherwise silently omit content.
Return generated bytes through Rails
For an in-memory PDF, Rails’ send_data is the appropriate response path. Specify both a useful filename and the MIME type application/pdf. Attachment disposition prompts a download; use disposition: "inline" if the intended behavior is to ask the browser to display the PDF.
Use send_file when the PDF already exists on disk. Rails cautions that file paths must not be constructed unsafely from user input: validate identifiers and authorize access rather than allowing a request parameter to select an arbitrary path. See the Rails file delivery guide.
Common failures and fixes
- PDF has missing images, fonts, or CSS: inspect the rendered HTML’s URLs and the browser’s ability to reach them. Set the renderer host and HTTPS context for absolute Rails-generated URLs; ensure markup passed with
setContenthas a valid way to resolve relative assets. - PDF is blank or partially rendered: confirm the template actually produced the expected HTML, then wait for a page-specific completion signal or required assets before calling
page.pdf(). Do not assume one network-idle event proves every app render is complete. - Colors or layout differ from the web page: PDF generation defaults to print media. Add print CSS or explicitly emulate screen media before printing if that is the intended design.
- Rails renders unwanted navigation or controls: use a dedicated PDF layout or render without a layout, then include only the document structure needed for print.
- Chromium will not launch in a worker: verify Puppeteer and its compatible browser are installed and runnable in that deployment environment. Check process output and close browser processes on both success and failure.
- HTML characters display incorrectly or content becomes unsafe: use normal Rails templates and safe helpers. Do not mark arbitrary strings as HTML-safe to bypass escaping.
- Response is corrupt or has the wrong type: return the binary PDF bytes unchanged through
send_datawithtype: "application/pdf"; ensure any process wrapper does not mix diagnostic text into the byte stream.
Operational considerations
Generating a PDF launches or uses a browser and waits for page rendering and assets, so it is more work than returning a prebuilt static file. For request-time generation, account for browser startup, slow external assets, and worker/request time limits. For longer jobs, render asynchronously, persist the resulting file under an application-controlled name, and provide an authorized download endpoint. The documentation cited here specifies APIs, not universal timing, throughput, or reliability figures; measure the actual templates and deployment environment before choosing concurrency or timeouts.
Rank #4
- 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
Also decide what happens when remote assets are slow or unreachable. A deliberate timeout and clear error path are preferable to hanging a web request, but the timeout should accommodate the document’s real dependencies. Keep the rendered content and any authenticated route access scoped to the intended user; do not expose private PDFs through guessable public paths.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Or skip the browser setup
If the job is to capture a URL rather than render a private Rails template, ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from one GET request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the outcome identified in response headers. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots a month without a card; paid plans start at $5 for 3,000 shots.
For API details and options, see the ScreenshotNeo documentation. Example cURL request:
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://example.com
-o shot.webp
This captures a website URL; it is not a substitute for rendering unsaved, private Rails view state. Create a free account at ScreenshotNeo to get 1,000 screenshots a month with no card required.
Frequently Asked Questions
Does Puppeteer accept a Rails HTML string directly?
Yes. Load the string into a page with Puppeteer’s page content API, then call page.pdf().
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated 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 matchCan Rails return the PDF without saving it first?
Yes. When the PDF is in memory, return its bytes with send_data; use send_file for a file already stored on disk.
Why does a PDF look different from the browser view?
Puppeteer prints with print CSS by default. Use print styles or explicitly emulate screen media before PDF generation if screen styling is required.
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.

