Encited logo
Pricing

How to Integrate Pre-rendering with Ruby on Rails

Integrate pre-rendering with Ruby on Rails using Rack middleware that serves rendered HTML to crawlers before Rails routes and authentication.

How Rails pre-rendering works

Ruby on Rails can add Encited as the first Rack middleware so crawlers receive rendered HTML before Rails routing and authentication run.

The Rack middleware sends eligible crawler GET requests to Encited. Browser visits and render fallbacks call the next Rails application in the Rack chain.

Prerequisites

  • Access to add a Rack middleware class under app/middleware.
  • Permission to insert EncitedPrerender at position zero in the Rails middleware stack.
  • LOVABLEHTML_API_KEY configured for Puma, Passenger, or the production process manager.

Set up pre-rendering with Rails

1

Add the Rack middleware

Save the snippet as app/middleware/encited_prerender.rb. It only uses Ruby's standard HTTP libraries.

2

Insert it first

Load the file and insert EncitedPrerender at position 0 so crawler requests are handled before cache or authentication middleware.

bin/rails middleware
3

Restart Rails

Set LOVABLEHTML_API_KEY through your process manager and restart Puma, Passenger, or your deployment service.

encited_prerender.rb
CopyDownload
# app/middleware/encited_prerender.rb
require "net/http"
require "uri"
class EncitedPrerender
CRAWLER = /(?:(OAI-SearchBot|ChatGPT-User(?:\/\d+\.\d+)?|GPTBot|ChatGPT|OpenAI-))|(?:(anthropic-ai|ClaudeBot|claude-web|Claude-Web|Claude-User|Claude-SearchBot))|(?:(GrokBot|xAI-Grok))|(?:(PerplexityBot|Perplexity-User))|(?:(Google-InspectionTool|GoogleOther(?:-Image|-Video)?|Googlebot|Google-CloudVertexBot|Storebot-Google))|(?:(Microsoft-Copilot|CopilotBot|Copilot-Chat|Microsoft-CopilotPlugin))|(?:(BingBot|bingbot|BingPreview))|(?:facebookexternalhit.*Twitterbot|Twitterbot.*facebookexternalhit)|(?:(Meta-ExternalAgent|meta-externalagent|meta-externalfetcher|MetaBot|FacebookBot|facebookexternalhit))|(?:LinkedInBot)|(?:Amazonbot)|(?:(Applebot-Extended|Applebot))|(?:Bytespider)|(?:(DuckAssistBot|DuckDuckBot))|(?:(cohere-training-data-crawler|cohere-ai|CohereAI))|(?:MistralAI-User)|(?:AI2Bot)|(?:CCBot)|(?:Diffbot)|(?:omgili)|(?:TimpiBot)|(?:YouBot)|(?:(Bravebot|Brave-Search))|(?:Yandex)|(?:Twitterbot)|(?:Discordbot)|(?:(Slackbot|Slack-ImgProxy))|(?:TelegramBot)|(?:(Pinterest|Pinterestbot))|(?:Snapchat)|(?:Redditbot)|(?:Tumblr)|(?:(Mastodon|http\.rb))|(?:Viber)|(?:^Line\/)|(?:WhatsApp)|(?:(SkypeUriPreview|Skype))|(?:(Teams|MicrosoftPreview))|(?:Zoom)|(?:Notion)|(?:(Quora|QuoraBot|Quora-Bot|Poe-Bot))|(?:Medium)|(?:(Pocket|PocketParser|PocketImageCache))|(?:(Flipboard|FlipboardProxy))|(?:(Embedly|embed\.ly))|(?:(Baiduspider|Baidu))|(?:(Sogou|sogou))|(?:(Yeti|NaverBot|Naver))|(?:SeznamBot)|(?:(Qwantify|Qwant))|(?:Ecosia)|(?:MojeekBot)|(?:Mail\.RU_Bot)|(?:Yahoo! Slurp)|(?:(SemrushBot|SEMrush))|(?:(AhrefsBot|AhrefsSiteAudit))|(?:(DotBot|Rogerbot|moz\.com))|(?:MJ12bot)|(?:Screaming Frog)|(?:SISTRIX)|(?:SEOkicks)|(?:serpstatbot)|(?:RSiteAuditor)|(?:LvHTML-SEOAuditBot)|(?:Barkrowler)|(?:HubSpot Crawler)|(?:(AwarioSmartBot|AwarioBot))/i
FORWARD_HEADERS = %w[
accept-language sec-fetch-mode sec-fetch-site sec-fetch-dest sec-fetch-user
upgrade-insecure-requests referer user-agent
].freeze
RESPONSE_HEADERS = %w[
x-lovablehtml-render-cache x-lovablehtml-snapshot-key cache-control etag
content-digest signature-input signature
].freeze
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
accept = request.get_header("HTTP_ACCEPT").to_s
user_agent = request.user_agent.to_s
is_html = accept.empty? || accept == "*/*" || accept.include?("text/html")
return @app.call(env) unless request.get? && is_html && user_agent.match?(CRAWLER)
public_url = request.url
uri = URI("https://encited.com/api/prerender/render")
uri.query = URI.encode_www_form(url: public_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 2
http.read_timeout = 30
rendered = http.start do
req = Net::HTTP::Get.new(uri)
req["x-lovablehtml-api-key"] = ENV.fetch("LOVABLEHTML_API_KEY")
req["accept"] = "text/html"
FORWARD_HEADERS.each do |name|
rack_name = "HTTP_#{name.upcase.tr('-', '_')}"
value = request.get_header(rack_name).to_s
req[name] = value unless value.empty?
end
http.request(req)
end
if rendered.code.to_i == 301 && rendered["location"]
return [301, { "location" => rendered["location"], "cache-control" => "no-store" }, []]
end
return @app.call(env) if rendered.code.to_i == 304
if rendered.code.to_i == 200 && rendered["content-type"].to_s.include?("text/html")
headers = { "content-type" => "text/html; charset=utf-8" }
RESPONSE_HEADERS.each do |name|
headers[name] = rendered[name] if rendered[name]
end
return [200, headers, [rendered.body]]
end
@app.call(env)
rescue StandardError
@app.call(env)
end
end
# config/application.rb - before routes/auth middleware
# config.middleware.insert_before 0, EncitedPrerender

Test the integration

1. Send a crawler request

curl -sS -D - -o /dev/null \ -A "Googlebot" \ -H "Accept: text/html" \ "https://your-domain.com/"

Look for a successful HTML response and the x-lovablehtml-render-cache header.

2. Confirm browser passthrough

curl -sS -D - -o /dev/null \ -A "Mozilla/5.0" \ -H "Accept: text/html" \ -H "Accept-Language: en-US" \ -H "Sec-Fetch-Mode: navigate" \ -H "Sec-Fetch-Dest: document" \ -H "Upgrade-Insecure-Requests: 1" \ "https://your-domain.com/"

This request should be served by your existing website without an Encited render-cache header.

Common errors

Rails cannot load EncitedPrerender

Ensure app/middleware is autoloaded or require the middleware file explicitly in the environment configuration before inserting it.

Authentication redirects crawlers before pre-rendering

Insert EncitedPrerender at index zero and confirm its position with bin/rails middleware.

Request and security behavior

  • Only eligible GET requests for public HTML pages are considered for pre-rendering.
  • Along with the public page URL, only the API key and crawler-classification headers are sent to Encited.
  • Cookie and Authorization headers are never forwarded to Encited.
  • If Encited is unavailable or does not return rendered HTML, the request falls through to your website.

Common questions

How does pre-rendering work with Ruby on Rails middleware?

A Rack middleware detects eligible crawler HTML requests, returns Encited rendered HTML, and passes browser traffic to the Rails application.

Where should the Rails pre-rendering middleware be registered?

Insert EncitedPrerender at position zero so it runs before sessions, authentication, caching, and Rails route handling.

How do I test Googlebot pre-rendering in Rails?

Request a public Rails URL with a Googlebot user agent and Accept: text/html, then inspect the response for x-lovablehtml-render-cache.

Avatar
How can we help?
Get instant answers to your questions or leave a message for an engineer will reach out
Ask AI about Encited
See our docs
Contact support
Leave a message
We'll get back to you soon
Avatar
Ask AI about Encited
Team is also here to help
Thinking
Preview
Drop an image to attach
Powered by ReplyMaven