Install on Vue
Vite Vue 3 installs in index.html. Nuxt 3 uses useHead in app.vue or the script array in nuxt.config.ts. Convert fires from onMounted on the success page.
Vue's install differs between Vite Vue 3 (static index.html) and Nuxt 3 (SSR with no index.html). Pick the path that matches your project, then fire the convert event from onMounted() on your post-purchase success page using useRoute() from vue-router.
Vite Vue 3: install in index.html
In your project's index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Your App</title>
<script>
(function(){
var s=document.createElement('script');
s.src='https://js.rekomi.com/v1/rekomi.js';
s.async=true;
s.dataset.programId='YOUR_PROGRAM_ID';
document.head.appendChild(s);
})();
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>Vite serves index.html at dev time and inlines the build output at production. The IIFE wrapper makes Vite preserve the inline script (Vite strips non-module scripts that aren't IIFE-wrapped).
Your program ID is in Rekomi at Settings > Tracking.
Nuxt 3: install via nuxt.config.ts
Nuxt 3 is SSR, so there's no static index.html. Configure the head script via the app.head.script array in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
src: "https://js.rekomi.com/v1/rekomi.js",
async: true,
"data-program-id": process.env.REKOMI_PROGRAM_ID,
},
],
},
},
});Set REKOMI_PROGRAM_ID in your .env. Nuxt loads it at build time and substitutes into the rendered head.
Alternative: useHead in app.vue. If you want the script to load conditionally (e.g., only in production), you can call useHead() inside app.vue instead:
<script setup>
useHead({
script: [
{
src: "https://js.rekomi.com/v1/rekomi.js",
async: true,
"data-program-id": import.meta.env.NUXT_REKOMI_PROGRAM_ID,
},
],
});
</script>
<template>
<NuxtPage />
</template>The useHead composable is reactive — if you wrap it in a conditional or computed, the script tag updates as state changes. For a static install, the nuxt.config.ts pattern is simpler.
Fire convert from onMounted
On your post-purchase success page (typically pages/success.vue):
<script setup>
import { onMounted } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
onMounted(() => {
const sessionId = route.query.session_id;
if (!sessionId) return;
const amount = parseInt(route.query.amount || "0", 10);
const email = route.query.email || "";
if (window.Rekomi) {
window.Rekomi.convert({
external_event_id: sessionId,
amount_cents: amount,
currency: "USD",
customer_email: email,
});
}
});
</script>
<template>
<div>Thanks for your purchase!</div>
</template>onMounted runs after the component renders and after the script has had a chance to hydrate. The if (window.Rekomi) guard handles edge cases where the loader hasn't finished on slow networks.
For production, prefer fetching canonical order data from your backend (e.g., a Nuxt server route at server/api/checkout-session/[id].ts) rather than trusting URL params.
Nuxt server-side checkout via Server Routes
Nuxt 3's server routes are great for Stripe Checkout Session creation. The Rekomi referral lives in the browser cookie; pass it via a custom header from the client:
// server/api/create-checkout-session.post.ts
import Stripe from "stripe";
export default defineEventHandler(async (event) => {
const referral = getHeader(event, "x-rekomi-referral");
const body = await readBody(event);
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: body.priceId, quantity: 1 }],
success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/pricing`,
client_reference_id: referral || undefined,
subscription_data: referral
? { metadata: { rekomi_affiliate_slug: referral } }
: undefined,
});
return { url: session.url };
});From the client (a Vue component):
<script setup>
async function startCheckout(priceId) {
const referral = window.Rekomi?.getReferral();
const { url } = await $fetch("/api/create-checkout-session", {
method: "POST",
headers: { "x-rekomi-referral": referral || "" },
body: { priceId },
});
window.location.href = url;
}
</script>Quirks worth knowing
Vite Vue 3 needs IIFE wrapping. Vite strips inline <script> tags from index.html that aren't module scripts, unless they're IIFE-wrapped (immediately-invoked function expression). The pattern above wraps in (function(){ ... })() so Vite preserves it.
Nuxt 3 useHead is reactive. Calling useHead inside a setup block makes the head reactive to state changes. For a static install, prefer the nuxt.config.ts approach because it doesn't re-render on every route change.
onMounted vs onActivated. If you use <KeepAlive> to cache the success page, onMounted fires once but onActivated fires on every cache hit. For a convert event you want onMounted (fires once).
SSR hydration. Nuxt 3 server-renders the success page, then hydrates client-side. onMounted only runs client-side, so the convert event always fires from the browser. No risk of double-firing from SSR.
Composition API only. The examples above use Vue 3's Composition API. If you're on Vue 2's Options API, use mounted() lifecycle hook with this.$route.query instead of useRoute().
Troubleshooting
Nuxt 3 script not in rendered head. Confirm REKOMI_PROGRAM_ID is set in .env and your build picked it up. Also check that you're calling Nuxt's build (not the dev server's HMR cache).
Vite preserves the script tag in dev but not in build. The IIFE wrapper is missing or malformed. Use the exact pattern above (no <script type="module">, no async/defer attributes on the script tag itself — those are inside the loader).
useRoute() returns query as undefined. Vue Router needs to be installed and configured. Confirm vue-router is in package.json and main.ts registers the router.
Convert fires before window.Rekomi is loaded. Slow network. Add a polling guard:
onMounted(async () => {
let attempts = 0;
while (!window.Rekomi && attempts++ < 50) {
await new Promise(r => setTimeout(r, 100));
}
if (window.Rekomi) { /* fire convert */ }
});Related
- JavaScript pixel reference
- Install on Stripe — for client_reference_id patterns
- Install on React — for the React equivalent
Install on React (Vite, CRA, Remix)
Vite and CRA install in index.html; Remix and React Router 7 install in app/root.tsx. SPA navigation doesn't reload pages, so convert fires from a useEffect in your success route.
Install on Rails
Server-side S2S is the primary path for Rails apps. application.html.erb head install for click capture, RekomiClient service class for HMAC-signed conversion fires.