Ruby on Rails installation
Rails Testimonial Widget with Turbo Drive
In a Rails app the embed belongs in a partial you render from any view. The part that trips people up is Turbo Drive: it swaps page bodies without a full load, so where the script tag sits decides whether the widget fills on the second visit as well as the first.
- An ERB partial that takes the widget id as a local
- A per-view switch for a full reload under Turbo
- The script_src entry for your CSP initializer
ERB generator
A partial, a render call and two optional extras
Type your widget id and the render call picks it up. Parts 1 and 2 are the install. Part 3 is only for a view where Turbo leaves the widget empty, and Part 4 only applies if your app already ships a Content Security Policy.
Part 1 · The shared partial
app/views/shared/_storyproof_widget.html.erb
<%# app/views/shared/_storyproof_widget.html.erb %>
<%# The div stays empty in your markup; embed.js fills it in the browser. %>
<div data-storyproof-widget="<%= widget_id %>"></div>
<script src="https://app.storyproof.io/embed.js" async></script>Part 2 · Rendering it in a view
app/views/<controller>/<action>.html.erb
<%# Any view, e.g. app/views/pages/home.html.erb %>
<section class="customer-proof">
<h2>What our customers say</h2>
<%= render partial: "shared/storyproof_widget", locals: { widget_id: "YOUR_WIDGET_ID" } %>
</section>Part 3 · Optional: force a full load for one view
Layout head plus content_for :head in the view
<%# app/views/layouts/application.html.erb, inside <head> %>
<%= yield :head %>
<%# Only in a view where the widget misbehaves after Turbo visits %>
<% content_for :head do %>
<meta name="turbo-visit-control" content="reload">
<% end %>Part 4 · Optional: allow the script host in your CSP
config/initializers/content_security_policy.rb
# config/initializers/content_security_policy.rb
Rails.application.configure do
config.content_security_policy do |policy|
# Keep your existing sources and add the StoryProof host.
policy.script_src :self, :https, "https://app.storyproof.io"
end
endStart the server, open the page, then click away to another route and back without refreshing. If the widget fills both times, Turbo is happy with it; if the second visit is blank, add Part 3 to that view.
The widget id only fills these templates on this page. It is not stored or sent.
The problem, solved
What goes wrong with embeds in a Turbo app, and the StoryProof side of the fix
- Problem 1
Blank widget after clicking a nav link
Turbo Drive fetches the next page and swaps its body in. A script that only ran in the head on the first full load never sees the new div.
How StoryProof solves it
The StoryProof embed is a div plus one async script, so both can sit together in a partial inside the body, where Turbo evaluates script elements each time it renders the page.
- Problem 2
Three partials loading the same script
Pricing, home and a case study page each render the partial, and on a page with two widgets the script tag appears twice.
How StoryProof solves it
One copy of embed.js fills every StoryProof div on a page, so on views with several widgets you can render the script once and the divs on their own.
- Problem 3
Every new quote becomes a deploy
Testimonials hard coded in a YAML file or a seed mean a pull request each time sales forwards a nice email.
How StoryProof solves it
The quotes live in StoryProof. You pick which approved testimonials each widget id shows there, and the view only carries the id.
- Problem 4
I do not want another table and admin
A testimonials gem means migrations, an upload path for video and a moderation screen that somebody has to maintain.
How StoryProof solves it
Customers record a video, leave an audio note or write a review from one collection link in the browser, and nothing goes public until it is approved, so the app keeps no testimonial data of its own.
In StoryProof
Ship it in a Rails app
Step 1 of 5
Fill the widget in StoryProof
Approve the testimonials you want, then choose which of them this widget shows and copy its widget id.
Step 2 of 5
Add the partial
Create app/views/shared/_storyproof_widget.html.erb from Part 1. The leading underscore marks it as a partial; you render it without one.
Step 3 of 5
Render it where proof belongs
Call render with the widget_id local from the view that needs it (Part 2). Different views can pass different ids, so a pricing page and a home page can show different selections.
Step 4 of 5
Test a Turbo visit, not just a refresh
Load the page directly, then reach it again through an in app link. Only the second check tells you how Turbo treats the embed on your site.
Step 5 of 5
Fall back to a full load if needed
If the widget stays empty after a Turbo visit, add the turbo-visit-control reload meta for that view (Part 3), or mark the links that lead there with data-turbo="false".
Where to put the script tag in a Rails layout
Before you start
Limits in a Rails app
Repeat evaluation is not documented by StoryProof
Turbo re-runs body scripts on each render. StoryProof has not published how embed.js behaves when evaluated again on the same tab, which is why this guide tells you to test a Turbo visit and keeps the reload fallback.
Turbo cache previews
On some visits Turbo shows a cached copy of a page before the fresh one arrives, so the widget area can briefly look different from the final render.
No gem, no engine
StoryProof is not a Ruby gem and adds no models or routes to your app. It is the embed in a partial, and the testimonials stay in StoryProof.
Badge on lower plans
Widgets carry a small "Powered by StoryProof" badge until Pro.
Docs as of 2026-11-13
The Turbo and Rails behaviour here follows the Turbo handbook and the Rails guides as read on 2026-11-13. These steps have not yet been run in a real Rails app by the StoryProof team.
FAQ
Questions people ask
Short answers to what usually comes up next.
Can I use javascript_include_tag for embed.js?
It returns a script tag for a source you give it, including an external URL, so it can produce the tag. Keep that tag in the body partial rather than the layout head, for the Turbo reasons above, and keep the async attribute.
Do I need Stimulus for this?
No. The embed is plain HTML and one script. A Stimulus controller is only worth writing if you have confirmed a specific Turbo problem the partial and reload fallback do not solve.
Will it work if Turbo is switched off?
Yes. Without Turbo Drive every navigation is a full page load, so the partial behaves like the embed on any static HTML page.
Can logged in customers leave a testimonial from inside the app?
Link to your StoryProof collection link from an account page or a post onboarding screen. The customer records or writes in the browser and the result waits for your approval.
Is there a no code option for the marketing team?
Every plan includes a hosted Wall of Love page with its own link. Linking to it needs no view changes at all.
Still have a question? Ask the StoryProof team or book a demo.
Get a widget id for your partial
Collect testimonials, approve the strongest and render one partial in your Rails views.
Sources
- Building Your Turbo Application, Turbo Handbook (Hotwire). Checked 2026-11-13.
- Navigate with Turbo Drive, Turbo Handbook (Hotwire). Checked 2026-11-13.
- Layouts and Rendering in Rails, Ruby on Rails Guides. Checked 2026-11-13.
- Securing Rails Applications: Content Security Policy, Ruby on Rails Guides. Checked 2026-11-13.
Last updated 2026-11-13.