Substack Embed — React

How to add a Substack subscribe form or feed widget to a React app. Two approaches: global script loading and a self-contained component with useEffect.

When you click Copy embed code in your Supascribe dashboard, you get a snippet with your embed ID and script URL already filled in.

There are two ways to set it up in React:

Load the script once in public/index.html — it only needs to appear once, regardless of how many embeds you use:

<!-- public/index.html -->
<body>
  <div id="root"></div>
  <script src="https://js.supascribe.com/v1/loader/example.js" async></script>
</body>

With the script loaded globally, the component is a plain div wrapper — no browser APIs, no side effects:

// components/SupascribeEmbed.tsx
interface SupascribeEmbedProps {
  embedId: string;
  type: "subscribe" | "feed";
}
 
export default function SupascribeEmbed({ embedId, type }: SupascribeEmbedProps) {
  if (type === "feed") {
    return <div data-supascribe-embed-id={embedId} data-supascribe-feed />;
  }
  return <div data-supascribe-embed-id={embedId} data-supascribe-subscribe />;
}

Use it anywhere:

// App.tsx
import SupascribeEmbed from "./components/SupascribeEmbed";
 
export default function App() {
  return (
    <main>
      <section>
        <h2>Get the newsletter</h2>
        <SupascribeEmbed embedId="abc123" type="subscribe" />
      </section>
 
      <section>
        <h2>Latest posts</h2>
        <SupascribeEmbed embedId="abc123" type="feed" />
      </section>
    </main>
  );
}

Method B: Self-contained component with useEffect

If you can't edit index.html (e.g. you're working within a larger app), load the script from the component itself. The component guards against loading the script more than once — safe to use multiple times on the same page:

// components/SupascribeEmbed.tsx
import { useEffect } from "react";
 
interface SupascribeEmbedProps {
  embedId: string;
  type: "subscribe" | "feed";
}
 
export default function SupascribeEmbed({ embedId, type }: SupascribeEmbedProps) {
  useEffect(() => {
    const scriptSrc = "https://js.supascribe.com/v1/loader/example.js";
    if (document.querySelector(`script[src="${scriptSrc}"]`)) return;
 
    const script = document.createElement("script");
    script.src = scriptSrc;
    script.async = true;
    document.body.appendChild(script);
  }, []);
 
  if (type === "feed") {
    return <div data-supascribe-embed-id={embedId} data-supascribe-feed />;
  }
  return <div data-supascribe-embed-id={embedId} data-supascribe-subscribe />;
}

Usage is the same:

// App.tsx
import SupascribeEmbed from "./components/SupascribeEmbed";
 
export default function App() {
  return (
    <main>
      <SupascribeEmbed embedId="abc123" type="subscribe" />
      <SupascribeEmbed embedId="abc123" type="feed" />
    </main>
  );
}

If you have a shared Footer component that mounts once per page, you can load the script there instead of index.html:

// components/Footer.tsx
import { useEffect } from "react";
 
export default function Footer() {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://js.supascribe.com/v1/loader/example.js";
    script.async = true;
    document.body.appendChild(script);
  }, []);
 
  return (
    <footer>
      {/* footer content */}
    </footer>
  );
}

Then use the embed div directly in any component — no component wrapper needed:

// components/NewsletterSection.tsx
export default function NewsletterSection() {
  return (
    <section>
      <h2>Get the newsletter</h2>
      <div data-supascribe-embed-id="abc123" data-supascribe-subscribe />
    </section>
  );
}

Using Next.js?

The pattern is the same — load the script in app/layout.tsx instead. See the Next.js example.

Next.js Example

App Router setup with layout.tsx.

Supascribe

Ready to grow your Substack?

Add a subscribe embed or landing page to any website in minutes. Free to start.

Start for free →