Script loading — performance
Load the Ekoo widget from the <head> for a faster start
By default, many integrations place the Ekoo script at the end of the <body>. That's safe but suboptimal: the browser waits until HTML parsing ends before starting the download. Moving it into the <head> with defer lets the browser download the script in parallel with the HTML, which shortens the time before the widget appears and improves your LCP.
It's safe
The Ekoo widget auto-initializes on the window.load event. Placing it in the <head> with defer introduces no race condition: the script will wait for the DOM (and therefore your <ekoo-widget> tags) to be ready before mounting anything.
Recommended pattern
1<!DOCTYPE html>2<html>3<head>4 <meta charset="UTF-8" />5 <title>My product page</title>67 <!-- Ekoo widget — loaded as early as possible, executed after the HTML is parsed -->8 <script src="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js" defer></script>9</head>10<body>11 <ekoo-widget12 data-ekoo="YOUR_WEBSITE_ID"13 data-ekoo-product-id="MY_PRODUCT_ID"14 data-ekoo-locale="fr"15 ></ekoo-widget>16</body>17</html>defer vs async vs no attribute
| Attribute | Download | Execution | For Ekoo |
|---|---|---|---|
defer | In parallel with the HTML | After HTML parsing, in document order | Recommended |
async | In parallel with the HTML | As soon as the download finishes (no order guarantee) | Technically OK, but risky if you use window.ekooOptions |
| (none) | Blocks HTML parsing | Immediate | Avoid in <head> |
Don't use async with window.ekooOptions
If you configure the widget through window.ekooOptions (rather than via data-ekoo-* attributes), stay on defer. With async, the script can run before your ekooOptions declaration, producing an empty initialization.
Going further: preconnect + preload
For an extra gain (useful when your LCP is tight), add two resource hints before the <script> tag:
preconnect— opens the TCP/TLS connection toapp.ekoo.cobefore the request itself.preload— signals to the browser that this script is critical and should be fetched at high priority.
1<head>2 <!-- 1. Open the TCP / TLS connection to app.ekoo.co as soon as possible -->3 <link rel="preconnect" href="https://app.ekoo.co" crossorigin />45 <!-- 2. Tell the browser to fetch the script with high priority -->6 <link7 rel="preload"8 as="script"9 href="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js"10 crossorigin11 />1213 <!-- 3. Actually load the script (defer = parsed after the HTML) -->14 <script src="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js" defer></script>15</head>Variants per stack
Google Tag Manager / Commanders Act
Create a dedicated Custom HTML tag, trigger All Pages (or Consent Initialization — All Pages if you handle consent). This tag only loads the script — the <ekoo-widget> container insertion stays in your per-product-page tag.
1<script>2(function () {3 if (window.__ekooStandaloneLoaded) return;4 window.__ekooStandaloneLoaded = true;56 var s = document.createElement('script');7 s.src = 'https://app.ekoo.co/widgets/widget-4.0.0-standalone.js';8 s.defer = true;9 document.head.appendChild(s);10})();11</script>Note
The window.__ekooStandaloneLoaded guard prevents any double load if the tag fires multiple times (virtual navigation, stacked triggers).
Shopify
Add the tag inside layout/theme.liquid, within the <head>:
1{% comment %} layout/theme.liquid — inside <head> {% endcomment %}2<link rel="preconnect" href="https://app.ekoo.co" crossorigin>3<script src="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js" defer></script>WordPress
Use wp_enqueue_script with in_footer => false and strategy => 'defer':
1function ekoo_enqueue_widget_script_in_head() {2 wp_enqueue_script(3 'ekoo-widget',4 'https://app.ekoo.co/widgets/widget-4.0.0-standalone.js',5 array(),6 '4.0.0',7 array(8 'strategy' => 'defer', // executed after HTML is parsed9 'in_footer' => false, // → printed inside <head>10 )11 );12}13add_action( 'wp_enqueue_scripts', 'ekoo_enqueue_widget_script_in_head' );Next.js (App Router)
1// app/layout.tsx — Next.js App Router2import Script from 'next/script'34export default function RootLayout({ children }: { children: React.ReactNode }) {5 return (6 <html>7 <head>8 <link rel="preconnect" href="https://app.ekoo.co" crossOrigin="anonymous" />9 </head>10 <body>11 {children}1213 {/* beforeInteractive = injected in <head> at build time, blocks hydration */}14 {/* Use 'afterInteractive' if you don't need the script before React hydrates */}15 <Script16 src="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js"17 strategy="beforeInteractive"18 />19 </body>20 </html>21 )22}Measure the gain
- Measure before with Chrome DevTools → Lighthouse (Mobile, Performance) — note the LCP and "Time to Interactive".
- Move the script to <head> with
defer. - Re-measure. You should see the Ekoo script download begin immediately (Network timeline).
- If LCP is still tight, add
preconnectthenpreload.
Common pitfalls
- Forgetting
defer— without this attribute, a<script>in the<head>blocks HTML parsing — you gain on the download but lose on rendering. - Loading the script on every page without a container — that's OK (the script does nothing without an
<ekoo-widget>), but ~30 KB transferred for nothing. Filter by page type when possible. - Double load — if you load the script both in the
<head>and via another tag (GTM, plugin), use thewindow.__ekooStandaloneLoadedguard. - CSP — add
app.ekoo.cotoscript-srcandconnect-src.
Note
See also Widget lifecycle to understand how window.ekooLoad / ekooReload / ekooUnload interact with loading.