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

Script in <head> with defer
html
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="UTF-8" />
5 <title>My product page</title>
6
7 <!-- 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-widget
12 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

AttributeDownloadExecutionFor Ekoo
deferIn parallel with the HTMLAfter HTML parsing, in document orderRecommended
asyncIn parallel with the HTMLAs soon as the download finishes (no order guarantee)Technically OK, but risky if you use window.ekooOptions
(none)Blocks HTML parsingImmediateAvoid 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 to app.ekoo.co before the request itself.
  • preload — signals to the browser that this script is critical and should be fetched at high priority.
Preconnect + Preload + Script (optimal combo)
html
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 />
4
5 <!-- 2. Tell the browser to fetch the script with high priority -->
6 <link
7 rel="preload"
8 as="script"
9 href="https://app.ekoo.co/widgets/widget-4.0.0-standalone.js"
10 crossorigin
11 />
12
13 <!-- 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.

GTM / Commanders Act — preload in <head>
html
1<script>
2(function () {
3 if (window.__ekooStandaloneLoaded) return;
4 window.__ekooStandaloneLoaded = true;
5
6 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>:

Shopify — theme.liquid (head)
liquid
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':

WordPress — functions.php
php
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 parsed
9 'in_footer' => false, // → printed inside <head>
10 )
11 );
12}
13add_action( 'wp_enqueue_scripts', 'ekoo_enqueue_widget_script_in_head' );

Next.js (App Router)

Next.js — app/layout.tsx
tsx
1// app/layout.tsx — Next.js App Router
2import Script from 'next/script'
3
4export 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}
12
13 {/* beforeInteractive = injected in <head> at build time, blocks hydration */}
14 {/* Use 'afterInteractive' if you don't need the script before React hydrates */}
15 <Script
16 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

  1. Measure before with Chrome DevTools → Lighthouse (Mobile, Performance) — note the LCP and "Time to Interactive".
  2. Move the script to <head> with defer.
  3. Re-measure. You should see the Ekoo script download begin immediately (Network timeline).
  4. If LCP is still tight, add preconnect then preload.

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 the window.__ekooStandaloneLoaded guard.
  • CSP — add app.ekoo.co to script-src and connect-src.
ℹ️

Note

See also Widget lifecycle to understand how window.ekooLoad / ekooReload / ekooUnload interact with loading.

Script Loading — Documentation — Ekoo