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
-
Open
src/app.html -
Paste the snippet before
</body>:<body data-sveltekit-preload-data="hover"><div style="display: contents">%sveltekit.body%</div><scriptsrc="https://spelo.ai/spelo.js"data-site-id="YOUR_SITE_ID"async></script></body> -
Run
npm run devand 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:
<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:
PUBLIC_SPELO_SITE_ID=abc123xySvelte 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:
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
npm run dev- Hit
http://localhost:5173 - 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.bodyvia Shadow DOM, outside SvelteKit’s tree. Shouldn’t cause warnings. If you see any, file an issue.