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

Use a stylesheet URL that the PDF renderer can resolve and reach—not a browser-only relative path. For PDFKit, put a fully qualified URL in the HTML or set root_url and protocol when converting raw HTML. In Rails with Wicked PDF, use wicked_pdf_stylesheet_link_tag or an absolute CDN asset URL, and precompile the stylesheet. The same rule applies to wkhtmltopdf: it runs outside your Rails process, so it must be able to resolve, connect to and authorize every CSS, font and image URL.

The rule that fixes most missing CSS

A normal browser knows the page’s origin and can turn /assets/pdf.css into a complete URL. A PDF process may instead receive an HTML string, a local file or a remote page with no Rails request context. If the stylesheet reference is relative, the renderer can request the wrong location or nothing at all.

Choose one of these reliable strategies:

  • Use a publicly reachable absolute URL such as https://cdn.example.com/pdf.css.
  • Resolve relative and protocol-relative URLs with PDFKit’s root_url and protocol options.
  • In Rails views rendered by Wicked PDF, use wicked_pdf_stylesheet_link_tag and ensure the resulting asset URL is absolute and precompiled.
  • If the CSS host is private, give the renderer network access and the required authentication, or download and inline the CSS before conversion.

Test the exact HTML and URL from the same machine, container or worker that runs the converter. A stylesheet that loads in your development browser is not proof that the PDF process can reach it.

PDFKit: load a remote stylesheet correctly

HTML containing a complete URL

When PDFKit receives HTML, the simplest option is a fully qualified link. This avoids relying on a current request host or working directory.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://cdn.example.com/pdf.css">
  </head>
  <body>Invoice</body>
</html>

PDFKit documents that local stylesheet paths can be supplied for raw HTML input, but its stylesheet collection cannot add stylesheets when the source itself is supplied as a URL or file. Put the remote link in the document in those cases instead of expecting a collection entry to be merged into the fetched page. See the PDFKit README.

Resolve relative links with root_url and protocol

If your template deliberately contains a relative asset path, provide the origin explicitly when constructing the kit.

require 'pdfkit'

html = <<~HTML
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8">
      <link rel="stylesheet" href="/assets/pdf.css">
    </head>
    <body>Invoice</body>
  </html>
HTML

kit = PDFKit.new(
  html,
  root_url: 'app.example.com',
  protocol: 'https'
)

File.binwrite('invoice.pdf', kit.to_pdf)

The resulting request is effectively to https://app.example.com/assets/pdf.css. Use the real asset host, including any subpath required by your deployment. If your HTML uses a protocol-relative URL such as //cdn.example.com/pdf.css, setting protocol: 'https' prevents the renderer from guessing.

When the source is already a URL

For a remote page, give PDFKit the page URL and make the page’s own <link> absolute:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
kit = PDFKit.new('https://app.example.com/invoices/42',
                 root_url: 'https://app.example.com',
                 protocol: 'https')
File.binwrite('invoice-42.pdf', kit.to_pdf)

Do not rely on adding a stylesheet through PDFKit’s stylesheet collection for a URL or file source; the documented limitation applies to those input modes.

Wicked PDF in Rails

Use the Rails-aware helper

wkhtmltopdf is launched as a separate process. As the Wicked PDF maintainers state, “The wkhtmltopdf binary is run outside of your Rails application; therefore, your normal layouts will not work.” They also require absolute references for CSS, JavaScript and images. The Rails-specific helper emits the stylesheet reference for the PDF view:

<!-- app/views/invoices/show.pdf.html.erb -->
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <%= wicked_pdf_stylesheet_link_tag 'pdf' %>
  </head>
  <body>
    <%= render 'invoice', invoice: @invoice %>
  </body>
</html>

Keep the helper in the PDF layout or view, not only in the browser layout. If you use a CDN, the equivalent is an absolute link:

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

Precompile the PDF stylesheet

Make the file part of the production asset build. For example:

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.
# config/initializers/assets.rb
Rails.application.config.assets.precompile += %w[pdf.css]

Then deploy the compiled file to the asset host used by the renderer. A development page can appear correct while production PDF generation fails if the PDF process receives an uncompiled logical path or a host that is not publicly reachable.

Render from the controller

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

Inspect the generated HTML and the final URL emitted by wicked_pdf_stylesheet_link_tag. If the host is only available inside a browser session, configure an absolute asset host that the wkhtmltopdf process can actually resolve, or provide the CSS locally through a controlled conversion step. The Wicked PDF README documents the helper and the absolute-reference requirement.

Using wkhtmltopdf directly

wkhtmltopdf is an open-source command-line utility that renders HTML into PDF with Qt WebKit. It accepts a URL or file input and provides options affecting external links and rendering. A remote stylesheet can be supplied in the page itself or with the page setting exposed as --user-style-sheet:

wkhtmltopdf 
  --user-style-sheet https://cdn.example.com/pdf.css 
  https://app.example.com/invoices/42 
  invoice-42.pdf

The corresponding library setting is userStyleSheet. See the page-settings documentation and the command-line usage documentation.

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

Local files, fonts and images

If the remote stylesheet references local images or fonts, the renderer’s local-file policy matters. The libwkhtmltox page settings include load.blockLocalFileAccess. A conversion that combines a remote CSS file with local resources must either permit the required paths or move those resources to reachable URLs. Treat local-file access as a security decision: do not enable broad access for untrusted HTML that could request files or internal resources.

What must be reachable at conversion time

CSS is only the first request. A PDF can still look unstyled when the stylesheet loads but its dependencies fail.

  • Network and DNS: the worker running PDFKit or wkhtmltopdf needs DNS, routing and TLS access to the CSS host.
  • Authentication: private CSS endpoints need credentials that the renderer can send. A browser cookie is not automatically available to a separate process.
  • Fonts and images: URLs inside url(...) are fetched by the renderer, not by Rails. Make them absolute or place them where the renderer can reach them.
  • Redirects: verify that the final redirected URL is reachable and does not require an interactive login.
  • HTML mode: raw strings, local files and remote URLs have different base-URL behavior. Set the base explicitly rather than assuming a request context.

Renderer choices and trade-offs

Approach Input CSS loading method Operational consideration Rendering model
PDFKit Raw HTML, local file or URL Absolute link; root_url/protocol for relative links; local paths for raw HTML Ruby wrapper around a wkhtmltopdf binary Qt WebKit through wkhtmltopdf
Wicked PDF Rails view or URL wicked_pdf_stylesheet_link_tag or an absolute asset/CDN URL; precompiled assets Rails asset deployment plus an external binary Qt WebKit through wkhtmltopdf
wkhtmltopdf CLI URL or file Page link or --user-style-sheet Manage the executable, permissions and network access yourself Qt WebKit
Prawn Ruby drawing instructions Not an HTML stylesheet-loading mechanism Draw layout directly in Ruby Direct PDF DSL rather than an HTML browser renderer

Choose Prawn when you want to draw the document directly and control every PDF primitive. It will not make an HTML <link> load automatically. Choose a wkhtmltopdf-based path when reusing HTML and CSS is more valuable, accepting its older Qt WebKit rendering model and binary-management overhead.

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

Troubleshooting checklist

The PDF has no styles at all

Open the exact stylesheet URL from the conversion host. Replace a relative path with an absolute HTTPS URL, or set PDFKit’s root_url and protocol. In Rails, confirm that the Wicked PDF helper emits an absolute path and that the file was precompiled.

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

The stylesheet returns 404 in production

Check the deployed asset manifest and asset host. A logical name such as pdf.css may map to a fingerprinted file; the renderer must receive the published URL, not a development-only path.

The CSS URL works in a browser but not in the PDF

The browser may have cookies, VPN access, proxy settings or a logged-in session that the external process lacks. Make the resource public to the worker, pass supported authentication, or download and inline the CSS before conversion.

CSS loads, but fonts or images are missing

Inspect every url(...) in the stylesheet. Convert relative references to reachable absolute URLs, or deliberately configure local-file access for trusted input and the required directories.

Only modern CSS features fail

wkhtmltopdf uses Qt WebKit, not a current browser engine. Simplify the PDF stylesheet or choose a renderer whose engine supports the CSS you need. Do not diagnose an engine limitation as a URL problem until the stylesheet request itself succeeds.

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

Wicked PDF works locally but times out in deployment

Confirm that the wkhtmltopdf binary is installed and executable in the production environment, then test DNS, outbound HTTPS and the asset host from that same worker. A Rails request succeeding in a browser does not test those conditions.

Or skip the browser setup

If maintaining a renderer and asset reachability is more work than your use case justifies, ScreenshotNeo is a hosted capture API and MCP server. It can return PNG, JPEG, WebP or PDF, accepts a URL in one request, removes cookie-consent banners, newsletter popups and chat widgets before capture, and reports whether a response was billed. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed. Its MCP tools—take_screenshot, get_page_info and capture_pdf—work with Claude, Cursor and other MCP clients.

Here is the documented cURL form (the endpoint also supports PDF output; see the ScreenshotNeo API documentation for output options):

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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Every plan includes the same features. The Free plan provides 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. If you want to avoid the browser and wkhtmltopdf setup, create a free ScreenshotNeo account.

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

Practical decision guide

  1. Already have Rails HTML: use Wicked PDF’s helper, precompile the CSS and verify the emitted absolute URL.
  2. Have a Ruby HTML string: use PDFKit with an absolute link, or set root_url and protocol.
  3. Need a command-line pipeline: use wkhtmltopdf and --user-style-sheet, while controlling local-file access.
  4. Need pixel-level direct drawing: use Prawn and recreate the layout in Ruby rather than expecting HTML CSS to apply.
  5. Want hosted capture and PDF output: use a hosted browser service such as ScreenshotNeo and let it handle the rendering environment.

Frequently Asked Questions

How can I verify which stylesheet the PDF process requested?

Serve the CSS through a temporary access log or inspect requests from the conversion worker, then compare that URL with the final HTML emitted by Rails or PDFKit.

Should I inline all CSS for every PDF?

Inlining can remove network and base-URL problems, but it is a preparation strategy rather than a requirement. Absolute URLs or correctly configured renderer options are sufficient when the renderer can reach the stylesheet.

Is a Prawn template compatible with Wicked PDF helpers?

No. Prawn draws PDF content directly in Ruby, while Wicked PDF renders HTML through wkhtmltopdf; they use different document models.

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.

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