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.

Use an absolute, reachable stylesheet URL—or make Rails emit one. A browser can resolve /assets/print.css inside your application, but PDFKit and Wicked PDF hand HTML to a separate renderer. That process needs a URL it can reach, a correctly configured base URL, or a local file it is explicitly allowed to read. For Rails, use wicked_pdf_stylesheet_link_tag with a precompiled asset. For PDFKit, use a fully qualified URL or set root_url and protocol when rendering raw HTML. If the CSS host is private, give the renderer network access and authentication, or download and inline the stylesheet before conversion.

Why a stylesheet works in the browser but disappears from the PDF

PDF generation is not performed by the browser tab that loaded your Rails page. Wicked PDF starts wkhtmltopdf outside the Rails process; its maintainers state that “the wkhtmltopdf binary is run outside of your Rails application; therefore, your normal layouts will not work.” The renderer receives HTML and must independently resolve every stylesheet, font, image and script reference.

A relative link such as <link rel="stylesheet" href="/assets/invoice.css"> has no useful origin when the input is an HTML string or a temporary file. A protocol-relative link such as //cdn.example.com/invoice.css also needs a protocol. In production, an asset host, digest filename or authentication wall can create a second failure even when development appeared correct.

The dependable rule is to choose one of these forms:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
  • A public, fully qualified URL, for example https://cdn.example.com/assets/invoice-8f31.css.
  • A relative URL plus a configured base, such as PDFKit’s root_url and protocol options.
  • A local stylesheet path that the renderer is allowed to read.
  • CSS downloaded or inlined before conversion, when the source is private or unreachable from the renderer.

Absolute does not mean merely “starts with a slash.” It means the renderer can resolve and fetch the complete address at conversion time.

Pick the loading strategy that matches your Ruby PDF library

Path HTML input CSS approach Important deployment concern
PDFKit Raw HTML, a local file, or a URL Fully qualified URL; for raw HTML, a local path or a relative URL resolved with root_url and protocol The stylesheet collection does not add stylesheets when the source itself is a URL or file
Wicked PDF Rails view rendered by wkhtmltopdf wicked_pdf_stylesheet_link_tag, an absolute asset URL, or a CDN URL Precompile the PDF stylesheet and ensure production asset-host settings produce an absolute address
wkhtmltopdf directly URL or HTML file --user-style-sheet or a link in the HTML; local-file permissions must match your setup Qt WebKit rendering and local-file/network security settings affect what loads
Prawn Ruby drawing instructions, not an HTML page No HTML stylesheet loading Use Prawn when you want to draw the PDF directly rather than render HTML/CSS

PDFKit’s README documents the distinction between raw HTML and URL/file sources. Wicked PDF’s README documents the absolute-reference requirement for CSS, JavaScript and images. The underlying renderer is wkhtmltopdf, an open-source command-line tool that uses Qt WebKit.

PDFKit: load a remote stylesheet or resolve a Rails-relative URL

Option 1: put a complete URL in the HTML

This is the least ambiguous arrangement for raw HTML. The renderer must be able to make an HTTPS request to the host.

html = <<~HTML
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8">
      <link rel="stylesheet" href="https://cdn.example.com/assets/pdf.css">
    </head>
    <body>
      <h1>Invoice 1042</h1>
      <p>Thank you for your order.</p>
    </body>
  </html>
HTML

kit = PDFKit.new(html)
kit.to_file("tmp/invoice.pdf")

Use a versioned or digest URL so a deployment does not serve an old stylesheet from a cache. Check the URL from the same machine, container or job that runs PDFKit—not only from your laptop.

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

Option 2: keep a relative link and configure the base URL

When application code generates the HTML, PDFKit can resolve relative references with root_url and protocol:

html = ApplicationController.render(
  template: "invoices/show",
  assigns: { invoice: invoice }
)

kit = PDFKit.new(
  html,
  root_url: "https://app.example.com",
  protocol: "https"
)
kit.to_file("tmp/invoice-#{invoice.id}.pdf")

The view can then contain:

<link rel="stylesheet" href="/assets/pdf.css">

The base must include the host that serves the asset. If production uses a separate asset host, use that host in the generated absolute URL or configure Rails’ asset host accordingly.

Do not rely on the stylesheet collection for URL or file sources

PDFKit supports adding local stylesheet paths when the source is raw HTML. That mechanism does not add stylesheets when the source is supplied as a URL or a file. In those modes, put the link in the HTML itself, or switch to a raw-HTML workflow in which a local path is appropriate.

Wicked PDF in Rails: use the helper and precompile the asset

View markup

Wicked PDF provides a helper that emits a renderer-friendly reference:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<!-- app/views/invoices/show.pdf.erb -->
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <%= wicked_pdf_stylesheet_link_tag "pdf" %>
  </head>
  <body>
    <h1>Invoice <%= @invoice.number %></h1>
    <p><%= @invoice.customer_name %></p>
  </body>
</html>

Alternatively, link directly to a publicly reachable asset:

<link rel="stylesheet" href="https://cdn.example.com/assets/pdf.css">

Controller and asset setup

# app/controllers/invoices_controller.rb
def show
  @invoice = Invoice.find(params[:id])
  respond_to do |format|
    format.html
    format.pdf do
      render pdf: "invoice-#{@invoice.number}", template: "invoices/show.pdf.erb"
    end
  end
end

Keep the PDF stylesheet in the asset pipeline and precompile it for production. A digest filename that was never built, or an asset host that resolves only inside the Rails process, leaves wkhtmltopdf with a dead link. The helper is Rails-specific; outside Rails, emit an absolute URL yourself.

Wicked PDF’s maintainers put the requirement plainly: “If you plan to use any CSS, JavaScript, or image files, you must modify your layout so that you provide an absolute reference to these files.”

Using wkhtmltopdf directly

If you call the renderer without a Ruby wrapper, put the stylesheet in the HTML or use its user-stylesheet setting:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
wkhtmltopdf --user-style-sheet /srv/pdf/pdf.css input.html output.pdf

Run wkhtmltopdf --help and consult the usage documentation for the options available in your installed build. The lower-level page settings include a userStyleSheet URL/path and load.blockLocalFileAccess; the latter matters when a remote stylesheet references local images or fonts. The corresponding settings are described in the libwkhtmltox page-settings documentation.

Do not enable broad local-file access merely to make one asset load. A PDF job that accepts untrusted HTML could otherwise be given access to files or network locations it should never read. Prefer a controlled asset directory, a public read-only asset host, or inlined CSS.

Private CSS, authentication and network reachability

A URL that works in your browser may still fail in a background PDF job because the job has different DNS, firewall rules, proxy settings or credentials. For a private stylesheet:

  1. Confirm that the PDF process can resolve the hostname and establish HTTPS connectivity.
  2. Confirm that the certificate chain is trusted by the renderer’s operating environment.
  3. Provide any required authentication in a way supported by your deployment, or fetch the CSS in Ruby before conversion.
  4. Rewrite relative url(...) references inside the CSS if downloading it; fonts and background images need reachable URLs too.
  5. Use a temporary, controlled file or inline <style> block, then remove it after conversion.

The documentation establishes the need for absolute paths and renderer configuration, but it does not guarantee that every remote authentication scheme will work. If the endpoint requires an interactive login, browser-only token or JavaScript challenge, make a server-side copy or expose a narrowly scoped asset URL instead.

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

CSS and asset details that commonly affect the final PDF

  • Print rules: Put PDF-specific declarations in the stylesheet you load for the PDF view. Do not assume the browser’s screen rules are the intended print design.
  • Fonts: A font referenced by CSS is another network or file request. Make its URL reachable from the renderer and verify licensing and format support.
  • Images: Use absolute image URLs or permitted local paths. A stylesheet can load while its background images remain missing.
  • Relative CSS URLs: Resolve them relative to the stylesheet’s own location, not necessarily the HTML page. Moving CSS to a CDN can therefore require changing url(...) paths.
  • Timing: If CSS is injected by JavaScript, ensure the renderer waits long enough; a static link is more reliable for a PDF layout.
  • Cache and deploys: Digest or version your CSS and avoid changing a file in place while jobs are running.

Troubleshooting: symptom, cause and fix

Symptom Likely cause Fix
All styling is absent The link is relative and the renderer has no base URL Use an HTTPS URL, or set PDFKit’s root_url and protocol
Works in development, fails in production The PDF asset was not precompiled or the production asset host is not absolute Precompile the PDF stylesheet and inspect the rendered HTML for its final URL
PDFKit ignores an added stylesheet The source was supplied as a URL or file Place the <link> in the HTML, or use raw HTML with a supported local path
CSS URL returns an error in the job DNS, firewall, certificate or authentication differs from the browser Fetch the URL from the renderer’s host and make the asset publicly reachable or download it first
Styles load but images or fonts do not Nested url(...) references point to unreachable locations Make every nested resource absolute or permit the required local directory
Local assets fail after tightening security Local-file access is blocked Use a controlled asset URL, or explicitly allow only the required directory; do not broadly expose the filesystem
Layout differs from Chrome wkhtmltopdf uses Qt WebKit rather than a current browser engine Test against the renderer’s engine and simplify unsupported CSS; choose a modern browser service when fidelity is critical

For diagnosis, save the exact HTML sent to the renderer, open it from the same execution environment, and inspect the stylesheet response status. This separates URL resolution from CSS compatibility.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability and cost decisions

Local wkhtmltopdf avoids a per-document hosted-renderer request but makes you responsible for installing and updating a binary, fonts, network access and concurrency. Remote CSS adds DNS and HTTP latency to every uncached job. Inlining a small, stable stylesheet removes one request; downloading a large stylesheet once and reusing a controlled copy can help batch jobs, but you must refresh it when the design changes.

For sensitive documents, decide whether the renderer may contact public hosts at all. A self-contained HTML package is easier to audit. For public pages, a hosted headless-browser service can reduce binary maintenance, but verify its PDF options, authentication model, data handling and pricing before moving production documents.

Or skip the browser setup

ScreenshotNeo is a hosted website screenshot API and MCP server that can return a clean screenshot or PDF from one GET request. It accepts the page like a visitor, removes cookie-consent banners, newsletter popups and chat widgets before capture, and reports whether the result was billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

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

For the complete parameter list and PDF settings, see the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

You can also control full-page capture, lazy-image loading, viewport and device presets, dark mode, retina scale, custom CSS and JavaScript, clicks, waits, blocked requests, cookies, headers, user agents, timezone, geolocation, transparent backgrounds, resizing, caching TTL, signed links, asynchronous webhooks and bulk capture. Every response includes X-Page-Verdict and X-Billed headers so a failed or unclean page is distinguishable from a billable result.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it without a card.

Frequently Asked Questions

Can I use a protocol-relative stylesheet URL?

Yes, but only when the renderer has a protocol to apply. Supplying protocol: "https" in PDFKit or writing the complete HTTPS URL removes that ambiguity.

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

Is a Rails asset helper enough for a PDF job?

Only if the helper outputs an absolute, reachable reference and the asset was built for the current environment. Inspect the generated HTML rather than assuming the helper’s browser behavior carries over.

When should I choose Prawn instead?

Choose Prawn when the document is best expressed as Ruby drawing commands and you do not need HTML/CSS rendering. It will not make an HTML <link> stylesheet load automatically.

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.