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