TanStack Start
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 />.
// 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.
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.
// 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.
Store your key as FORMSHIELD_KEY in your environment or your host’s secrets.