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
Install the packages
It needs React 18.3 or 19 and Node.js 20 or later, and the
/startentry needs@tanstack/react-start1.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
Register the middleware
Put the secret key of a server source from Data → Sources in
MIRAFIVE_SECRET_KEY, never with aVITE_prefix. Create the middleware once, at module scope, becausecreateStart()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], }))
Wrap the root route
Put the website key of your website source in
VITE_MIRAFIVE_KEYand pass it to the provider, which counts the first page and every TanStack Router navigation. The package does not readimport.meta.envitself, 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> ) }
Send the paid order from a server function
context.mirais 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' } }) })
Check both sides
Deploy, open a page and watch Data → Live; localhost sends nothing unless you pass
trackLocalhostto the provider. For the server, call this function once. It resolves with the reasoninstall_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?
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?
context.mira.Does it work with TanStack Router without Start?
@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?
VITE_MIRAFIVE_KEY must be set before the build, because Vite inlines it then; without it the console shows [mirafive] no website key.