Custom HTML / headless
Add elNudge to any website or headless storefront — vanilla HTML, Next.js, Nuxt, SvelteKit, Remix, and more.
elNudge is platform-agnostic. If you can add a <script> tag to your page, it works. This guide covers vanilla HTML sites, headless storefronts, and the most common JavaScript meta-frameworks.
Vanilla HTML — paste the snippet in <head>
For any site where you control the HTML directly, paste the loader snippet inside <head> on every page. The closest place to the top of <head> the better — the script is async and will never block rendering.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My Store</title>
<!-- elNudge — add near the top of <head> -->
<script>
(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');
</script>
</head>
<body>
<!-- your content -->
</body>
</html>
Replace sk_live_YOUR_SITE_KEY with your key from app.elnudge.com → your site → Install.
Headless / meta-frameworks
Add the snippet once in your app shell or layout component — it only needs to run once per page lifecycle.
Next.js (App Router)
In app/layout.tsx:
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<script
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');`,
}}
/>
</head>
<body>{children}</body>
</html>
);
}
Next.js (Pages Router)
In pages/_document.tsx:
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Nuxt 3
In app.vue or nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
innerHTML: `(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');`,
type: 'text/javascript',
},
],
},
},
});
SvelteKit
In src/app.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
%sveltekit.head%
<script>
(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');
</script>
</head>
<body>
%sveltekit.body%
</body>
</html>
Remix
In app/root.tsx:
import { Links, Meta, Scripts, ScrollRestoration } from '@remix-run/react';
export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<script
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');`,
}}
/>
</head>
<body>
<Scripts />
<ScrollRestoration />
</body>
</html>
);
}
TypeScript declaration
If you use TypeScript and want to call window.__eln(...) without type errors, add the following declaration to a .d.ts file in your project (e.g., types/elnudge.d.ts):
interface ElNudge {
(command: 'track', event: 'CART_ADD', payload: {
product_id: string;
product_name: string;
variant: string;
price: string;
currency: string;
quantity: number;
}): void;
(command: 'track', event: 'CART_REMOVE', payload: {
product_name: string;
price: string;
cart_total_after: string;
}): void;
(command: 'track', event: 'PURCHASE', payload: {
order_value: string;
currency: string;
item_count: number;
}): void;
(command: 'track', event: 'PAGE_VIEW', payload?: {
page_title?: string;
page_url?: string;
}): void;
q?: IArguments[];
}
declare global {
interface Window {
__eln: ElNudge;
}
}
export {};
Manual commerce events
For headless storefronts that manage their own cart state, fire events wherever your cart logic runs:
// Add to cart
window.__eln('track', 'CART_ADD', {
product_id: item.id,
product_name: item.title,
variant: item.selectedVariant,
price: item.price.toFixed(2),
currency: 'USD',
quantity: item.qty,
});
// Remove from cart
window.__eln('track', 'CART_REMOVE', {
product_name: removed.title,
price: removed.price.toFixed(2),
cart_total_after: newCart.total.toFixed(2),
});
// Purchase confirmed
window.__eln('track', 'PURCHASE', {
order_value: order.total.toFixed(2),
currency: order.currency,
item_count: order.lineItems.length,
});
Call these inside the same promise chain or callback where you update your cart state — not in a useEffect or onMount that fires after a delay.
SPA route change tracking
The SDK patches history.pushState, history.replaceState, and listens to popstate and hashchange automatically. Route changes are tracked without any extra code on Next.js, Nuxt, SvelteKit, Remix, and any router that uses the History API.
If your framework uses a custom router that does not go through the History API, fire PAGE_VIEW manually on route change:
// Example: custom router onNavigate callback
router.onNavigate((route) => {
if (window.__eln) {
window.__eln('track', 'PAGE_VIEW', {
page_title: document.title,
page_url: window.location.href,
});
}
});
Quiet zones
Suppress nudges on paths that should not show nudges (e.g., /checkout, /account, /blog/*):
<script>
(function(w,d,s,k){
w.__eln=w.__eln||function(){(w.__eln.q=w.__eln.q||[]).push(arguments)};
var t=d.createElement(s);t.async=1;
t.src='https://cdn.elnudge.com/v1/sdk.js';
t.setAttribute('data-site-key',k);
t.setAttribute('data-quiet-zones','/blog/*,/account,/account/*,/privacy,/terms');
d.head.appendChild(t);
})(window,document,'script','sk_live_YOUR_SITE_KEY');
</script>
You can also manage quiet zones from app.elnudge.com → your site → Engagement Rules → Quiet Zones.
Verify the install
- Open your site in a browser with the console visible.
- Run:
typeof window.__eln === 'function' // should return true - Navigate between routes and confirm the SDK does not re-initialize (no duplicate events).
- Fire a test
CART_ADDmanually from the console:window.__eln('track', 'CART_ADD', { product_id: 'test-1', product_name: 'Test Product', variant: '', price: '99.00', currency: 'USD', quantity: 1, }); - Check app.elnudge.com → your site → Live — the event should appear within seconds.