Skip to main content

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.

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

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.

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

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

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

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.

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

  1. Script Component (next/script), Next.js Documentation. Checked 2026-11-04.

Last updated 2026-11-04.