Backend frameworks · 5 min setup
How to add analytics to a Ruby on Rails app
<head> of app/views/layouts/application.html.erb, rendered only in production. Turbo Drive visits are counted as pageviews automatically, and the tag loads once because Turbo keeps scripts in <head> across visits.Updated
If your app sends a Content-Security-Policy (Rails generates config/initializers/content_security_policy.rb), allow the VisitTrack origin there. For conversions, record signups and payments on the server with the visitor id from the tracker's first-party _vt_vid cookie — Rails reads it with cookies[:_vt_vid].
Ruby on Rails at a glance
- Where the tag goes
app/views/layouts/application.html.erb,<head>- Production only
<% if Rails.env.production? %>- Turbo
- Drive visits tracked; tag loaded once
- CSP
config/initializers/content_security_policy.rb- Server events
Net::HTTP.postto/api/track
How to install VisitTrack on Ruby on Rails
Step 1: Store the ids in credentials or ENV
The site id is public; the API key (VisitTrack Settings → API / MCP) is secret and stays on the server. ENV is used below — Rails credentials work the same.
Step 2: Add the tag to the application layout
Inside
<head>.javascript_include_tagturns thedata:hash into adata-siteattribute.app/views/layouts/application.html.erb<head> <title><%= content_for(:title) || "My app" %></title> <meta name="viewport" content="width=device-width,initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> <% if Rails.env.production? %> <%= javascript_include_tag "https://visitrack.app/tracker.js", defer: true, data: { site: ENV.fetch("VISITRACK_SITE_ID") } %> <% end %> </head>Step 3: Allow the origin in your CSP initializer
Add it to your existing policy; keep whatever else you already allow.
config/initializers/content_security_policy.rbRails.application.configure do config.content_security_policy do |policy| policy.script_src :self, "https://visitrack.app" policy.connect_src :self, "https://visitrack.app" end endStep 4: Deploy and browse
Deploy, open the production site and click around.
bin/devon localhost renders no tag and would be ignored anyway.
How to check VisitTrack is working on Ruby on Rails
- View source in production: a
<script>tag withsrc="https://visitrack.app/tracker.js"and yourdata-siteis in the head. - Click links between pages (Turbo Drive) — the VisitTrack live view shows you, and each page appears in the Pages tab.
- DevTools → Network: one
POSTtovisitrack.app/api/collectper Turbo visit, with no full page reload.
Does VisitTrack track Ruby on Rails page navigation?
Turbo Drive fetches the next page and updates the URL with history.pushState, which the tracker wraps — so every Drive visit is a pageview, and back/forward (popstate) too. Turbo merges the new <head> and doesn't re-run scripts that are already there, so the tracker starts once per full page load.
Turbo Frames don't change the URL unless they set data-turbo-action="advance"; when they do, that's a pageview too. Plain frame swaps aren't.
How to track custom events and goals in Ruby on Rails
Send signups from the server. With Devise, override the registrations controller and report the event once the user is saved:
# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def create
super do |user|
VisitTrack.event(cookies[:_vt_vid], "signup", method: "email") if user.persisted?
end
end
end
# app/lib/visit_track.rb
require "net/http"
module VisitTrack
def self.event(visitor_id, name, props = {})
return if visitor_id.blank?
Net::HTTP.post(
URI("https://visitrack.app/api/track"),
{ type: "event", visitorId: visitor_id, name: name, props: props }.to_json,
"Authorization" => "Bearer #{ENV.fetch("VISITRACK_API_KEY")}",
"Content-Type" => "application/json"
)
rescue StandardError
nil # analytics must never break signup
end
endRoute Devise to the controller with devise_for :users, controllers: { registrations: "users/registrations" }. Move the call into a background job if you prefer, passing the cookie value as an argument. In views, data: { vt_goal: "pricing_cta" } on link_to renders data-vt-goal.
Revenue attribution on Ruby on Rails
Creating Stripe Checkout Sessions from a controller? Pass the visitor id as client_reference_id, then connect Stripe in VisitTrack's Settings → Revenue — payments are credited to the visitor's first source. See Stripe revenue attribution.
session = Stripe::Checkout::Session.create({
mode: "subscription",
line_items: [{ price: params[:price_id], quantity: 1 }],
success_url: "#{root_url}thanks?session_id={CHECKOUT_SESSION_ID}",
cancel_url: pricing_url,
client_reference_id: cookies[:_vt_vid]
})
redirect_to session.url, allow_other_host: trueTrack AI crawlers in Rails
AI crawlers don't execute JavaScript, so report them from the server. An after_action with a short-lived thread keeps the response fast. Files in public/ (including the default public/robots.txt) are served without reaching your controllers, so route robots.txt through a controller if you want those hits. Background: AI crawler tracking.
class ApplicationController < ActionController::Base
after_action :report_crawl
private
def report_crawl
Thread.new(request.path, request.user_agent, request.remote_ip) do |path, ua, ip|
Net::HTTP.post(
URI("https://visitrack.app/api/collect-bot"),
{ siteId: ENV.fetch("VISITRACK_SITE_ID"), path: path, userAgent: ua, ip: ip }.to_json,
"Content-Type" => "application/json"
)
rescue StandardError
nil
end
end
endScript options you might need on Ruby on Rails
All optional — add them to the same tag. Full reference: script configuration.
| Attribute | What it does |
|---|---|
data-cookieless | Store nothing in the browser; the server derives a daily-rotating id. See cookieless mode. |
data-exclude="/app/*" | Never track these paths (comma-separated, * wildcard). Checked on every navigation. |
data-allow-local | Track on localhost and *.local — for testing an install only. |
data-debug | Log every event the script sends, and why it skipped one, to the console. |
data-auto="false" | Turn off automatic events (downloads, contact clicks, form submits, rage clicks, JS errors, 404s). |
Ruby on Rails troubleshooting
Pageviews doubled after Turbo visits
The tag is in the <body> (Turbo re-runs body scripts on each visit) or in a second layout. Keep one copy in <head>.
CSP error for visitrack.app
Add the origin to both script_src and connect_src in the initializer, and restart the server.
cookies[:_vt_vid] is nil
The site is in cookieless mode, the tracker didn't load, or the request came from another root domain. In cookieless mode, use your own user id as visitorId.
KeyError: VISITRACK_SITE_ID on boot
ENV.fetch raises when the variable is missing. Set it in production, or use ENV["VISITRACK_SITE_ID"] and skip the tag when it's blank.
Ruby on Rails analytics FAQ
Where do I add an analytics script in Rails?
In the <head> of app/views/layouts/application.html.erb, rendered only when Rails.env.production?. Every view that uses the layout then loads it once.
Does VisitTrack work with Turbo Drive?
Yes. Turbo Drive updates the URL with the History API, which the tracker follows, so each visit is a pageview. Scripts in <head> aren't re-run on Turbo visits, so nothing is double-counted.
How do I allow VisitTrack in the Rails Content-Security-Policy?
Add https://visitrack.app to policy.script_src and policy.connect_src in config/initializers/content_security_policy.rb.
How do I track Devise signups in VisitTrack?
Override Devise's registrations controller, and in the create block send a signup event to /api/track with the _vt_vid cookie as visitorId once the user is persisted.
Can VisitTrack attribute Stripe revenue in a Rails app?
Yes. Pass client_reference_id with the visitor id when creating the Checkout Session, and connect the Stripe webhook in Settings → Revenue.
Keep going
- Laravel analyticsAdd VisitTrack to Laravel in your Blade layout with @production, send signups from a Registered listener, and attribute Cashier Stripe Checkout revenue.
- Django analyticsAdd VisitTrack to Django in base.html via a context processor, allow it in Django 6's built-in CSP, and send signups and Stripe revenue from views.
- Next.js analyticsAdd VisitTrack to a Next.js App Router or Pages Router app with next/script — client-side route changes are tracked automatically.
- Cloudflare Pages analyticsAdd VisitTrack on Cloudflare Pages in your HTML or with an HTMLRewriter middleware, exclude it from Rocket Loader, and track AI crawlers at the edge.
- Install the tracking scriptThe reference tag, CSP notes and how to check it's working.
- Custom events and signupsvisitrack(), data-vt-goal, scroll events and server-side signups.
- Use casesHow SaaS teams, indie hackers, stores and agencies use VisitTrack.
- VisitTrack vs Google AnalyticsCookie-free analytics with revenue attribution, compared honestly.
Add VisitTrack to your Ruby on Rails site
Cookie-free analytics with revenue attribution, live visitors, funnels and session replays. One script tag, every feature on every plan. 14 days free, no card required.
Not on Ruby on Rails? See all 29 integration guides.