# Schnellstart SvelteKit

Für Headless-Shops gibt es kein Plugin — dafür das SDK. Der Weg ist derselbe
wie bei den Plugins: Der Browser spricht nur mit deiner Domain, dein Server
spricht mit dem Collector. Warum das wichtig ist, steht unter
[100 % Abdeckung](/docs/abdeckung.md); hier geht es um die Umsetzung.

```bash
npm install @trackdolphin/sdk
```

Du brauchst die **Collector-URL** aus dem Dashboard unter **Einrichtung**.
Sie steht in deinem Server-Environment, nicht im Browser — über
`$env/static/private` bricht der Build, wenn sie fehlt, statt dass der Shop
still nichts sendet:

```bash
# .env
TD_ENDPOINT=https://abc123.trdph.com/collect
```

## 1. Die Proxy-Route

Eine Datei. Sie nimmt Events vom Browser an, ergänzt Besucher-IP und
User-Agent, setzt die Besucherkennung als echtes Server-Cookie und reicht
alles an den Collector weiter. Fehler landen im Server-Log, nie im Browser.

```ts
// src/routes/td/+server.ts
import { TD_ENDPOINT } from "$env/static/private";
import { createCollectProxy } from "@trackdolphin/sdk/proxy";
import type { RequestHandler } from "./$types";

const proxy = createCollectProxy({ endpoint: TD_ENDPOINT });

export const POST: RequestHandler = ({ request }) => proxy(request);
export const OPTIONS = POST;
```

## 2. SDK starten und Seitenaufrufe melden

`init()` gehört ins Root-Layout, und weil SvelteKit zwischen Routen keine
neue Seite lädt, kommen Seitenaufrufe aus `afterNavigate` — das läuft auch
beim ersten Rendern.

```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { afterNavigate } from "$app/navigation";
  import { init, track } from "@trackdolphin/sdk";

  let { children } = $props();

  // Relativ: der Browser sieht nur deine Domain. Die Besucherkennung
  // vergibt die Route (Set-Cookie) — das SDK liest sie nur.
  init({ endpoint: "/td", requireConsent: true });

  afterNavigate(() => {
    track({ type: "custom", custom_name: "page_view" });
  });
</script>

{@render children()}
```

Die Einwilligung aus deinem Cookie-Banner übergibst du mit `setConsent()` —
als `true`/`false` oder granular nach
[Consent Mode v2](/docs/sdk.md#consent-mode-v2). Mit `requireConsent: true`
bleiben Events bis dahin in der Warteschlange.

## 3. Produkt und Warenkorb

```svelte
<!-- src/routes/produkt/[slug]/+page.svelte -->
<script lang="ts">
  import { viewItem, addToCart } from "@trackdolphin/sdk";

  let { data } = $props();
  const item = { id: data.product.sku, name: data.product.name, price: data.product.price };

  // Beim Laden der Seite (und bei jedem Produktwechsel)
  $effect(() => {
    viewItem([item], item.price);
  });
</script>

<button onclick={() => addToCart([item], item.price)}>In den Warenkorb</button>
```

## 4. Der Kauf: Browser und Server mit derselben event_id

Der verlässliche Kauf kommt vom Server — aus der Bestellung, nicht aus dem
Browser. Damit Meta beide Seiten als eine Conversion erkennt, teilen sich
Browser und Server die `event_id`. Ableiten, nicht würfeln: `order_${id}`.

Serverseitig in der Form-Action, die die Bestellung abschließt:

```ts
// src/routes/checkout/+page.server.ts
import { TD_ENDPOINT } from "$env/static/private";
import { createClient } from "@trackdolphin/sdk/server";
import { redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";

const td = createClient({ endpoint: TD_ENDPOINT });

export const actions: Actions = {
  default: async ({ request }) => {
    const form = await request.formData();
    const order = await finishOrder(form);   // zuerst das Geschäft

    try {
      await td.purchase({
        event_id: `order_${order.id}`,
        value: order.total,
        currency: "EUR",
        email: order.email,        // wird hier gehasht, verlässt den Server nie im Klartext
        phone: order.phone,
        items: order.items.map((i) => ({ id: i.sku, quantity: i.quantity, price: i.price })),
      });
    } catch (e) {
      console.error("Tracking fehlgeschlagen — Bestellung ist gesichert", e);
    }

    redirect(303, `/danke/${order.id}`);
  },
};
```

Und auf der Danke-Seite dieselbe `event_id` im Browser — sie bringt die
Klick-IDs und die Besucherkennung mit, die nur der Browser kennt:

```svelte
<!-- src/routes/danke/[id]/+page.svelte -->
<script lang="ts">
  import { purchase } from "@trackdolphin/sdk";

  let { data } = $props();

  $effect(() => {
    purchase({ event_id: `order_${data.order.id}`, value: data.order.total, currency: "EUR" });
  });
</script>
```

Fällt das Tracking aus, ist die Bestellung trotzdem da. Andersherum kostet ein
Ausfall bei uns dich einen Kunden — das darf nie passieren, deshalb das
`try`/`catch` nach dem Geschäft.

## 5. Prüfen

Ruf eine Produktseite auf. Im Dashboard unter **Einrichtung** zählt der
Eventzähler innerhalb weniger Sekunden hoch; unter **Events** siehst du jedes
einzelne mit Empfangskanal und Zustellstatus je Plattform. Der Kauf erscheint
dort einmal — mit beiden Quellen, Browser und Server, unter einer `event_id`.

Kommt nichts an:

- Der Browser meldet `404` auf `/td`: Die Datei liegt nicht unter
  `src/routes/td/+server.ts`, oder ein `handle`-Hook fängt die Route ab.
- Der Browser bekommt `202` statt `204`: Die Route lebt, der Collector hat
  abgelehnt — die Ursache steht im Server-Log (meist eine falsche
  `TD_ENDPOINT`).
- `requireConsent: true`, aber nie `setConsent()` aufgerufen: Die Events
  warten in der Warteschlange.

## Rückfall, falls die Route einmal nicht antwortet

Mit `fallbackEndpoint` schickt das SDK das erste Event einer Sitzung per
`fetch` an die Route und merkt sich die Antwort. Antwortet sie nicht oder mit
5xx, geht der Rest der Sitzung direkt an den Collector — ohne Tarnung, aber
nicht verloren:

```ts
init({ endpoint: "/td", fallbackEndpoint: "https://abc123.trdph.com/collect" });
```

## Danach

[Historie importieren](/docs/backfill.md) — das zeigt dir in Zahlen, wie viel
dein bisheriges Tracking übersehen hat. Alles Weitere zur API steht auf der
[SDK-Seite](/docs/sdk.md).
