Next.js and React
Drop a testimonial widget into a Next.js or React app as a component
Wrap the two-line embed in a small component, load the script with next/script, and render it wherever proof belongs. Copy a typed component for the App Router or a useEffect version for plain React.
- A typed component for the App Router
- A React version for Vite and similar setups
- Content Security Policy entries to add
Code generator
Components with your widget id filled in
Enter the widget id and copy the version for your stack. The Next.js version is two files; the React version is one.
Part 1 · Next.js App Router: the component
components/StoryProofWidget.tsx
// components/StoryProofWidget.tsx
import Script from 'next/script';
// A Server Component is fine: next/script works without 'use client'.
export default function StoryProofWidget({ id }: { id: string }) {
return (
<>
{/* Leave this div empty. The embed script fills it. */}
<div data-storyproof-widget={id} />
<Script src="https://app.storyproof.io/embed.js" strategy="afterInteractive" />
</>
);
}Part 2 · Next.js App Router: using it on a route
app/page.tsx
// app/page.tsx (or any route)
import StoryProofWidget from '@/components/StoryProofWidget';
export default function Page() {
return (
<section>
<h2>What customers say</h2>
<StoryProofWidget id="YOUR_WIDGET_ID" />
</section>
);
}Part 3 · Plain React: the component
src/StoryProofWidget.tsx
// StoryProofWidget.tsx for React without Next (Vite, CRA)
import { useEffect } from 'react';
const SRC = 'https://app.storyproof.io/embed.js';
export function StoryProofWidget({ id }: { id: string }) {
useEffect(() => {
// Load the script once per page, however many times this mounts.
if (document.querySelector(`script[src="${SRC}"]`)) return;
const script = document.createElement('script');
script.src = SRC;
script.async = true;
document.body.appendChild(script);
}, []);
return <div data-storyproof-widget={id} />;
}
// <StoryProofWidget id="YOUR_WIDGET_ID" />Run the dev server and load the route in a browser. If your site sends a Content Security Policy, add the entries below first.
The code is assembled in your browser.
The problem, solved
Where third-party widgets fight a React app, and why this one does not
- Problem 1
A pasted script tag renders nothing
Copying a vendor snippet into JSX looks right and does nothing, because React never executes script elements it renders. Half an afternoon goes on finding that out.
How StoryProof solves it
The StoryProof embed is only an empty div carrying the widget id plus one async script, a shape that maps cleanly onto next/script or a short effect.
- Problem 2
Every mount pulls the script again
A component used in three places that injects its own script triples the network work and can race itself. The bug only shows up once marketing reuses the component.
How StoryProof solves it
A single copy of embed.js fills every StoryProof widget div on the page, so the script can live in a root layout and each component renders just its div.
- Problem 3
Each new quote turns into a pull request
If testimonials are hard-coded in the repo, marketing files a ticket for every swap and a developer redeploys for a one-line content change.
How StoryProof solves it
The widget fetches its testimonials from StoryProof each time it loads, so approving a new one updates the live site with no commit and no deploy.
- Problem 4
Unreviewed customer content in production
Rendering whatever a customer typed into a shipped app is a risk nobody signed off on, and it lands on the engineer who wired it in.
How StoryProof solves it
Submissions stay out of every widget until someone approves them, and each widget id shows only the approved testimonials chosen for it.
In StoryProof
Wire it up
Step 1 of 4
Build the widget in StoryProof and note its id
Choose the layout and the approved testimonials it shows. The id is the value of data-storyproof-widget in the embed code.
Step 2 of 4
Add the component
Create the component file from the generator. In Next.js, next/script with the afterInteractive strategy, its default, loads the script early but after some hydration has happened.
Step 3 of 4
Render it where proof belongs
Pass the widget id as a prop. Several pages can use the same component with different ids.
Step 4 of 4
Allow the script in your CSP
If you set a Content-Security-Policy header, add https://app.storyproof.io to script-src and connect-src. If your policy also restricts images and media, allow the hosts your browser console reports the widget loading from.
Before you start
Framework caveats
Client-side navigation
The script loads once per page load. If a widget mounted after a client-side route change stays empty, link to that page with a normal anchor so it loads fresh, and report it to StoryProof support.
Static export works
Nothing in the component needs a server, so it works with output: export and on any static host.
No npm package
There is no StoryProof npm package. The component above is the integration.
Check a production build
React Strict Mode runs effects twice in development, which can make loading look doubled while you debug. Confirm the behaviour in a production build before you go looking for a bug.
FAQ
Questions people ask
Short answers to what usually comes up next.
Can I put a script tag directly in JSX?
React does not run script tags inserted as JSX. Use next/script in Next.js, or create the element in an effect in plain React, as in the generator.
Does it need to be a Client Component?
No. next/script works from a Server Component, so the wrapper can stay on the server.
Will it hurt Core Web Vitals?
With afterInteractive the script loads after hydration starts, so it does not hold up the first render. Reserve height for the widget with CSS on the wrapper to avoid layout shift.
Does the same component work in the Pages Router?
next/script is available in the Pages Router as well, so the same component can be imported into a page there. Test it on a route before rolling it out across the site.
Should I lazy-load a widget near the bottom of the page?
You can try strategy="lazyOnload", which waits until the browser is idle. Scroll to the widget on a slow connection afterwards and check it has rendered by the time a visitor reaches it.
Still have a question? Ask the StoryProof team or book a demo.
Get a widget id and ship the component
Create a free workspace, approve a few testimonials and paste the id into your component.
Sources
- Script Component (next/script), Next.js Documentation. Checked 2026-11-04.
Last updated 2026-11-04.