Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →For a normal Rails response, put the CSS string inside a <style> element in the HTML string, then return that document with render html:. A stylesheet helper links a file or URL; it does not interpret a raw CSS string. If the destination is a PDF or image, use the renderer’s inline-style option instead. Nokogiri can parse the markup, but it does not calculate browser CSS layout.
The correct implementation depends on what “rendering” means in your application: an HTTP response, an ERB template held in memory, a browser-quality PDF/image, or HTML parsing and transformation.
Return an HTML string from Rails
Build a complete document and place the CSS text in the head. Rails then sends the resulting document as text/html.
html = <<~HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: sans-serif; margin: 2rem; }
.notice { color: #176b3a; font-weight: 600; }
</style>
</head>
<body>
<p class="notice">Ready</p>
</body>
</html>
HTML
render html: html.html_safe
Why html_safe matters
Rails escapes a string passed to render html: unless the string is already marked HTML-safe. Without the final call, tags can appear as literal text rather than being interpreted by the browser.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Marking the whole string safe is appropriate only when every part of it is trusted or has been safely constructed. Never concatenate raw user input into this string and then call html_safe. Escape user-provided values, or generate the document with Rails tag helpers and normal view escaping.
#1 Best Overall
Layouts are not included automatically
An inline HTML response omits the application layout by default. Pass layout: true or a named layout when that is what you want:
render html: html.html_safe, layout: true
# or
render html: html.html_safe, layout: 'print'
For a small response this is convenient. For a substantial page, a normal view template is easier to maintain than a large heredoc.
Choose the rendering path
| Requirement | Use | Key distinction |
|---|---|---|
| Return a small HTML document | render html: |
Literal HTML; ordinary strings are escaped. |
| Evaluate ERB held in a string | render inline: |
Runs ERB; it is template evaluation, not simple HTML return. |
| Apply CSS text to browser HTML | A <style> element |
Keep the CSS in the document head. |
| Generate a PDF, PNG, or JPEG | Grover or another document renderer | A browser engine performs layout and needs its runtime and assets. |
| Inspect or transform markup | Nokogiri | Parses a tree; it does not render visual CSS. |
When the string contains ERB
If the string is a template source such as <h1>Hello, <%= @name %>!</h1>, use render inline::
template = '<!doctype html>
<style>.title { color: #176b3a; }</style>
<h1 class="title">Hello, <%= @name %>!</h1>'
render inline: template, layout: false
Rails evaluates the ERB expression before returning the result. Inline templates also do not use a layout unless you request one. Keep the data used by the template escaped according to its context, and prefer a view file for complex pages; the Rails guide describes inline rendering as an uncommon choice for application views.
CSS strings, linked stylesheets, and assets
Embed raw CSS
Interpolation is useful when a component or generated report supplies CSS dynamically:
Rank #2
css = <<~CSS
.card { border: 1px solid #ddd; padding: 1rem; }
.card--warning { background: #fff4cc; }
CSS
body = '<div class="card card--warning">Check this item</div>'
html = "<!doctype html><html><head><style>#{css}</style></head><body>#{body}</body></html>"
render html: html.html_safe
Only interpolate CSS that your application controls. If a user can supply the CSS, treat it as untrusted content and validate or sanitize it before including it.
Link a CSS resource
For a file managed by the Rails asset pipeline, use a normal view and stylesheet_link_tag:
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitches<%= stylesheet_link_tag 'application', media: 'all' %>
The helper emits a <link> element pointing to a stylesheet resource. It is not an API for passing CSS text. A linked file is generally preferable when the same styles are reused, cacheability matters, or the stylesheet is too large for an inline response.
Generate a PDF or image with inline CSS
A Rails response only returns bytes to a browser. It does not itself create a PDF or screenshot. A browser-backed renderer such as Grover accepts HTML and can inject CSS text through style_tag_options:
style_tag_options = [
{ content: '.body { background: red; color: white; }' }
]
pdf = Grover.new(
'<html><body class="body"><h1>Heading</h1></body></html>',
style_tag_options: style_tag_options
).to_pdf
File.binwrite('report.pdf', pdf)
Grover uses Puppeteer and Chromium and can produce PDF, PNG, and JPEG output. The renderer must be installed and usable in the environment where the Ruby process runs. For direct calls outside middleware, plan how relative assets are resolved: provide a display_url or convert relative image, font, and stylesheet paths to absolute URLs or filesystem paths. Without a display URL, Chromium resolves relative paths against a default host, which commonly makes local assets appear missing.
Rank #3
PDF-specific options
Use the renderer’s documented PDF options for paper size, margins, landscape orientation, and page ranges. Keep CSS that affects pagination in the injected style, and verify print-specific rules such as @page and print-color-adjust in the Chromium version installed with your application. The available documentation demonstrates the inline-style mechanism; it does not establish universal performance or feature parity across all Chromium and Puppeteer versions.
Free tools Windows power users keep installed
One-click scans. No signup required.
WickedPDF as another documented route
WickedPDF also accepts HTML through pdf_from_string. Its documentation shows absolute asset paths and a stylesheet helper for file-based CSS. The referenced example is for version 0.9.4, so check the version installed in your application before copying its options or assuming compatibility.
Why Nokogiri does not solve CSS rendering
Nokogiri’s HTML5 parser accepts a document with Nokogiri.HTML5(input) or a fragment with Nokogiri::HTML5.fragment(input). You can query nodes, add a style element, rewrite attributes, and serialize the result:
document = Nokogiri.HTML5('<article><h1>Title</h1></article>')
head = document.at_css('head') || document.at_css('html').add_child('<head></head>').first
head.add_child('<style>article { max-width: fortyrem; }</style>')
puts document.to_html
That code manipulates markup only. Nokogiri does not load fonts, execute JavaScript, calculate computed styles, or paint pixels. The HTML5 API is also not available on JRuby according to its documentation. Send the resulting HTML to a browser-based renderer when you need visual output.
Security and correctness checklist
- Keep trusted CSS and HTML separate from user-provided values.
- Escape text inserted into HTML; do not use
html_safeas a shortcut around escaping. - Use
render inline:only when you intentionally want ERB evaluation. - Choose
stylesheet_link_tagfor a stylesheet resource, not a raw CSS string. - For PDF or image output, make relative assets resolvable from the renderer process.
- Set an explicit character encoding and include a doctype for predictable browser parsing.
- Test print layout separately from the normal web response; viewport, fonts, and pagination can change the result.
Troubleshooting common failures
The browser displays the tags instead of styling the page
The response was escaped. Confirm that the document is trusted before using html.html_safe, or render through a normal Rails template so Rails can escape individual values while preserving the template’s markup.
The CSS is present but has no effect
Inspect the final HTML and verify that the CSS is inside a <style> element in the document. Check selector specificity, malformed CSS, and whether a later rule overrides it. A stylesheet_link_tag pointing at a missing asset will not apply an in-memory string.
Rank #4
ERB appears literally
render html: returns literal markup; it does not evaluate ERB. Use render inline: for a deliberately evaluated template string, or move the template to a view file.
The PDF has no images, fonts, or styles
Relative URLs are often the cause when Grover is called directly. Supply a suitable display_url, use absolute URLs, or provide filesystem paths that Chromium can read. Also confirm that the Chromium/Puppeteer runtime is installed and that the process has network or file permissions.
The output is an HTML tree but not a visual page
Nokogiri is a parser, not a layout engine. Serialize its result and pass it to a browser renderer for PDF, PNG, or JPEG output.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallThe response unexpectedly lacks the application layout
Inline HTML rendering does not include a layout by default. Add layout: true or specify the layout name.
Or skip the browser setup
If your goal is a reliable screenshot or PDF endpoint rather than building and operating a Chromium pipeline, ScreenshotNeo accepts one request with a URL. It removes cookie-consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Every response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for authentication and options. This cURL request captures the target URL as an image:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same endpoint can be called from Ruby, Python, or Node.js:
Recommended Free Tools
require 'net/http'
require 'uri'
uri = URI('https://api.screenshotneo.com/v1/shot')
uri.query = URI.encode_www_form(access_key: 'YOUR_API_KEY', url: 'https://stripe.com')
File.binwrite('shot.webp', Net::HTTP.get(uri))
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}`);
ScreenshotNeo supports PNG, JPEG, WebP, and PDF output plus full-page capture, lazy-image loading, CSS-selector element capture, dark mode, device presets, custom viewports, retina scale, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs, webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by many screenshot APIs, which can reduce migration work.
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing provides two months free. Create a free ScreenshotNeo account to try the endpoint.
Performance, reliability, and cost choices
Inline HTML versus linked CSS
Inlining a small stylesheet removes a separate asset request and makes an HTML fragment self-contained, which is useful for email-like snippets and generated documents. Large or shared stylesheets are easier to cache and maintain as linked assets. The right choice depends on payload size, reuse, and whether the consumer can fetch external resources.
Browser rendering versus parsing
Browser-backed rendering performs layout and painting, so it requires more runtime resources than string assembly or Nokogiri parsing. Parsing is appropriate for structural edits; it cannot replace a visual renderer. Do not infer a speed or memory advantage for one library without testing the exact versions, document size, assets, and deployment environment.
Caching generated captures
For repeated URLs, cache deliberately and define an invalidation policy. ScreenshotNeo lets you choose a cache TTL and reports cache hits as not billed. For an in-process Grover pipeline, cache the finished artifact only when the underlying page and CSS are known to be unchanged.
Frequently Asked Questions
Can I pass a CSS string directly to `stylesheet_link_tag`?
No. Use a `
Two free Windows tools

