Skip to main content

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.

Shown in StoryProof when you copy a widget’s embed code.

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.

  • Problem 2

    Adding the script inside each route

    Copying the script into every route that shows proof spreads one concern across many files.

  • 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.

  • Problem 4

    Customer content rendered without review

    Whatever a widget shows ends up in production markup that an engineer wired in.

  • 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.

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.

How a visitor reaches the route, and what to expect

Way inWhat Remix doesWidget behaviour
Typed URL, bookmark or search resultServes the full documentScript runs on load and fills the div
Link with reloadDocumentHands the click to the browser like an a hrefSame as a full load
Plain Link or NavLinkRenders the route on the clientNot claimed by StoryProof; test it, or switch the link

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.

Sources

  1. <Scripts>, Remix Documentation (v2). Checked 2026-11-14.
  2. root, Remix Documentation (v2). Checked 2026-11-14.
  3. <Link>, Remix Documentation (v2). Checked 2026-11-14.

Last updated 2026-11-14.