See which campaign brings your TanStack buyers

One provider counts every route change, and request middleware hands your server functions a client for the orders that paid.

Package @mirafive/sdk-tanstack

Set up in 5 steps

  1. Install the packages

    It needs React 18.3 or 19 and Node.js 20 or later, and the /start entry needs @tanstack/react-start 1.168 or later. The middleware loads the server SDK lazily on the server, so it never reaches the browser bundle.

    Code for step 1 of the TanStack Start setup: Install the packages. npm install @mirafive/sdk-tanstack @mirafive/sdk-react @mirafive/sdk-browser @mirafive/sdk-server

  2. Register the middleware

    Put the secret key of a server source from Data → Sources in MIRAFIVE_SECRET_KEY, never with a VITE_ prefix. Create the middleware once, at module scope, because createStart() runs its factory per request.

    Code for step 2 of the TanStack Start setup: Register the middleware. import { miraMiddleware } from '@mirafive/sdk-tanstack/start' import { createStart } from '@tanstack/react-start' const mirafive = miraMiddleware() export const startInstance = createStart(() => ({ requestMiddleware: [mirafive], }))

  3. Wrap the root route

    Put the website key of your website source in VITE_MIRAFIVE_KEY and pass it to the provider, which counts the first page and every TanStack Router navigation. The package does not read import.meta.env itself, because Vite only replaces it in your own code.

    Code for step 3 of the TanStack Start setup: Wrap the root route. import { MiraProvider } from '@mirafive/sdk-tanstack' import { createRootRoute, HeadContent, Outlet, Scripts } from '@tanstack/react-router' import type { ReactNode } from 'react' export const Route = createRootRoute({ shellComponent: RootDocument, component: RootComponent, }) function RootComponent() { return ( <MiraProvider websiteKey={import.meta.env.VITE_MIRAFIVE_KEY}> <Outlet /> </MiraProvider> ) } function RootDocument({ children }: { children: ReactNode }) { return ( <html lang="en"> <head> <HeadContent /> </head> <body> {children} <Scripts /> </body> </html> ) }

  4. Send the paid order from a server function

    context.mira is the server SDK's client, one per process. track() only buffers, and the middleware flushes once the response is ready, also when the handler throws.

    Code for step 4 of the TanStack Start setup: Send the paid order from a server function. import { createServerFn } from '@tanstack/react-start' export const confirmPayment = createServerFn({ method: 'POST' }) .validator((data: { userId: string }) => data) .handler(async ({ context, data }) => { context.mira.track('order_paid', { userId: data.userId, properties: { revenue: 328, currency: 'EUR' } }) })

  5. Check both sides

    Deploy, open a page and watch Data → Live; localhost sends nothing unless you pass trackLocalhost to the provider. For the server, call this function once. It resolves with the reason install_check, nothing is stored, and you remove it afterwards.

    Code for step 5 of the TanStack Start setup: Check both sides. import { createServerFn } from '@tanstack/react-start' export const installCheck = createServerFn({ method: 'POST' }).handler(async ({ context }) => { return context.mira.send([{ name: '$install_check' }]) })

MIRA FIVE is analytics built around the person. In a TanStack Start app one package covers both sides: a provider with the React hooks for the browser, and request middleware that puts a server client on context for server functions and server routes.

How do I record what matters?

In a component, read the client with useMira() and call track. useTrackOnMount(name, properties) sends one event when a component appears, once also under StrictMode.

import { useMira } from '@mirafive/sdk-tanstack'

export function AddToCart() {
  const mira = useMira()

  return <button onClick={() => mira.track('added_to_cart', { product: 'merino-crew' })}>Add to cart</button>
}

How does a paid order reach its channel?

Through the person who paid. Switch the website source to Full under Data → Sources, then give the provider full mode and the identity() plugin:

import { identity } from '@mirafive/sdk-browser/identity'
import { MiraProvider } from '@mirafive/sdk-tanstack'
import { Outlet } from '@tanstack/react-router'

function RootComponent() {
  return (
    <MiraProvider websiteKey={import.meta.env.VITE_MIRAFIVE_KEY} mode="full" plugins={[identity()]}>
      <Outlet />
    </MiraProvider>
  )
}

In components, useMira().consent({ statistics: true, experiments: true, targeting: false }) passes your banner’s answer, identify(user.id, { plan: user.plan }) follows sign-in and reset() follows sign-out. Before an answer, full mode stores and sends nothing. The server function sends the order with the same user id, so the purchase lands on that person, next to the campaign that first brought them. Create a purchase goal for order_paid, and Acquisition shows buyers and revenue per channel and campaign.

How do I make a webhook count once?

send() delivers at once, and a repeat with the same idempotency key is stored once:

await context.mira.send(
  [{ name: 'order_paid', userId: order.customerId, properties: { revenue: 328, currency: 'EUR' } }],
  { idempotencyKey: `order-${order.id}` },
)

The source’s Install tab has Let your coding agent install it, a prompt for Claude Code, Cursor or Codex with every step and the check. The TanStack Start SDK docs cover Workers, Vercel and TanStack Router without Start, and the setup page shows who does what, in order.

Other setups

The same numbers, whichever way your site is built.

Questions and answers

Why are server events lost on Cloudflare Workers or Vercel?
The response ends before the delivery. Pass the platform's waitUntil to miraMiddleware({ waitUntil }), from cloudflare:workers or @vercel/functions. On Node.js the process keeps running and the flush completes on its own.
Can I use context.mira in a route loader?
No. Route loaders are isomorphic, so call a server function from them instead. Server functions and server routes both see context.mira.
Does it work with TanStack Router without Start?
Yes. A single-page app needs @mirafive/sdk-tanstack, @mirafive/sdk-react and @mirafive/sdk-browser. Wrap RouterProvider in MiraProvider, and pageviews and useMira() work the same.
Why does nothing arrive from the browser?
Localhost sends nothing by default. VITE_MIRAFIVE_KEY must be set before the build, because Vite inlines it then; without it the console shows [mirafive] no website key.

See which channel brings buyers

Free for 25,000 events a month. No card needed.