Building blocksInstallFramework guides

Gatsby

SSR-safe Gatsby integration for the booking widget.

Gatsby pre-renders pages at build time, so the widget script must only load in the browser. Everything below runs inside useEffect, which never executes during the build.

src/pages/book.jsx
import * as React from "react";

const CDN_URL = "https://widgets.medos.one/v2/unified.js";
const CONTAINER_ID = "medos-booking-gatsby";

/** Reuses an existing tag so navigation doesn't re-download the bundle. */
function loadMedosBooking() {
  if (window.MedosBooking) return Promise.resolve();

  const existing = document.querySelector(`script[src="${CDN_URL}"]`);
  if (existing) {
    return new Promise((resolve) =>
      existing.addEventListener("load", () => resolve(), { once: true }),
    );
  }

  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = CDN_URL;
    script.async = true;
    script.onload = () => resolve();
    script.onerror = () => reject(new Error("Medos Booking failed to load"));
    document.head.appendChild(script);
  });
}

export default function BookPage() {
  React.useEffect(() => {
    let cancelled = false;

    loadMedosBooking()
      .then(() => {
        if (cancelled) return;
        window.MedosBooking.init({
          apiKey: "mk_your_publishable_key",
          mode: "inline",
          containerId: CONTAINER_ID,
        });
      })
      .catch((err) => console.error(err));

    return () => {
      cancelled = true;
      // Inline mode has no close() — clear the container so a remount
      // doesn't stack a second widget.
      const el = document.getElementById(CONTAINER_ID);
      if (el) el.innerHTML = "";
    };
  }, []);

  return (
    <main>
      <h1>Book an appointment</h1>
      <div id={CONTAINER_ID} style={{ minHeight: 600 }} />
    </main>
  );
}

Why the cancelled flag

React runs effects twice in development, and Gatsby's client router remounts pages. init() is not idempotent — a second call mounts a second widget into the same container — so the flag plus the container reset keep it to one.

gatsby-ssr.js alternative

You can ship the script tag through setHeadComponents instead. You still need a useEffect to call MedosBooking.init(), and the readiness check above still applies, since a tag in <head> isn't guaranteed to have executed before your component mounts.

On this page