Skip to content
GitHub
Get started →

Install on SvelteKit

SvelteKit’s src/app.html is the HTML shell for every page. Add the widget there and it runs everywhere — on SSR pages, client-nav’d pages, and prerendered pages.

Steps

  1. Open src/app.html

  2. Paste the snippet before </body>:

    <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
    <script
    src="https://spelo.ai/spelo.js"
    data-site-id="YOUR_SITE_ID"
    async
    ></script>
    </body>
  3. Run npm run dev and verify the orb appears.

Route changes

SvelteKit’s router uses history.pushState. The widget hooks into this automatically — it re-reads DOM and page metadata on each route change. No additional code.

As a +layout.svelte (optional)

If you’d rather control it in a root layout, use onMount:

src/routes/+layout.svelte
<script lang="ts">
import { onMount } from 'svelte'
import { PUBLIC_SPELO_SITE_ID } from '$env/static/public'
onMount(() => {
if (!PUBLIC_SPELO_SITE_ID) return
if (document.querySelector(`script[data-site-id="${PUBLIC_SPELO_SITE_ID}"]`)) return
const s = document.createElement('script')
s.src = 'https://spelo.ai/spelo.js'
s.setAttribute('data-site-id', PUBLIC_SPELO_SITE_ID)
s.async = true
document.body.appendChild(s)
})
</script>
<slot />

Declare the env var:

.env
PUBLIC_SPELO_SITE_ID=abc123xy

Svelte 5 / Runes

Works identically. onMount is still the right hook.

SSR / prerender

The widget is client-only. +page.server.ts and +layout.server.ts won’t execute it. On the server, the <script> tag is serialized into HTML; on the client, it executes.

If you want to conditionally include the widget (e.g. only on authenticated routes), wrap it in a {#if} block in +layout.svelte:

{#if $page.data.user}
<!-- onMount-based injection -->
{/if}

Adapter notes

Spelo works with every SvelteKit adapter: @sveltejs/adapter-auto, adapter-vercel, adapter-netlify, adapter-node, adapter-cloudflare, adapter-static.

The widget is a pure browser script — it doesn’t care which adapter runs your server.

CSP

If you’ve set CSP via svelte.config.js:

svelte.config.js
const config = {
kit: {
csp: {
directives: {
'script-src': ['self', 'https://spelo.ai'],
'connect-src': ['self', 'https://api.spelo.ai', 'https://api.openai.com', 'wss:'],
'media-src': ['self', 'blob:'],
},
},
},
}

Verify

  1. npm run dev
  2. Hit http://localhost:5173
  3. Orb at the bottom; click → allow mic → speak

Troubleshooting

  • Widget doesn’t appear on prerendered routes → edit app.html (it’s included in every prerendered HTML file).
  • Hydration warnings → the widget renders into document.body via Shadow DOM, outside SvelteKit’s tree. Shouldn’t cause warnings. If you see any, file an issue.