> ## Documentation Index
> Fetch the complete documentation index at: https://www.commercengine.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SvelteKit

> Add SEO/AEO surfaces and WebMCP agent tools to SvelteKit in server or adapter-static deployments.

SvelteKit can run with a request-time server or as `adapter-static`. Use the server hook when requests reach SvelteKit in production; use the prebuild asset writer when the deployment is static.

## 1. Shared config and SEO instance

```typescript src/lib/commerce-seo.config.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { defineCommerceSeoConfig } from "@commercengine/seo/config";

export const commerceSeo = defineCommerceSeoConfig({
  site: {
    name: "Acme",
    url: "https://acme.example",
    brandName: "Acme",
    locale: "en_US",
  },
  routes: { productBase: "/product", categoryBase: "/category" },
});

export const { site, routes } = commerceSeo;
```

Keep the SEO instance in server/build-safe code:

```typescript src/lib/server/seo.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createCommerceSeo } from "@commercengine/seo";
import { commerceSeo } from "$lib/commerce-seo.config";
import { storefront } from "$lib/storefront";

export const seo = createCommerceSeo({ ...commerceSeo, storefront });
```

## 2. Build product/category head data in load

```typescript src/routes/product/[slug]/+page.server.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createSvelteKitProductHead } from "@commercengine/seo/sveltekit";
import { seo } from "$lib/server/seo";
import { storefront } from "$lib/storefront";

export async function load({ params }) {
  const { data, error, response } = await storefront
    .publicStorefront()
    .catalog.getProductDetail({ product_id: params.slug });

  if (error && response.status !== 404) {
    throw new Error(error.message);
  }

  const product = data?.product ?? null;
  return {
    product,
    seoHead: product ? await createSvelteKitProductHead(seo, product) : undefined,
  };
}
```

Render the model from `<svelte:head>`:

```svelte src/routes/product/[slug]/+page.svelte theme={"theme":{"light":"github-light","dark":"github-dark"}}
<script lang="ts">
  let { data } = $props();
</script>

{#if data.seoHead}
  <svelte:head>
    <title>{data.seoHead.title}</title>
    {#each data.seoHead.meta as tag}
      {#if tag.property}
        <meta property={tag.property} content={tag.content} />
      {:else}
        <meta name={tag.name} content={tag.content} />
      {/if}
    {/each}
    {#each data.seoHead.links as tag}
      <link rel={tag.rel} href={tag.href} type={tag.type} />
    {/each}
    {#each data.seoHead.scripts as script}
      {@html `<script type="${script.type}">${script.content}</script>`}
    {/each}
  </svelte:head>
{/if}
```

<Warning>
  Gate site-wide Open Graph/Twitter fallback tags when `page.data.seoHead` exists. Svelte does not automatically deduplicate a layout's generic `og:title` or `og:image` against page-specific values.
</Warning>

As with other frameworks, add breadcrumbs separately using `seo.productUrl()` and `seo.categoryUrl()`.

## 3A. Server deployment: use the SvelteKit handle

```typescript src/hooks.server.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { createSvelteKitSeoHandle } from "@commercengine/seo/sveltekit/server";
import { seo } from "$lib/server/seo";

export const handle = createSvelteKitSeoHandle(seo, {
  robots: true,
  sitemap: true,
});
```

If your project already has a `handle`, compose the SEO handle with SvelteKit's `sequence()` helper rather than replacing the existing hook.

The handler owns Markdown mirrors, content negotiation, `llms.txt`, `sitemap.md`, robots, and XML sitemap routes.

## 3B. `adapter-static`: prebuild into `static/`

Static hosting cannot inspect `Accept` at runtime and cannot build arbitrary `/search.md?q=...` queries. Generate explicit discovery files before the SvelteKit build:

Reuse the shared `commerceSeo` declaration from step 1 and load local env files before SvelteKit/Vite starts. Running the prebuild as TypeScript keeps that config as a single source of truth:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
pnpm add -D tsx dotenv
```

```typescript scripts/generate-seo-assets.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { existsSync } from "node:fs";
import { createCommerceSeo } from "@commercengine/seo";
import { writeCommerceSeoAssets } from "@commercengine/seo/build";
import { createStorefront, Environment } from "@commercengine/storefront";
import { config as loadEnvFiles } from "dotenv";
import { commerceSeo } from "../src/lib/commerce-seo.config";

const envFiles = [
  ".env.production.local",
  ".env.local",
  ".env.production",
  ".env",
].filter(existsSync);
if (envFiles.length) loadEnvFiles({ path: envFiles });

const storeId = process.env.PUBLIC_STORE_ID;
const apiKey = process.env.PUBLIC_API_KEY;
if (!storeId || !apiKey) throw new Error("[seo] missing storefront credentials");

const seo = createCommerceSeo({
  ...commerceSeo,
  storefront: createStorefront({
    storeId,
    apiKey,
    environment:
      process.env.PUBLIC_CE_ENV === "production"
        ? Environment.Production
        : Environment.Staging,
  }),
});

await writeCommerceSeoAssets(seo, { outDir: "static" });
```

```json package.json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "scripts": {
    "build": "tsx scripts/generate-seo-assets.ts && vite build"
  }
}
```

Leave `indexable` unset in this shared script. Deployment detection then keeps preview builds non-indexable and marks only proven production deployments indexable.

Generated SEO files should be gitignored.

### Vercel and `adapter-static`

SvelteKit static output can emit route files such as `privacy-policy.html`. On Vercel, enable clean URLs so sitemap-advertised extensionless routes resolve:

```json vercel.json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "cleanUrls": true
}
```

If `/foo.html` works but `/foo` returns 404 in production, the Svelte build succeeded and only host routing is missing.

## 4. Register WebMCP in root `onMount`

```svelte src/routes/+layout.svelte theme={"theme":{"light":"github-light","dark":"github-dark"}}
<script lang="ts">
  import { goto } from "$app/navigation";
  import { onMount } from "svelte";

  let agentTools: AbortController | null = null;

  onMount(() => {
    let cancelled = false;

    // Your normal storefront/bootstrap can run in parallel.
    void initStorefront();

    void (async () => {
      const [
        { registerCommerceWebMcp },
        { createHostedCheckoutBridge },
        { getCheckout },
        { storefront },
        { routes, site },
      ] = await Promise.all([
        import("@commercengine/ai/webmcp"),
        import("@commercengine/ai/checkout"),
        import("@commercengine/checkout"),
        import("$lib/storefront"),
        import("$lib/commerce-seo.config"),
      ]);

      const registration = await registerCommerceWebMcp({
        storefront,
        siteUrl: site.url,
        routes,
        checkout: createHostedCheckoutBridge({ getState: () => getCheckout() }),
        navigation: { navigate: (url) => goto(url) },
        diagnostics: import.meta.env.DEV
          ? (event) => console.info("[commerce-ai]", event.code, event.message ?? "")
          : undefined,
      });

      if (cancelled) registration?.abort();
      else agentTools = registration;
    })();

    return () => {
      cancelled = true;
      agentTools?.abort();
    };
  });
</script>
```

<Warning>
  Keep the cleanup `return` last in `onMount`. Code placed after it is unreachable but still typechecks and builds, which can leave a storefront with no agent tools and no compiler error. Development diagnostics are the fastest way to catch this.
</Warning>

## Production checklist

* choose server hook or static prebuild from the adapter/deployment target
* static build outputs go to `static/`, not `public/`
* `vercel.json` uses `cleanUrls` for adapter-static deployments on Vercel
* generic layout metadata yields to page `seoHead`
* root `onMount` actually reaches the WebMCP registration block
* development diagnostics prove registration ran
* cleanup aborts the registration
* production deep links and `.md` mirrors both return 200

<Card title="Reference starter" icon="github" href="https://github.com/tark-ai/ce-starter-projects/tree/main/apps/soja-svelte">
  Full SvelteKit adapter-static storefront using both packages.
</Card>
