URL: /guides/tanstack-start

---
title: TanStack Start
description: Add the FormShield beacon and optional server reporting to a TanStack Start app
---

Integrate FormShield in TanStack Start in two layers: the beacon in the root document for client pageviews, and optional server-side reporting from a server route or middleware to catch crawlers that never run JavaScript. The beacon alone is enough to start. You need a publishable key (`fs_pub_live_…`) from your project's Settings.

## Add the beacon

Inject the script into the root document so it loads once per page. In TanStack Start the root route renders the document shell — add the `<script>` to the `<head>` of the document your `__root` route returns, alongside `<Scripts />`.

```tsx
// src/routes/__root.tsx
import { createRootRoute, Outlet, Scripts } from "@tanstack/react-router"

export const Route = createRootRoute({
  component: RootComponent,
})

function RootComponent() {
  return (
    <html lang="en">
      <head>
        <script
          async
          src="https://api.formshield.dev/js/formshield.js"
          data-fs-project-key="fs_pub_live_…"
          data-fs-action="pageview"
          data-fs-mode="pageload"
        />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}
```

Replace `fs_pub_live_…` with your key. Because the script lives in the document head, it loads once per document and records one pageview on load. Open any page and the observation appears in the dashboard Logs.

<Note>
  The publishable key is safe in client code. If your root route declares head tags through the route's `head` option instead of rendering the document directly, add the same `<script>` there — the goal is one beacon tag in the served document.
</Note>

### Route changes

`data-fs-mode="pageload"` records a pageview on the initial document load. Client-side navigations in a TanStack Start SPA do not reload the document, so they are not yet recorded as separate pageviews — that is a follow-up. For per-route analytics today, add server reporting, which sees every request the server handles.

## Capture crawlers with server reporting

The beacon never runs for crawlers and AI agents. Report requests server-side to capture them. TanStack Start runs on a server runtime, so report from a server route or your platform's request handler and send it in the background — do not block your response on it.

```ts
// report a request without blocking the response
async function reportToFormShield(request: Request): Promise<void> {
  try {
    const url = new URL(request.url)
    await fetch("https://api.formshield.dev/v1/report", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.FORMSHIELD_KEY}`,
      },
      body: JSON.stringify({
        ua: request.headers.get("user-agent") ?? undefined,
        ip:
          request.headers.get("cf-connecting-ip") ??
          request.headers.get("x-forwarded-for")?.split(",")[0]?.trim(),
        hostname: url.hostname,
        path: url.pathname,
        action: "pageview",
      }),
    })
  } catch {
    // reporting must never break the request
  }
}
```

Call `reportToFormShield(request)` from your server handler. If your platform exposes a `waitUntil` (for example on Cloudflare), pass the promise to it so the report survives after the response returns. Otherwise fire it without awaiting, capped with a short timeout so a slow report never stalls the response.

<Warning>
  On serverless runtimes an unawaited fetch can be killed when the handler returns. Prefer a platform `waitUntil`; if none is available, await the fetch with `AbortSignal.timeout(500)`. See the [server reporting guide](/guides/server-reporting#how-it-behaves) for the full trade-off.
</Warning>

Store your key as `FORMSHIELD_KEY` in your environment or your host's secrets.

## Next steps

<CardGroup cols={2}>
  <Card title="Pageview tracking" icon="chart-line" href="/guides/pageview-tracking">
    Every beacon attribute and the observation shape.
  </Card>
  <Card title="Server reporting" icon="server" href="/guides/server-reporting">
    The full `/v1/report` reference and fire-and-forget pattern.
  </Card>
</CardGroup>
