Add a Voice AI Agent to SvelteKit (app.html Guide)
Add the AnveVoice voice agent to SvelteKit in src/app.html or with svelte:head in the root +layout.svelte, with CSP and prerender notes.
How to add AnveVoice to a SvelteKit app
Add the AnveVoice tag to src/app.html, below the %sveltekit.body% placeholder, to cover every page of a SvelteKit app. Or put it inside svelte:head in the root +layout.svelte. Allow the AnveVoice origin if you set a Content Security Policy. Steps checked against the official documentation on 2026-10-14.
At a glance
- Where the code goes: src/app.html (all pages) or svelte:head in src/routes/+layout.svelte
- Coding needed: One script element; Svelte 5 layout syntax if you use the layout route
- Applies to: SSR, prerendered and adapter-static builds
- Watch out for: A CSP set in the SvelteKit config blocking https://anvevoice.app
Before you start
Copy your embed ID from app.anvevoice.app (Free plan, $0, no card). SvelteKit gives you two natural homes for a script. The first is the page template, src/app.html, which wraps every response. The second is the root layout component, src/routes/+layout.svelte, which wraps every route and can add elements to the document head with svelte:head. Use app.html when you simply want the tag everywhere and prefer to keep it out of your component tree. Use the layout when you need to render the tag conditionally, for example only in production.
Method 1: src/app.html
SvelteKit's project-structure docs describe src/app.html as the page template. The %sveltekit.head% placeholder receives the link and script elements the app needs plus any svelte:head content, and %sveltekit.body% receives the rendered page. The docs recommend wrapping %sveltekit.body% in a div or other element, not placing it directly inside body, to avoid hydration bugs caused by browser extensions. Your own script goes after that wrapper, so it never interferes with hydration.
- 1. Open src/app.html — It sits next to the routes folder.
- 2. Add the script after the body placeholder — Keep the default placeholders and add the AnveVoice tag just before the closing body tag. Replace YOUR_EMBED_ID. Code: <!-- src/app.html --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> %sveltekit.head% </head> <body> <div style="display: contents">%sveltekit.body%</div> <script src="https://anvevoice.app/embed.js?embedId=YOUR_EMBED_ID" async></script> </body> </html>
- 3. Restart the dev server — SvelteKit reads the template at startup, so restart npm run dev to see the change.
Method 2: svelte:head in the root layout
A +layout.svelte file at src/routes applies to every page. In current SvelteKit, a layout receives its content as a children prop and renders it with the render tag. The svelte:head element inserts elements into the document head, and its content is exposed separately from the body during server rendering. The element must sit at the top level of the component, never inside a block or another element.
- 1. Open or create src/routes/+layout.svelte — If a layout already exists, keep its markup and styles.
- 2. Add svelte:head with the script — Place svelte:head at the top level and keep the render call for children. Code: <!-- src/routes/+layout.svelte --> <script> let { children } = $props(); </script> <svelte:head> <script async src="https://anvevoice.app/embed.js?embedId=YOUR_EMBED_ID"></script> </svelte:head> {@render children()}
Content Security Policy
SvelteKit has a csp option in its Vite plugin configuration. Setting script-src to self blocks scripts from other origins, and the docs say SvelteKit adds nonces or hashes to the inline scripts it generates. If you enable CSP, add https://anvevoice.app to script-src, otherwise the browser silently drops the tag. For scripts you add yourself in app.html, SvelteKit offers a %sveltekit.nonce% placeholder, though an origin allowance is simpler for an external file.
Prerendering and static adapters
Both methods work with prerendered output and adapter-static. The script element ends up in the generated HTML files. Because the tag is not tied to a page route, there is no need to register it for prerendering.
Check that it works
- 1. Build and preview — Run npm run build and npm run preview to test the production output.
- 2. Inspect the HTML — View source and search for anvevoice.app/embed.js.
- 3. Try voice — Open the site, click the round microphone button bottom-right, allow microphone access and ask a question.
Common problems
- Button missing with CSP on: add https://anvevoice.app to the script-src directive.
- Tag duplicated: it is in both app.html and +layout.svelte. Remove one.
- Layout errors after editing: Svelte 5 layouts use the children prop and the render tag; older syntax with a slot element belongs to earlier Svelte versions.
- Microphone blocked: the site must be on HTTPS, and no Permissions-Policy header should disable the microphone.
Sources
- SvelteKit Docs: Project structure (src/app.html) (https://svelte.dev/docs/kit/project-structure) — checked 2026-10-14
- SvelteKit Docs: Routing (+layout.svelte) (https://svelte.dev/docs/kit/routing) — checked 2026-10-14
- Svelte Docs: svelte:head (https://svelte.dev/docs/svelte/svelte-head) — checked 2026-10-14
- SvelteKit Docs: Configuration (csp) (https://svelte.dev/docs/kit/configuration) — checked 2026-10-14
Related
- How to add AnveVoice to a site hosted on Netlify
- How to add AnveVoice to a Nuxt app
- How to add AnveVoice to an Astro site
- How to add AnveVoice to a React Router app
Try AnveVoice on your site
Free plan: $0, 50,000 tokens a month, no card. Get your embed ID in the dashboard, then paste one tag.