Remix installation
Remix App Testimonial Widget Embed
Remix renders every route on the server first, which suits the StoryProof embed well: a script tag written into the root Layout arrives in the HTML and the browser runs it like any other. The catch is what happens afterwards. Remix links navigate on the client, so the route that holds the widget should be reached with a document load, and Remix has a prop for exactly that.
- One script line in the root Layout
- A plain div inside the route module
- reloadDocument on links to that route
Remix files
Root Layout, route module and the link
Enter the widget id. File one is your existing app/root.tsx with a single added line, file two is the route that shows testimonials, and file three is the link that reaches it.
Part 1 · File one: root Layout
app/root.tsx
// app/root.tsx: add one line to the Layout export
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
{/* StoryProof: server rendered, so the browser runs it on a document load */}
<script src="https://app.storyproof.io/embed.js" async />
</body>
</html>
);
}Part 2 · File two: the testimonial route
app/routes/customers.tsx
// app/routes/customers.tsx
export default function Customers() {
return (
<section>
<h1>Customer stories</h1>
{/* Empty on purpose: embed.js writes the testimonials in here */}
<div data-storyproof-widget="YOUR_WIDGET_ID" />
</section>
);
}Part 3 · File three: a link with reloadDocument
app/components/SiteNav.tsx
// app/components/SiteNav.tsx (or wherever you link to it)
import { Link } from "@remix-run/react";
<Link to="/customers" reloadDocument>
Customer stories
</Link>Run the dev server, open /customers in a new tab, then go to the homepage and click your nav link. Both routes in should show the widget. View source to confirm the script appears once.
The files are assembled in your browser. The widget id is not sent anywhere.
The problem, solved
Remix habits that trip up embeds, and the fix for each
- Problem 1
Scripts will not take my src attribute
The Remix docs say src, async and defer cannot be passed to the Scripts component, because Remix manages them for its own bundles.
How StoryProof solves it
Leave Scripts alone. The StoryProof embed is one async script line plus a div, so a separate script element in the Layout body is enough.
- Problem 2
Adding the script inside each route
Copying the script into every route that shows proof spreads one concern across many files.
How StoryProof solves it
The root Layout wraps every route, and one embed.js there fills every StoryProof div on whichever page renders.
- Problem 3
Blank widget after clicking through
A normal Remix Link fetches data and renders the new route on the client, so the page never reloads.
How StoryProof solves it
Give links to the testimonial route the reloadDocument prop, which Remix documents as a document navigation, so the route arrives as a full page.
- Problem 4
Customer content rendered without review
Whatever a widget shows ends up in production markup that an engineer wired in.
How StoryProof solves it
Nothing reaches a widget until someone approves it in StoryProof, and each widget id shows only the approved testimonials chosen for it.
- Problem 5
Proof needed in many places at once
Sales emails, a pricing route and a docs footer all want the same set of testimonials.
How StoryProof solves it
A hosted Wall of Love comes with every plan, so other routes and emails can link to it instead of embedding a second widget.
In StoryProof
Add it to a Remix app
Step 1 of 4
Pick the testimonials in StoryProof
Build a widget, choose its layout and the approved testimonials it should show, and copy its id.
Step 2 of 4
Add the script to the root Layout
Open app/root.tsx and add the async script element inside body, after Scripts. Remix renders the Layout on the server, so the tag is in the HTML the browser parses.
Step 3 of 4
Place the div in the route
Render the div with data-storyproof-widget in the route module, and keep it free of children so the script owns its contents.
Step 4 of 4
Set reloadDocument on links to it
Update nav items and buttons that point at the testimonial route. Then test a fresh load and a click through from another route.
How a visitor reaches the route, and what to expect
Before you start
Remix caveats
Remix docs as of 2026-11-14
The Layout export, the Scripts attribute rules and reloadDocument follow the Remix v2 docs as read on 2026-11-14. The files have not been run on a live Remix app.
React Router 7 projects
Remix v2 work continues in React Router. If you have moved, check that its docs still describe the same Layout and reloadDocument behaviour.
Client navigation is not claimed
StoryProof does not claim a widget renders after a client side route change. Use reloadDocument rather than relying on it.
No npm package
There is no StoryProof package for Remix. The script line and the div are the whole install.
Branding on lower plans
A small "Powered by StoryProof" badge stays on widgets until Pro.
FAQ
Questions people ask
Short answers to what usually comes up next.
Does a script in root.tsx cause a hydration warning?
The same script element renders on the server and the client, so the markup matches. Watch the console during your first test anyway, since your Layout may differ from the example.
Should the script go in head instead?
It works there too because it is async. Placing it at the end of body keeps it after your own content and Remix’s bundles.
Is reloadDocument slower for visitors?
It costs one full page load for that click, which the visitor would have paid on a typed URL anyway. Other links on your site stay client side.
Can I load the script only on the testimonial route?
You can render the script element inside that route instead of the Layout, as long as visitors reach the route with a document load. The Layout is simpler when several routes show widgets.
Still have a question? Ask the StoryProof team or book a demo.
Put approved testimonials on your Remix routes
Collect and approve in StoryProof, then add one script line and one div.