There is no SDK and no npm package, on purpose. In a framework site you do two things: load embed.js once in your root layout, and render an inert placeholder tag wherever a vane should appear. embed.js finds the placeholders on load and after every client-side navigation. It works with SSR and static export. Below: the recipe for each stack, plus how to let CMS editors place vanes without touching code.
<!-- once, in your root layout --> <script async src="https://vaneit.com/embed.js"></script> <!-- where each vane goes --> <script type="text/plain" data-vane="your-vane-id"></script>
Before you start
You need your Vane ID. In your VANE it dashboard, open the vane, find Embed on your site, and click Copy ID under the snippet. Pick a placement there too if you want one: it is the data-place value in the copied line (float-br, float-bl, over-tl, over-tr, over-bl, over-br, over-c or section; leave it off for inline). The vane must be set live.
Next.js
App Router (Pages Router: same idea in _app)
Load embed.js once in the root layout with next/script. Where each vane goes, render an inert placeholder tag.
- In
app/layout.tsx, add<Script src="https://vaneit.com/embed.js" />inside<body>. Once, for the whole site. - Where the vane belongs, render
<script type="text/plain" data-vane="your-vane-id" />. Adddata-placefor a placement other than inline. - That is all. Server components are fine, and new pages reached by client-side navigation render their vanes on their own.
Do not put data-vane on the <Script> itself. next/script appends to the end of the body and loads each address only once, so only one vane would render, at the bottom of the page.
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script src="https://vaneit.com/embed.js" />
</body>
</html>
);
}
// any page or component
export default function Post() {
return (
<article>
<p>Twenty years taught us to roast light.</p>
<script type="text/plain" data-vane="your-vane-id" />
</article>
);
} Did it work? Run the dev server and the hex renders right next to the placeholder. Navigate to another route with a vane and it renders there too; navigate away and it is removed.
React (Vite, Create React App, Remix)
a small component
Same pattern: embed.js once in index.html (or your root route), a placeholder wherever a vane goes.
- Add
<script async src="https://vaneit.com/embed.js"></script>once toindex.html, with nodata-vane. - Make a tiny
Vanecomponent that renders the placeholder, and use it wherever you like. - Give it
key={id}so that switching to a different vane swaps the placeholder rather than editing it in place.
A type="text/plain" script never executes, so React leaves it where it is and does not move it to the head. If you ever need to force a render (you should not), call window.VaneIt?.scan() in a useEffect.
<!-- index.html, once -->
<script async src="https://vaneit.com/embed.js"></script>
// Vane.jsx
export function Vane({ id, place }) {
return <script key={id} type="text/plain" data-vane={id} data-place={place} />;
}
// anywhere
<Vane id="your-vane-id" />
<Vane id="your-vane-id" place="section" /> Did it work? The hex appears next to each placeholder, including in components that mount after a route change or when a modal opens.
Vue and Nuxt
component is="script"
Vue removes script tags from templates, so render the placeholder through a dynamic component instead.
- Load embed.js once: in
index.htmlfor Vue, or inapp.head.scriptinnuxt.configfor Nuxt. - Where the vane goes, use
<component is="script" type="text/plain" …>. Vue renders that as a real, inert script tag. - Bind the id with
:data-vaneand add:keyif the id can change.
A plain <script> in a Vue template is dropped by the compiler with a “Tags with side effect” warning. The component is="script" form avoids that.
<!-- Vane.vue -->
<script setup>
defineProps({ id: String, place: String });
</script>
<template>
<component is="script" type="text/plain" :key="id" :data-vane="id" :data-place="place" />
</template> Did it work? The hex renders next to the placeholder, and pages reached through Vue Router render their vanes without a reload.
Svelte and SvelteKit
svelte:element this="script"
Svelte reserves script tags for component code, so render the placeholder with svelte:element.
- Load embed.js once: in
src/app.htmlfor SvelteKit, orindex.htmlfor plain Svelte. - Where the vane goes, use
<svelte:element this="script" type="text/plain" data-vane={id} />. - Wrap it in
{#key id}if the id can change.
Nothing else is needed for SvelteKit navigation: new placeholders are picked up when a page mounts, and removed vanes go with their page.
<!-- Vane.svelte -->
<script>
let { id, place = undefined } = $props();
</script>
{#key id}
<svelte:element this="script" type="text/plain" data-vane={id} data-place={place} />
{/key} Did it work? The hex renders next to the placeholder on first load and after client-side navigation.
Astro
plain snippet, is:inline
Astro pages are HTML, so the normal snippet works. Mark it is:inline so Astro leaves it exactly as written.
- Paste the snippet from your dashboard into the
.astrofile where the vane belongs, and addis:inline. - Inside a React, Vue or Svelte island, use that framework’s placeholder recipe above instead.
- With view transitions (
<ClientRouter />), vanes on the new page render after each navigation.
Several snippets on one page are fine: each renders its own vane and the engine loads once.
<p>Twenty years taught us to roast light.</p> <script is:inline async src="https://vaneit.com/embed.js" data-vane="your-vane-id"></script>
Did it work? The hex renders where the snippet sits in the built page.
Sanity, Contentful and friends
a field on the content model
Let editors place vanes without touching code by storing the vane id as a field, then rendering the placeholder in your frontend when the field is set.
- Add a plain text field to the content type, for example
vaneId, with a note telling editors where to find it. - Optionally add a placement field with the same values the dashboard offers.
- In your frontend, render your framework’s placeholder (above) only when the field has a value.
- For Portable Text or a rich text field, register a custom block type so editors can drop a vane mid-article.
Tell editors to paste the Vane ID from the dashboard, not the whole snippet. There is a Copy ID button under the code for exactly this. If they paste the snippet anyway, strip it server-side by pulling the value out of data-vane.
Did it work? Set the field on one entry, publish, and the vane appears on that entry only. Leave it blank on another and nothing renders.
SSR, CSP and routing
| Concern | Answer |
|---|---|
| Server rendering | Safe. The placeholder is plain markup and nothing runs during render, so SSR, SSG and static export all work. Nothing to guard with a typeof window check. |
| Hydration | The placeholder is inert, so the server and client markup match. The vane is added next to it only after the page has loaded and the vane has arrived from our servers. |
| Client-side routing | embed.js watches for placeholders added after load, so components mounted on navigation render their vanes. When a placeholder is removed, its vane goes with it. To force a check yourself, call window.VaneIt?.scan(). |
| Content Security Policy | script-src https://vaneit.com; connect-src https://vzccllblesgmopqapmja.supabase.co; style-src 'unsafe-inline'; font-src data:; img-src https://vaneit.com https://vzccllblesgmopqapmja.supabase.co; frame-src https://vaneit.com (the last only if you use the iframe). No eval. |
| Bundle size | Zero. Nothing enters your bundle; the loader is fetched from our CDN and cached for four hours. |
| TypeScript | Nothing to import. If your setup complains about window.VaneIt, declare it as { scan(): void }. |
Common questions
Is there an npm package?
No, deliberately. The integration is one script in your layout plus a placeholder tag, so a package would only wrap something already small and give you a dependency to keep updated. If a React component would genuinely help your team, tell us what you would want from it.
Why not just put the normal script tag in my component?
Frameworks treat a script tag with src specially. next/script appends it to the end of the body and loads each src only once, so only one vane renders and it lands at the bottom of the page. React 19 moves async scripts into the head and removes duplicates. Vue drops script tags from templates altogether. The placeholder has type="text/plain", which no framework runs, moves or removes, so it stays exactly where you put it.
Does it work with server-side rendering?
Yes. The placeholder is plain markup on the server and nothing runs until the browser loads embed.js. There is no window access during render, so it will not break hydration or a static export.
Does it survive client-side route changes?
Yes. embed.js watches the page for new placeholders, so a vane inside a component that mounts after navigation renders on its own. When a placeholder leaves the page, its vane (including a floating one) is removed with it. If you hit a case where it does not, tell us the framework and router and we will look.
Where do I put the id in a headless CMS setup?
Two patterns. Either hardcode it in the component if the same question runs across a template, or store the id as a plain text field on the content model so editors can choose per entry. The second is worth it only when each entry genuinely needs its own tally.
Content Security Policy?
script-src https://vaneit.com; connect-src https://vzccllblesgmopqapmja.supabase.co; style-src 'unsafe-inline'; font-src data:; img-src https://vaneit.com https://vzccllblesgmopqapmja.supabase.co; frame-src https://vaneit.com (the last only if you use the iframe). No eval.
Can I self-host embed.js?
We would rather you did not. Pinning a copy means you stop getting format additions and fixes, and the loader is versioned against the API. If you have a policy reason you cannot load third-party scripts, get in touch and we will talk through the options honestly rather than pretend there is a clean one.
One script, any stack
No SDK to install, nothing in your bundle, nothing to keep updated.
Start free Plain HTML