Framer builds sites as single-page applications (SPAs), which changes how embedded widgets behave. Knowing this up front avoids the two problems studios most often report: the widget not appearing when a visitor navigates to the page from elsewhere on the site, and popups being confined to a small box.
Before you start
Generate your widget code in the bsport back office: Settings → Widgets, choose the widget type, configure your options, then scroll down and copy the code. Every time you change a setting a new code is generated, and you have to paste the new version onto your site for the change to take effect.
Method 1 — Embed component (quick, with limits)
In the Framer canvas, click Insert → search for Embed
Drag it onto the page
In the right-hand panel choose HTML and paste your widget code
Resize the frame and Publish
The single-page app issue
Framer does not reload the page when a visitor moves between sections of your site — it swaps the content in place. On top of that, the Embed component wraps your code in an iframe, so you lose control over when the widget script runs. The result: the widget can show up empty on the first visit — especially on iOS (Safari and Chrome) — and only appear after a full refresh. Because it works on refresh, the problem often goes unnoticed in testing.
⚠️ If you see the widget on refresh but not on first load or when navigating to the page, this is the cause. The fix is to replace the Embed component with a Framer code component (Method 2 below), which loads the widget script and mounts the widget itself every time the page is shown.
Method 2 — Code component (recommended)
In Framer, open the Assets panel and create a new code component.
Delete the default code and paste the code below.
import { useEffect } from "react";// Replace with the parentElement value from your bsport widget code
const WIDGET_ID = "bsport-widget-XXXXXX";export default function BsportWidget() {
useEffect(() => {
// Copy these values from your bsport widget code
const config = {
parentElement: WIDGET_ID,
companyId: 1234, // replace with your company ID
franchiseId: null,
dialogMode: 1,
widgetType: "calendar",
showFab: false,
fullScreenPopup: false,
styles: undefined,
config: {
calendar: {},
},
}; const widgetScriptId = "bsport-widget-cdn"; const loadScript = () => {
return new Promise((resolve, reject) => {
if (window.BsportWidget) {
return resolve("already loaded");
}
const existingScript = document.getElementById(widgetScriptId);
if (existingScript) {
existingScript.addEventListener("load", resolve);
existingScript.addEventListener("error", reject);
return;
}
const script = document.createElement("script");
script.id = widgetScriptId;
script.src = "https://cdn.bsport.io/scripts/widget.js";
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}; const mountWidget = (config, repeat = 1) => {
if (repeat > 50) return;
if (!window.BsportWidget) {
return setTimeout(() => mountWidget(config, repeat + 1), 100 * repeat);
}
// Avoid mounting twice
if (document.getElementById(WIDGET_ID)?.hasChildNodes()) {
return;
}
// Clear a previous registration so the widget can mount again
const list = window.BsportWidget.el_list_id;
if (list?.includes(WIDGET_ID)) {
list.splice(list.indexOf(WIDGET_ID), 1);
}
window.BsportWidget.mount(config);
}; loadScript()
.then(() => mountWidget(config))
.catch(console.error);
}, []); return <div id={WIDGET_ID}></div>;
}In your bsport widget code, find the object inside
MountBsportWidget({ ... })and copy its values (company ID, widget type, options) into theconfigobject below.<script>
...
</script>
<script>
MountBsportWidget({
"parentElement": "bsport-widget-441452",
"companyId": companyId,
"franchiseId": null,
"dialogMode": 1,
"widgetType": "calendar",
"showFab": false,
"fullScreenPopup": false,
"styles": undefined,
"config": {
"calendar": {
}
}
});
</script>Set
WIDGET_IDto theparentElementvalue from your widget code (for examplebsport-widget-441452).Delete the old Embed component from your page, then drag the new code component from Assets onto the page in its place.
Set the component's width and height to Fill so the widget isn't clipped or hidden.
Publish, then test by navigating to the page from another page of your site and on an iPhone — not only with a refresh.
Why this works
In a single-page app, moving between pages doesn't trigger a full reload, so embedded scripts — especially inside an iframe — may not run again. The code component loads the bsport script itself and mounts the widget directly into its own container each time the page is shown, so the widget renders reliably on first load, after navigating within the site, and on return visits.
Troubleshooting
The widget appears on refresh but not on first load or when navigating from another page.
The single-page app issue described above. Switch to Method 2.
A popup opens but is cut off.
The iframe limitation in Method 1. Switch to Method 2.
The code component shows nothing.
Check that WIDGET_ID matches the parentElement from your widget code, that the company ID is correct, and that the component is set to Fill.
Changes made in the back office are not showing.
The configuration does not update automatically on your site. Copy the newly generated code and replace the old one entirely, then republish. If you use Method 2, update the values in the config object instead.
The widget looks different from the back-office preview.
Enable Compatibility Mode in your widget settings, then copy and paste the new code.
Still stuck? Contact our support team with the page URL and a screenshot, and we'll take a look.




