[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-navigation":3,"docs-page-analytics":166},[4,38,57,65,114,138,157],{"id":5,"group":6,"pages":7},"get-started","Get started",[8,13,18,23,28,33],{"path":9,"title":10,"description":11,"navTitle":10,"group":6,"order":12},"\u002Fdocs\u002Fgetting-started","Getting started","Create your app from the kit, run it locally with no credentials, make yourself superadmin and find where your own code goes.",1,{"path":14,"title":15,"description":16,"navTitle":15,"group":6,"order":17},"\u002Fdocs\u002Flayout","Project structure","Where code lives by where it runs, and which server\u002F directories turn a new file into a route, a job or a provider.",2,{"path":19,"title":20,"description":21,"navTitle":20,"group":6,"order":22},"\u002Fdocs\u002Fconfiguration","Configuration","starter.config.ts switches each part of the kit and its providers, environment variables hold the secrets: every key and what it changes.",3,{"path":24,"title":25,"description":26,"navTitle":25,"group":6,"order":27},"\u002Fdocs\u002Ftemplate","Updating from the kit","Keep the kit as a git remote and take each update as one commit you review, with pnpm template:update, then resolve what conflicts.",4,{"path":29,"title":30,"description":31,"navTitle":30,"group":6,"order":32},"\u002Fdocs\u002Fdeployment","Deployment","Ship the app as one Cloudflare Worker: create the resources, set the secrets, deploy main from GitHub Actions or your machine.",5,{"path":34,"title":35,"description":36,"navTitle":35,"group":6,"order":37},"\u002Fdocs\u002Fmcp","MCP server","Connect Claude Code, Cursor or VS Code to https:\u002F\u002Fnuxter.app\u002Fmcp, so your agent searches and reads these docs and their design notes.",6,{"id":39,"group":40,"pages":41},"accounts","Accounts",[42,47,52],{"path":43,"title":44,"description":45,"navTitle":44,"group":40,"order":46},"\u002Fdocs\u002Fauth","Auth","Pick the sign-in methods, add an OAuth provider, protect a route, and handle sessions, second factors and account deletion.",7,{"path":48,"title":49,"description":50,"navTitle":49,"group":40,"order":51},"\u002Fdocs\u002Ftenancy","Organizations","Scope routes and pages to an organization, and work with its members, roles, seats, invitations and a second level such as branches.",8,{"path":53,"title":54,"description":55,"navTitle":54,"group":40,"order":56},"\u002Fdocs\u002Fpermissions","Roles and permissions","Routes check permissions that an account’s role or its organization’s plan grants: checking one, adding a role, and the staff tools.",9,{"id":58,"group":59,"pages":60},"payments","Payments",[61],{"path":62,"title":59,"description":63,"navTitle":59,"group":59,"order":64},"\u002Fdocs\u002Fpayments","Sell plans through Stripe, Polar, Lemon Squeezy or Creem: define plans, connect a provider, gate features, trials, coupons and refunds.",10,{"id":66,"group":67,"pages":68},"building-blocks","Building blocks",[69,74,79,84,89,94,99,104,109],{"path":70,"title":71,"description":72,"navTitle":71,"group":67,"order":73},"\u002Fdocs\u002Fdatabase","Database","Postgres through Drizzle, on Hyperdrive in production and PGlite locally: add a table, query, transactions, migrations and seed data.",11,{"path":75,"title":76,"description":77,"navTitle":76,"group":67,"order":78},"\u002Fdocs\u002Fbackground-jobs","Background jobs","Run work after the response on Cloudflare Queues with retries: add and enqueue a job, run jobs locally, and schedule cron tasks.",12,{"path":80,"title":81,"description":82,"navTitle":81,"group":67,"order":83},"\u002Fdocs\u002Fevents","Events","React to what the kit does by adding a file to server\u002Flisteners\u002F, fire events of your own, and look up every event’s payload.",13,{"path":85,"title":86,"description":87,"navTitle":86,"group":67,"order":88},"\u002Fdocs\u002Femail","Email","Set up sending, send your own mail with deliverEmail, change the templates and wording, handle unsubscribes and add a provider.",14,{"path":90,"title":91,"description":92,"navTitle":91,"group":67,"order":93},"\u002Fdocs\u002Fcontact","Contact","A \u002Fcontact page that mails you a message or shows your address after a Turnstile check: choose the channels, set the address, know the limits.",15,{"path":95,"title":96,"description":97,"navTitle":96,"group":67,"order":98},"\u002Fdocs\u002Fnotifications","Notifications","The dashboard bell: add a notification type, send one with notify(), and how categories, opt-outs and retention apply.",16,{"path":100,"title":101,"description":102,"navTitle":101,"group":67,"order":103},"\u002Fdocs\u002Fstorage","Storage","Uploads in R2 with one policy per purpose: add a purpose, accept an upload, replace or delete a file, and serve it to its owner.",17,{"path":105,"title":106,"description":107,"navTitle":106,"group":67,"order":108},"\u002Fdocs\u002Ffeature-flags","Feature flags","Declare typed flags in starter.config.ts, read them with flag() or useFlag(), roll them out by rule or percentage, and add a provider.",18,{"path":110,"title":111,"description":112,"navTitle":111,"group":67,"order":113},"\u002Fdocs\u002Fai","AI","Call models from server code by role: text, images, speech, transcription, embeddings and video, with provider fallbacks.",19,{"id":115,"group":116,"pages":117},"web","Web",[118,123,128,133],{"path":119,"title":120,"description":121,"navTitle":120,"group":116,"order":122},"\u002Fdocs\u002Fsecurity-headers","Security headers","The Content-Security-Policy and hardening headers on every response: allow another origin, keep tokens out of Referer, accept cross-site posts.",20,{"path":124,"title":125,"description":126,"navTitle":125,"group":116,"order":127},"\u002Fdocs\u002Frate-limiting","Rate limiting","Cap how often a caller hits a route with requireRateLimit, count only failures, space out sends, and protect the zone.",21,{"path":129,"title":130,"description":131,"navTitle":130,"group":116,"order":132},"\u002Fdocs\u002Fanalytics","Analytics","Product analytics and ad conversions sent from the server with no browser script: add an event, connect a provider, and handle consent.",22,{"path":134,"title":135,"description":136,"navTitle":135,"group":116,"order":137},"\u002Fdocs\u002Fobservability","Observability","Find a failed request by its id, send faults to Sentry or Better Stack, report an error you caught, and change the error page.",23,{"id":139,"group":140,"pages":141},"content-and-look","Content and look",[142,147,152],{"path":143,"title":144,"description":145,"navTitle":144,"group":140,"order":146},"\u002Fdocs\u002Fblog","Blog","Markdown posts with an RSS feed per language, and workflows that draft posts with an AI model and open each one as a pull request.",24,{"path":148,"title":149,"description":150,"navTitle":149,"group":140,"order":151},"\u002Fdocs\u002Flanguages","Languages","Add or remove a language, write copy in the locale files, and write code, refusals and links that follow the reader’s language.",25,{"path":153,"title":154,"description":155,"navTitle":154,"group":140,"order":156},"\u002Fdocs\u002Ftheme","Theme","Change the brand colour and font, and build dashboard sections, list pages, loading placeholders and charts that match the kit.",26,{"id":158,"group":159,"pages":160},"quality","Quality",[161],{"path":162,"title":163,"description":164,"navTitle":163,"group":159,"order":165},"\u002Fdocs\u002Ftesting","Testing","Unit tests in Node, component tests in a real Nuxt app and Playwright end to end: test a route or a component, and what CI runs.",27,{"page":167,"surround":2319,"linksToKit":454},{"path":129,"title":130,"description":131,"navTitle":130,"group":116,"seo":168,"body":170},{"title":169,"description":131},"Server-side analytics in Nuxt, no browser script",{"type":171,"value":172,"toc":2302},"minimark",[173,177,181,186,241,245,350,356,360,367,427,430,583,597,600,605,651,676,680,686,710,725,729,736,820,846,853,857,871,989,992,1060,1067,1071,1084,1087,1091,1097,1122,1132,1136,1177,1184,1188,1239,1250,1360,1363,1479,1508,1512,1537,1973,2004,2044,2047,2200,2230,2234,2298],[174,175,130],"h1",{"id":176},"analytics",[178,179,180],"p",{},"The kit records product analytics and ad conversions from the server. Events\ngo to Workers Analytics Engine, PostHog or Umami, and conversions to Meta,\nTikTok, X or Reddit. No provider script runs in the browser and no key reaches\nthe page. You work here when you add an event, connect a provider or change how\nconsent works.",[182,183,185],"h2",{"id":184},"how-it-works","How it works",[187,188,189,205,223,226],"ol",{},[190,191,192,193,204],"li",{},"Every event and its props are declared once, as a Zod schema, in\n",[194,195,200],"a",{"href":196,"rel":197,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fshared\u002Fsignals\u002Fevents.ts",[198],"nofollow","Licensees only",[201,202,203],"code",{},"shared\u002Fsignals\u002Fevents.ts",".",[190,206,207,208,211,212,215,216,219,220,204],{},"Server code calls ",[201,209,210],{},"track(event, name, props)",". The browser calls\n",[201,213,214],{},"useAnalytics().track(name, props)",", which posts a beacon to\n",[201,217,218],{},"analytics.ingestPath",". That route accepts only the events in\n",[201,221,222],{},"CLIENT_EVENTS",[190,224,225],{},"The server builds the context from the request: a cookieless visitor id, a\nsession id, the signed-in user, the page, the referrer, UTM parameters,\nclick ids and the visitor's consent.",[190,227,228,229,236,237,240],{},"After the response,\n",[194,230,233],{"href":231,"rel":232,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fsignals\u002Findex.ts",[198],[201,234,235],{},"server\u002Fsignals\u002Findex.ts"," sends the event to\nevery configured sink the visitor consented to. ",[201,238,239],{},"track"," returns at once and\nnever throws.",[182,242,244],{"id":243},"what-is-tracked-already","What is tracked already",[246,247,248,261],"table",{},[249,250,251],"thead",{},[252,253,254,258],"tr",{},[255,256,257],"th",{},"Event",[255,259,260],{},"Sent from",[262,263,264,278,288,298,308,320,330],"tbody",{},[252,265,266,272],{},[267,268,269],"td",{},[201,270,271],{},"page_view",[267,273,274,275],{},"Every GET answered 2xx with HTML, and client navigations under ",[201,276,277],{},"'ssr+beacon'",[252,279,280,285],{},[267,281,282],{},[201,283,284],{},"consent_updated",[267,286,287],{},"The consent banner",[252,289,290,295],{},[267,291,292],{},[201,293,294],{},"signup_started",[267,296,297],{},"Declared for the browser. Nothing sends it yet",[252,299,300,305],{},[267,301,302],{},[201,303,304],{},"signup_completed",[267,306,307],{},"Password, magic-link and OAuth registration",[252,309,310,315],{},[267,311,312],{},[201,313,314],{},"checkout_started",[267,316,317],{},[201,318,319],{},"POST \u002Fapi\u002Fpayments\u002Fcheckout",[252,321,322,327],{},[267,323,324],{},[201,325,326],{},"checkout_coupon_refused",[267,328,329],{},"The same route, each time it refuses a coupon with a 422",[252,331,332,347],{},[267,333,334,337,338,337,341,337,344],{},[201,335,336],{},"purchase",", ",[201,339,340],{},"subscription_started",[201,342,343],{},"subscription_cancelled",[201,345,346],{},"refund",[267,348,349],{},"The payment webhook",[178,351,352,353,355],{},"The webhook events carry the buyer's user id, plus the consent and attribution\nrecorded when they started the checkout, because the webhook request itself\ncomes from the payment provider (",[194,354,59],{"href":62},").",[182,357,359],{"id":358},"add-an-event","Add an event",[178,361,362,363,366],{},"Declare it in ",[201,364,365],{},"SIGNAL_EVENTS",":",[368,369,374],"pre",{"className":370,"code":371,"language":372,"meta":373,"style":373},"language-ts shiki shiki-themes github-light github-light github-dark","report_exported: z\n  .object({ format: z.enum(['csv', 'pdf']) })\n  .strict(),\n","ts","",[201,375,376,388,417],{"__ignoreMap":373},[377,378,380,384],"span",{"class":379,"line":12},"line",[377,381,383],{"class":382},"snPdu","report_exported",[377,385,387],{"class":386},"sxrX7",": z\n",[377,389,390,393,396,399,402,405,409,411,414],{"class":379,"line":17},[377,391,392],{"class":386},"  .",[377,394,395],{"class":382},"object",[377,397,398],{"class":386},"({ format: z.",[377,400,401],{"class":382},"enum",[377,403,404],{"class":386},"([",[377,406,408],{"class":407},"sIIMD","'csv'",[377,410,337],{"class":386},[377,412,413],{"class":407},"'pdf'",[377,415,416],{"class":386},"]) })\n",[377,418,419,421,424],{"class":379,"line":22},[377,420,392],{"class":386},[377,422,423],{"class":382},"strict",[377,425,426],{"class":386},"(),\n",[178,428,429],{},"Then track it from any handler:",[368,431,433],{"className":370,"code":432,"language":372,"meta":373,"style":373},"import { track } from '#server\u002Fsignals'\n\nexport default defineEventHandler(async (event) => {\n  const { format } = await readValidatedBody(\n    event,\n    z.object({ format: z.enum(['csv', 'pdf']) }).parse,\n  )\n\n  track(event, 'report_exported', { format })\n\n  return { format }\n})\n",[201,434,435,450,456,489,516,521,543,548,552,566,570,578],{"__ignoreMap":373},[377,436,437,441,444,447],{"class":379,"line":12},[377,438,440],{"class":439},"s8jYJ","import",[377,442,443],{"class":386}," { track } ",[377,445,446],{"class":439},"from",[377,448,449],{"class":407}," '#server\u002Fsignals'\n",[377,451,452],{"class":379,"line":17},[377,453,455],{"emptyLinePlaceholder":454},true,"\n",[377,457,458,461,464,467,470,473,476,480,483,486],{"class":379,"line":22},[377,459,460],{"class":439},"export",[377,462,463],{"class":439}," default",[377,465,466],{"class":382}," defineEventHandler",[377,468,469],{"class":386},"(",[377,471,472],{"class":439},"async",[377,474,475],{"class":386}," (",[377,477,479],{"class":478},"sP4rz","event",[377,481,482],{"class":386},") ",[377,484,485],{"class":439},"=>",[377,487,488],{"class":386}," {\n",[377,490,491,494,497,501,504,507,510,513],{"class":379,"line":27},[377,492,493],{"class":439},"  const",[377,495,496],{"class":386}," { ",[377,498,500],{"class":499},"sBjJW","format",[377,502,503],{"class":386}," } ",[377,505,506],{"class":439},"=",[377,508,509],{"class":439}," await",[377,511,512],{"class":382}," readValidatedBody",[377,514,515],{"class":386},"(\n",[377,517,518],{"class":379,"line":32},[377,519,520],{"class":386},"    event,\n",[377,522,523,526,528,530,532,534,536,538,540],{"class":379,"line":37},[377,524,525],{"class":386},"    z.",[377,527,395],{"class":382},[377,529,398],{"class":386},[377,531,401],{"class":382},[377,533,404],{"class":386},[377,535,408],{"class":407},[377,537,337],{"class":386},[377,539,413],{"class":407},[377,541,542],{"class":386},"]) }).parse,\n",[377,544,545],{"class":379,"line":46},[377,546,547],{"class":386},"  )\n",[377,549,550],{"class":379,"line":51},[377,551,455],{"emptyLinePlaceholder":454},[377,553,554,557,560,563],{"class":379,"line":56},[377,555,556],{"class":382},"  track",[377,558,559],{"class":386},"(event, ",[377,561,562],{"class":407},"'report_exported'",[377,564,565],{"class":386},", { format })\n",[377,567,568],{"class":379,"line":64},[377,569,455],{"emptyLinePlaceholder":454},[377,571,572,575],{"class":379,"line":73},[377,573,574],{"class":439},"  return",[377,576,577],{"class":386}," { format }\n",[377,579,580],{"class":379,"line":78},[377,581,582],{"class":386},"})\n",[178,584,585,586,589,590,593,594,596],{},"A wrong name or prop fails type checking at the call. Name events in\nsnake_case, 40 characters at most, and keep each schema ",[201,587,588],{},".strict()"," so an\nunknown prop is rejected. Keep props small: Umami Cloud bills per stored\nproperty, and Analytics Engine replaces props over 2,048 bytes with\n",[201,591,592],{},"{\"$truncated\":true}",". ",[201,595,203],{}," belongs to the template, so\nexpect a small conflict there on updates.",[178,598,599],{},"Track after your handler has written the session. The context reads the\nsession a few awaits after the call, so an event tracked before sign-in\ncompletes carries no user id, and Meta and X drop such a conversion.",[601,602,604],"h3",{"id":603},"from-the-browser","From the browser",[368,606,608],{"className":370,"code":607,"language":372,"meta":373,"style":373},"const { track } = useAnalytics()\n\ntrack('signup_started', { method: 'password' })\n",[201,609,610,629,633],{"__ignoreMap":373},[377,611,612,615,617,619,621,623,626],{"class":379,"line":12},[377,613,614],{"class":439},"const",[377,616,496],{"class":386},[377,618,239],{"class":499},[377,620,503],{"class":386},[377,622,506],{"class":439},[377,624,625],{"class":382}," useAnalytics",[377,627,628],{"class":386},"()\n",[377,630,631],{"class":379,"line":17},[377,632,455],{"emptyLinePlaceholder":454},[377,634,635,637,639,642,645,648],{"class":379,"line":22},[377,636,239],{"class":382},[377,638,469],{"class":386},[377,640,641],{"class":407},"'signup_started'",[377,643,644],{"class":386},", { method: ",[377,646,647],{"class":407},"'password'",[377,649,650],{"class":386}," })\n",[178,652,653,654,656,657,659,660,663,664,667,668,675],{},"Only names in ",[201,655,222],{}," type-check here. To accept a new one, add it to\n",[201,658,222],{}," and give it a ",[201,661,662],{},"case"," in ",[201,665,666],{},"parseClientEvent"," in\n",[194,669,672],{"href":670,"rel":671,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fsignals\u002Fingest.post.ts",[198],[201,673,674],{},"server\u002Fsignals\u002Fingest.post.ts",", which\nchecks each beacon event against its schema. Anyone can post to that route, so\nanything involving money stays a server event.",[182,677,679],{"id":678},"page-views","Page views",[178,681,682,685],{},[201,683,684],{},"analytics.pageviews"," decides how page views are counted:",[687,688,689,699,704],"ul",{},[190,690,691,694,695,698],{},[201,692,693],{},"'ssr'"," counts document requests: GETs answered 2xx with ",[201,696,697],{},"text\u002Fhtml",".\nNavigations after hydration happen in the browser and are not counted.",[190,700,701,703],{},[201,702,277],{},", the default, also sends a view from the client router on each\nnavigation, query-only ones included. The first page is not counted twice.",[190,705,706,709],{},[201,707,708],{},"false"," counts none.",[178,711,712,713,716,717,720,721,724],{},"The stored path drops the locale prefix, so ",[201,714,715],{},"\u002Fsr\u002Fpricing"," and ",[201,718,719],{},"\u002Fpricing"," are\none page. Only ",[201,722,723],{},"utm_*"," and click-id parameters leave the Worker. The rest of the\nquery string is dropped, so tokens in reset or invitation links never reach a\nprovider.",[182,726,728],{"id":727},"connect-a-provider","Connect a provider",[178,730,731,732,735],{},"List it in ",[201,733,734],{},"analytics.providers",". Every event goes to every listed provider.",[246,737,738,751],{},[249,739,740],{},[252,741,742,745,748],{},[255,743,744],{},"Provider",[255,746,747],{},"What it is",[255,749,750],{},"Set",[262,752,753,770,797],{},[252,754,755,760,767],{},[267,756,757],{},[201,758,759],{},"cloudflare",[267,761,762,763,766],{},"Workers Analytics Engine, the ",[201,764,765],{},"ANALYTICS"," binding",[267,768,769],{},"Nothing",[252,771,772,777,784],{},[267,773,774],{},[201,775,776],{},"posthog",[267,778,779,780,783],{},"PostHog's ",[201,781,782],{},"\u002Fbatch\u002F"," endpoint",[267,785,786,789,790,793,794],{},[201,787,788],{},"NUXT_ANALYTICS_PROVIDERS_POSTHOG_KEY",", and ",[201,791,792],{},"_HOST"," if not ",[201,795,796],{},"https:\u002F\u002Feu.i.posthog.com",[252,798,799,804,810],{},[267,800,801],{},[201,802,803],{},"umami",[267,805,806,807],{},"Umami Cloud or your own instance, ",[201,808,809],{},"\u002Fapi\u002Fsend",[267,811,812,789,815,793,817],{},[201,813,814],{},"NUXT_ANALYTICS_PROVIDERS_UMAMI_KEY",[201,816,792],{},[201,818,819],{},"https:\u002F\u002Fcloud.umami.is",[687,821,822,840,843],{},[190,823,824,825,827,828,831,832,835,836,839],{},"PostHog: the host is the ingest host, ",[201,826,796],{}," or\n",[201,829,830],{},"https:\u002F\u002Fus.i.posthog.com",". The dashboard host accepts the request and stores\nnothing. The key is the project key, ",[201,833,834],{},"phc_…",". Page views arrive as\n",[201,837,838],{},"$pageview",". Person profiles are created only for signed-in events, so events\nfrom before sign-in are not joined to the user.",[190,841,842],{},"Umami: the host is the instance origin without a trailing slash, and the key\nis the website id.",[190,844,845],{},"Cloudflare: Analytics Engine has no dashboard and keeps data for three\nmonths. Query it with SQL.",[178,847,848,849,852],{},"Outside ",[201,850,851],{},"pnpm dev",", the Worker logs at boot every listed provider whose key is\nunset.",[182,854,856],{"id":855},"query-analytics-engine","Query Analytics Engine",[178,858,859,860,863,864,366],{},"Each event is one row in the ",[201,861,862],{},"nuxt_starter_kit_analytics"," dataset, written by\n",[194,865,868],{"href":866,"rel":867,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fsignals\u002Fanalytics\u002Fcloudflare.ts",[198],[201,869,870],{},"server\u002Fsignals\u002Fanalytics\u002Fcloudflare.ts",[246,872,873,883],{},[249,874,875],{},[252,876,877,880],{},[255,878,879],{},"Column",[255,881,882],{},"Holds",[262,884,885,899,922,943,966,979],{},[252,886,887,896],{},[267,888,889,892,893],{},[201,890,891],{},"blob1"," to ",[201,894,895],{},"blob4",[267,897,898],{},"Event name, path, host, referrer host",[252,900,901,909],{},[267,902,903,892,906],{},[201,904,905],{},"blob5",[201,907,908],{},"blob7",[267,910,911,912,337,915,337,918,921],{},"Country, device (",[201,913,914],{},"desktop",[201,916,917],{},"mobile",[201,919,920],{},"tablet","), locale",[252,923,924,932],{},[267,925,926,892,929],{},[201,927,928],{},"blob8",[201,930,931],{},"blob10",[267,933,934,337,937,337,940],{},[201,935,936],{},"utm_source",[201,938,939],{},"utm_medium",[201,941,942],{},"utm_campaign",[252,944,945,953],{},[267,946,947,892,950],{},[201,948,949],{},"blob11",[201,951,952],{},"blob15",[267,954,955,956,337,959,337,962,965],{},"Origin (",[201,957,958],{},"ssr",[201,960,961],{},"beacon",[201,963,964],{},"server","), session id, visitor id, props as JSON, currency",[252,967,968,976],{},[267,969,970,892,973],{},[201,971,972],{},"double1",[201,974,975],{},"double4",[267,977,978],{},"Always 1, revenue amount, 1 when signed in, 1 when a bot",[252,980,981,986],{},[267,982,983],{},[201,984,985],{},"index1",[267,987,988],{},"Host",[178,990,991],{},"Top pages this week, bots excluded:",[368,993,997],{"className":994,"code":995,"language":996,"meta":373,"style":373},"language-bash shiki shiki-themes github-light github-light github-dark","curl \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Faccounts\u002F$ACCOUNT_ID\u002Fanalytics_engine\u002Fsql\" \\\n  --header \"Authorization: Bearer $API_TOKEN\" \\\n  --data \"SELECT blob2 AS path, SUM(_sample_interval) AS views\n          FROM nuxt_starter_kit_analytics\n          WHERE blob1 = 'page_view' AND double4 = 0\n            AND timestamp > NOW() - INTERVAL '7' DAY\n          GROUP BY path ORDER BY views DESC LIMIT 20\"\n","bash",[201,998,999,1016,1032,1040,1045,1050,1055],{"__ignoreMap":373},[377,1000,1001,1004,1007,1010,1013],{"class":379,"line":12},[377,1002,1003],{"class":382},"curl",[377,1005,1006],{"class":407}," \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Faccounts\u002F",[377,1008,1009],{"class":386},"$ACCOUNT_ID",[377,1011,1012],{"class":407},"\u002Fanalytics_engine\u002Fsql\"",[377,1014,1015],{"class":499}," \\\n",[377,1017,1018,1021,1024,1027,1030],{"class":379,"line":17},[377,1019,1020],{"class":499},"  --header",[377,1022,1023],{"class":407}," \"Authorization: Bearer ",[377,1025,1026],{"class":386},"$API_TOKEN",[377,1028,1029],{"class":407},"\"",[377,1031,1015],{"class":499},[377,1033,1034,1037],{"class":379,"line":22},[377,1035,1036],{"class":499},"  --data",[377,1038,1039],{"class":407}," \"SELECT blob2 AS path, SUM(_sample_interval) AS views\n",[377,1041,1042],{"class":379,"line":27},[377,1043,1044],{"class":407},"          FROM nuxt_starter_kit_analytics\n",[377,1046,1047],{"class":379,"line":32},[377,1048,1049],{"class":407},"          WHERE blob1 = 'page_view' AND double4 = 0\n",[377,1051,1052],{"class":379,"line":37},[377,1053,1054],{"class":407},"            AND timestamp > NOW() - INTERVAL '7' DAY\n",[377,1056,1057],{"class":379,"line":46},[377,1058,1059],{"class":407},"          GROUP BY path ORDER BY views DESC LIMIT 20\"\n",[178,1061,1062,1063,1066],{},"The token needs Account Analytics Read. Sum ",[201,1064,1065],{},"_sample_interval"," rather than\ncounting rows, because Analytics Engine samples under load.",[182,1068,1070],{"id":1069},"visitors-and-sessions","Visitors and sessions",[178,1072,1073,1074,1077,1078,1081,1082,355],{},"No cookie identifies a visitor. The visitor id is an HMAC of the IP, user agent\nand host, keyed with a salt derived for each UTC day from\n",[201,1075,1076],{},"NUXT_ANALYTICS_SALT",", or from ",[201,1079,1080],{},"NUXT_SESSION_PASSWORD"," when that is unset. A\nsession is a fixed 30-minute window, and signed-in events also carry the user\nid. Feature flags bucket anonymous visitors on the same id\n(",[194,1083,106],{"href":105},[178,1085,1086],{},"So a person is a new visitor every UTC day. Same-day funnels, sources and\nconversion rates are exact. Returning visitors, retention and cohorts can't be\nmeasured. A visit that crosses a window boundary counts as two sessions, and\nchanging the salt makes everyone new.",[182,1088,1090],{"id":1089},"consent","Consent",[178,1092,1093,1096],{},[201,1094,1095],{},"analytics.consent"," sets the default for the analytics sinks:",[687,1098,1099,1112],{},[190,1100,1101,1104,1105,1108,1109,204],{},[201,1102,1103],{},"'implied'",", the default, sends events unless the visitor objects (",[201,1106,1107],{},"_ac=0",",\nset from Privacy preferences in the user menu) or their browser sends\n",[201,1110,1111],{},"Sec-GPC: 1",[190,1113,1114,1117,1118,1121],{},[201,1115,1116],{},"'explicit'"," sends nothing until ",[201,1119,1120],{},"_ac=1",", and the consent banner asks. When\nthe visitor accepts, the kit sends one page view for the page they are on.",[178,1123,1124,1125,1128,1129,1131],{},"Conversion sinks always need ",[201,1126,1127],{},"_mc=1",", so turning marketing on shows the banner\nuntil the visitor chooses. ",[201,1130,1111],{}," refuses both. The choices last 180\ndays.",[182,1133,1135],{"id":1134},"the-beacon-route","The beacon route",[178,1137,1138,1139,337,1141,1144,1145,337,1147,337,1150,337,1153,337,1155,827,1158,1161,1162,1165,1166,1169,1170,204],{},"The browser posts to ",[201,1140,218],{},[201,1142,1143],{},"\u002Fapi\u002Fe"," as shipped. Pick your own\nper project, since blocklists add rules for paths many sites share. The build\nrefuses a path that isn't lowercase segments, and one containing a word that\nblocklists match, such as ",[201,1146,239],{},[201,1148,1149],{},"analytic",[201,1151,1152],{},"collect",[201,1154,961],{},[201,1156,1157],{},"pixel",[201,1159,1160],{},"metric",". The words match anywhere, so ",[201,1163,1164],{},"\u002Fapi\u002Fdrum"," fails on ",[201,1167,1168],{},"rum",". The full\nlist is in ",[194,1171,1174],{"href":1172,"rel":1173,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fmodules\u002Fsignals-registry.ts",[198],[201,1175,1176],{},"modules\u002Fsignals-registry.ts",[178,1178,1179,1180,1183],{},"The route exists whenever an analytics or marketing sink is on. It limits each\nIP through the ",[201,1181,1182],{},"ANALYTICS_RATE_LIMIT"," binding (60 a minute per Cloudflare\nlocation) and answers 429 past that, and 413 to a body over 4,096 bytes.\nOtherwise it answers 204, and drops cross-site posts, malformed bodies and\nbatches of more than 10 events without saying so.",[182,1185,1187],{"id":1186},"send-conversions-to-ad-platforms","Send conversions to ad platforms",[368,1189,1191],{"className":370,"code":1190,"language":372,"meta":373,"style":373},"marketing: {\n  enabled: true,\n  providers: ['meta', 'tiktok'],\n},\n",[201,1192,1193,1201,1215,1234],{"__ignoreMap":373},[377,1194,1195,1198],{"class":379,"line":12},[377,1196,1197],{"class":382},"marketing",[377,1199,1200],{"class":386},": {\n",[377,1202,1203,1206,1209,1212],{"class":379,"line":17},[377,1204,1205],{"class":382},"  enabled",[377,1207,1208],{"class":386},": ",[377,1210,1211],{"class":499},"true",[377,1213,1214],{"class":386},",\n",[377,1216,1217,1220,1223,1226,1228,1231],{"class":379,"line":22},[377,1218,1219],{"class":382},"  providers",[377,1221,1222],{"class":386},": [",[377,1224,1225],{"class":407},"'meta'",[377,1227,337],{"class":386},[377,1229,1230],{"class":407},"'tiktok'",[377,1232,1233],{"class":386},"],\n",[377,1235,1236],{"class":379,"line":27},[377,1237,1238],{"class":386},"},\n",[178,1240,1241,1242,1249],{},"Then set the platform's variables. ",[194,1243,1246],{"href":1244,"rel":1245,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002F.env.example",[198],[201,1247,1248],{},".env.example"," says where\neach value comes from.",[246,1251,1252,1265],{},[249,1253,1254],{},[252,1255,1256,1259,1262],{},[255,1257,1258],{},"Platform",[255,1260,1261],{},"Variables",[255,1263,1264],{},"Test mode",[262,1266,1267,1287,1306,1341],{},[252,1268,1269,1274,1282],{},[267,1270,1271],{},[201,1272,1273],{},"meta",[267,1275,1276,337,1279],{},[201,1277,1278],{},"NUXT_MARKETING_META_DATASET_ID",[201,1280,1281],{},"_ACCESS_TOKEN",[267,1283,1284],{},[201,1285,1286],{},"NUXT_MARKETING_META_TEST_EVENT_CODE",[252,1288,1289,1294,1301],{},[267,1290,1291],{},[201,1292,1293],{},"tiktok",[267,1295,1296,337,1299],{},[201,1297,1298],{},"NUXT_MARKETING_TIKTOK_PIXEL_CODE",[201,1300,1281],{},[267,1302,1303],{},[201,1304,1305],{},"NUXT_MARKETING_TIKTOK_TEST_EVENT_CODE",[252,1307,1308,1313,1338],{},[267,1309,1310],{},[201,1311,1312],{},"x",[267,1314,1315,337,1318,337,1321,337,1324,337,1326,337,1329,337,1332,337,1335],{},[201,1316,1317],{},"NUXT_MARKETING_X_PIXEL_ID",[201,1319,1320],{},"_CONSUMER_KEY",[201,1322,1323],{},"_CONSUMER_SECRET",[201,1325,1281],{},[201,1327,1328],{},"_ACCESS_SECRET",[201,1330,1331],{},"_EVENT_PURCHASE",[201,1333,1334],{},"_EVENT_SUBSCRIBE",[201,1336,1337],{},"_EVENT_SIGNUP",[267,1339,1340],{},"None",[252,1342,1343,1348,1355],{},[267,1344,1345],{},[201,1346,1347],{},"reddit",[267,1349,1350,337,1353],{},[201,1351,1352],{},"NUXT_MARKETING_REDDIT_PIXEL_ID",[201,1354,1281],{},[267,1356,1357],{},[201,1358,1359],{},"NUXT_MARKETING_REDDIT_TEST_ID",[178,1361,1362],{},"Each platform receives these events and drops the rest:",[246,1364,1365,1383],{},[249,1366,1367],{},[252,1368,1369,1371,1374,1377,1380],{},[255,1370,257],{},[255,1372,1373],{},"Meta",[255,1375,1376],{},"TikTok",[255,1378,1379],{},"X",[255,1381,1382],{},"Reddit",[262,1384,1385,1409,1428,1455],{},[252,1386,1387,1391,1396,1400,1404],{},[267,1388,1389],{},[201,1390,304],{},[267,1392,1393],{},[201,1394,1395],{},"CompleteRegistration",[267,1397,1398],{},[201,1399,1395],{},[267,1401,1402],{},[201,1403,1337],{},[267,1405,1406],{},[201,1407,1408],{},"SIGN_UP",[252,1410,1411,1415,1420,1424,1426],{},[267,1412,1413],{},[201,1414,314],{},[267,1416,1417],{},[201,1418,1419],{},"InitiateCheckout",[267,1421,1422],{},[201,1423,1419],{},[267,1425],{},[267,1427],{},[252,1429,1430,1434,1439,1443,1450],{},[267,1431,1432],{},[201,1433,340],{},[267,1435,1436],{},[201,1437,1438],{},"Subscribe",[267,1440,1441],{},[201,1442,1438],{},[267,1444,1445,1447,1448],{},[201,1446,1334],{},", else ",[201,1449,1331],{},[267,1451,1452],{},[201,1453,1454],{},"PURCHASE",[252,1456,1457,1461,1466,1471,1475],{},[267,1458,1459],{},[201,1460,336],{},[267,1462,1463],{},[201,1464,1465],{},"Purchase",[267,1467,1468],{},[201,1469,1470],{},"CompletePayment",[267,1472,1473],{},[201,1474,1331],{},[267,1476,1477],{},[201,1478,1454],{},[178,1480,1481,1482,337,1485,337,1488,337,1491,337,1494,337,1497,1214,1500,1503,1504,1507],{},"Click ids (",[201,1483,1484],{},"gclid",[201,1486,1487],{},"gad_source",[201,1489,1490],{},"fbclid",[201,1492,1493],{},"msclkid",[201,1495,1496],{},"ttclid",[201,1498,1499],{},"twclid",[201,1501,1502],{},"rdt_cid",") are kept in an HttpOnly ",[201,1505,1506],{},"_cid"," cookie for 90 days once the visitor\ngrants marketing consent. Checkout copies them, with the consent, IP, user\nagent and page, onto the buyer's account, and the webhook reads that copy back.\nX accepts nothing until your developer app has Ads API approval.",[182,1509,1511],{"id":1510},"add-a-sink","Add a sink",[178,1513,1514,1515,1518,1519,1522,1523,1526,1527,1529,1530,1533,1534,366],{},"Create ",[201,1516,1517],{},"server\u002Fsignals\u002Fanalytics\u002Fcustom\u002F\u003Cid>.ts",", or ",[201,1520,1521],{},"marketing\u002Fcustom\u002F\u003Cid>.ts","\nfor an ad platform, default-export a ",[201,1524,1525],{},"SignalAdapter",", and list the id in\n",[201,1528,734],{}," or ",[201,1531,1532],{},"marketing.providers",". A sink that posts to your own\nendpoint, ",[201,1535,1536],{},"server\u002Fsignals\u002Fanalytics\u002Fcustom\u002Fwarehouse.ts",[368,1538,1540],{"className":370,"code":1539,"language":372,"meta":373,"style":373},"import type { SignalAdapter } from '#server\u002Fsignals\u002Ftypes'\n\nconst warehouse: SignalAdapter = {\n  id: 'warehouse',\n  consentCategory: 'none',\n  batchSize: 50,\n  acceptsPageviews: true,\n  identityControl: 'explicit',\n  mixedIdentityBatch: true,\n  transport: 'fetch',\n\n  async send(batch, { host, key }) {\n    try {\n      const response = await $fetch.raw(`${host}\u002Fevents`, {\n        method: 'POST',\n        headers: { authorization: `Bearer ${key}` },\n        body: batch.map(({ name, props, at, context }) => ({\n          name,\n          props,\n          at,\n          visitor: context.visitorId,\n        })),\n        ignoreResponseError: true,\n      })\n\n      return response.ok\n        ? { ok: true, delivered: batch.length }\n        : {\n            ok: false,\n            reason: `HTTP ${response.status}`,\n            retryable: response.status >= 500,\n          }\n    } catch (error) {\n      return { ok: false, reason: String(error), retryable: true }\n    }\n  },\n}\n\nexport default warehouse\n",[201,1541,1542,1557,1561,1578,1588,1598,1608,1617,1626,1635,1645,1649,1676,1683,1714,1724,1740,1777,1782,1787,1792,1797,1802,1811,1816,1820,1828,1847,1855,1865,1886,1900,1906,1918,1940,1946,1952,1958,1963],{"__ignoreMap":373},[377,1543,1544,1546,1549,1552,1554],{"class":379,"line":12},[377,1545,440],{"class":439},[377,1547,1548],{"class":439}," type",[377,1550,1551],{"class":386}," { SignalAdapter } ",[377,1553,446],{"class":439},[377,1555,1556],{"class":407}," '#server\u002Fsignals\u002Ftypes'\n",[377,1558,1559],{"class":379,"line":17},[377,1560,455],{"emptyLinePlaceholder":454},[377,1562,1563,1565,1568,1570,1573,1576],{"class":379,"line":22},[377,1564,614],{"class":439},[377,1566,1567],{"class":499}," warehouse",[377,1569,366],{"class":439},[377,1571,1572],{"class":382}," SignalAdapter",[377,1574,1575],{"class":439}," =",[377,1577,488],{"class":386},[377,1579,1580,1583,1586],{"class":379,"line":27},[377,1581,1582],{"class":386},"  id: ",[377,1584,1585],{"class":407},"'warehouse'",[377,1587,1214],{"class":386},[377,1589,1590,1593,1596],{"class":379,"line":32},[377,1591,1592],{"class":386},"  consentCategory: ",[377,1594,1595],{"class":407},"'none'",[377,1597,1214],{"class":386},[377,1599,1600,1603,1606],{"class":379,"line":37},[377,1601,1602],{"class":386},"  batchSize: ",[377,1604,1605],{"class":499},"50",[377,1607,1214],{"class":386},[377,1609,1610,1613,1615],{"class":379,"line":46},[377,1611,1612],{"class":386},"  acceptsPageviews: ",[377,1614,1211],{"class":499},[377,1616,1214],{"class":386},[377,1618,1619,1622,1624],{"class":379,"line":51},[377,1620,1621],{"class":386},"  identityControl: ",[377,1623,1116],{"class":407},[377,1625,1214],{"class":386},[377,1627,1628,1631,1633],{"class":379,"line":56},[377,1629,1630],{"class":386},"  mixedIdentityBatch: ",[377,1632,1211],{"class":499},[377,1634,1214],{"class":386},[377,1636,1637,1640,1643],{"class":379,"line":64},[377,1638,1639],{"class":386},"  transport: ",[377,1641,1642],{"class":407},"'fetch'",[377,1644,1214],{"class":386},[377,1646,1647],{"class":379,"line":73},[377,1648,455],{"emptyLinePlaceholder":454},[377,1650,1651,1654,1657,1659,1662,1665,1668,1670,1673],{"class":379,"line":78},[377,1652,1653],{"class":439},"  async",[377,1655,1656],{"class":382}," send",[377,1658,469],{"class":386},[377,1660,1661],{"class":478},"batch",[377,1663,1664],{"class":386},", { ",[377,1666,1667],{"class":478},"host",[377,1669,337],{"class":386},[377,1671,1672],{"class":478},"key",[377,1674,1675],{"class":386}," }) {\n",[377,1677,1678,1681],{"class":379,"line":83},[377,1679,1680],{"class":439},"    try",[377,1682,488],{"class":386},[377,1684,1685,1688,1691,1693,1695,1698,1701,1703,1706,1708,1711],{"class":379,"line":88},[377,1686,1687],{"class":439},"      const",[377,1689,1690],{"class":499}," response",[377,1692,1575],{"class":439},[377,1694,509],{"class":439},[377,1696,1697],{"class":386}," $fetch.",[377,1699,1700],{"class":382},"raw",[377,1702,469],{"class":386},[377,1704,1705],{"class":407},"`${",[377,1707,1667],{"class":386},[377,1709,1710],{"class":407},"}\u002Fevents`",[377,1712,1713],{"class":386},", {\n",[377,1715,1716,1719,1722],{"class":379,"line":93},[377,1717,1718],{"class":386},"        method: ",[377,1720,1721],{"class":407},"'POST'",[377,1723,1214],{"class":386},[377,1725,1726,1729,1732,1734,1737],{"class":379,"line":98},[377,1727,1728],{"class":386},"        headers: { authorization: ",[377,1730,1731],{"class":407},"`Bearer ${",[377,1733,1672],{"class":386},[377,1735,1736],{"class":407},"}`",[377,1738,1739],{"class":386}," },\n",[377,1741,1742,1745,1748,1751,1754,1756,1759,1761,1764,1766,1769,1772,1774],{"class":379,"line":103},[377,1743,1744],{"class":386},"        body: batch.",[377,1746,1747],{"class":382},"map",[377,1749,1750],{"class":386},"(({ ",[377,1752,1753],{"class":478},"name",[377,1755,337],{"class":386},[377,1757,1758],{"class":478},"props",[377,1760,337],{"class":386},[377,1762,1763],{"class":478},"at",[377,1765,337],{"class":386},[377,1767,1768],{"class":478},"context",[377,1770,1771],{"class":386}," }) ",[377,1773,485],{"class":439},[377,1775,1776],{"class":386}," ({\n",[377,1778,1779],{"class":379,"line":108},[377,1780,1781],{"class":386},"          name,\n",[377,1783,1784],{"class":379,"line":113},[377,1785,1786],{"class":386},"          props,\n",[377,1788,1789],{"class":379,"line":122},[377,1790,1791],{"class":386},"          at,\n",[377,1793,1794],{"class":379,"line":127},[377,1795,1796],{"class":386},"          visitor: context.visitorId,\n",[377,1798,1799],{"class":379,"line":132},[377,1800,1801],{"class":386},"        })),\n",[377,1803,1804,1807,1809],{"class":379,"line":137},[377,1805,1806],{"class":386},"        ignoreResponseError: ",[377,1808,1211],{"class":499},[377,1810,1214],{"class":386},[377,1812,1813],{"class":379,"line":146},[377,1814,1815],{"class":386},"      })\n",[377,1817,1818],{"class":379,"line":151},[377,1819,455],{"emptyLinePlaceholder":454},[377,1821,1822,1825],{"class":379,"line":156},[377,1823,1824],{"class":439},"      return",[377,1826,1827],{"class":386}," response.ok\n",[377,1829,1830,1833,1836,1838,1841,1844],{"class":379,"line":165},[377,1831,1832],{"class":439},"        ?",[377,1834,1835],{"class":386}," { ok: ",[377,1837,1211],{"class":499},[377,1839,1840],{"class":386},", delivered: batch.",[377,1842,1843],{"class":499},"length",[377,1845,1846],{"class":386}," }\n",[377,1848,1850,1853],{"class":379,"line":1849},28,[377,1851,1852],{"class":439},"        :",[377,1854,488],{"class":386},[377,1856,1858,1861,1863],{"class":379,"line":1857},29,[377,1859,1860],{"class":386},"            ok: ",[377,1862,708],{"class":499},[377,1864,1214],{"class":386},[377,1866,1868,1871,1874,1877,1879,1882,1884],{"class":379,"line":1867},30,[377,1869,1870],{"class":386},"            reason: ",[377,1872,1873],{"class":407},"`HTTP ${",[377,1875,1876],{"class":386},"response",[377,1878,204],{"class":407},[377,1880,1881],{"class":386},"status",[377,1883,1736],{"class":407},[377,1885,1214],{"class":386},[377,1887,1889,1892,1895,1898],{"class":379,"line":1888},31,[377,1890,1891],{"class":386},"            retryable: response.status ",[377,1893,1894],{"class":439},">=",[377,1896,1897],{"class":499}," 500",[377,1899,1214],{"class":386},[377,1901,1903],{"class":379,"line":1902},32,[377,1904,1905],{"class":386},"          }\n",[377,1907,1909,1912,1915],{"class":379,"line":1908},33,[377,1910,1911],{"class":386},"    } ",[377,1913,1914],{"class":439},"catch",[377,1916,1917],{"class":386}," (error) {\n",[377,1919,1921,1923,1925,1927,1930,1933,1936,1938],{"class":379,"line":1920},34,[377,1922,1824],{"class":439},[377,1924,1835],{"class":386},[377,1926,708],{"class":499},[377,1928,1929],{"class":386},", reason: ",[377,1931,1932],{"class":382},"String",[377,1934,1935],{"class":386},"(error), retryable: ",[377,1937,1211],{"class":499},[377,1939,1846],{"class":386},[377,1941,1943],{"class":379,"line":1942},35,[377,1944,1945],{"class":386},"    }\n",[377,1947,1949],{"class":379,"line":1948},36,[377,1950,1951],{"class":386},"  },\n",[377,1953,1955],{"class":379,"line":1954},37,[377,1956,1957],{"class":386},"}\n",[377,1959,1961],{"class":379,"line":1960},38,[377,1962,455],{"emptyLinePlaceholder":454},[377,1964,1966,1968,1970],{"class":379,"line":1965},39,[377,1967,460],{"class":439},[377,1969,463],{"class":439},[377,1971,1972],{"class":386}," warehouse\n",[178,1974,1975,1976,1979,1980,1983,1984,1987,1988,716,1990,1992,1993,1996,1997,2000,2001,2003],{},"Declare its credentials with\n",[201,1977,1978],{},"analytics.adapters: { warehouse: { credentials: ['host', 'key'] } }"," and set\n",[201,1981,1982],{},"NUXT_ANALYTICS_PROVIDERS_WAREHOUSE_HOST"," and\n",[201,1985,1986],{},"NUXT_ANALYTICS_PROVIDERS_WAREHOUSE_KEY",". An analytics sink receives only\n",[201,1989,1667],{},[201,1991,1672],{},", and is skipped while the key is empty. A marketing sink's\nkeys become ",[201,1994,1995],{},"NUXT_MARKETING_\u003CID>_\u003CKEY>"," and arrive together as\n",[201,1998,1999],{},"config.credentials",". Restart ",[201,2002,851],{}," after adding the file.",[178,2005,2006,2007,2010,2011,2013,2014,2017,2018,2021,2022,2024,2025,2028,2029,2032,2033,2035,2036,2039,2040,2043],{},"The other fields tell the dispatcher what to send. ",[201,2008,2009],{},"consentCategory"," is the\nconsent the sink needs: ",[201,2012,1595],{}," for analytics, ",[201,2015,2016],{},"'marketing'"," for conversions.\n",[201,2019,2020],{},"acceptsPageviews: false"," keeps ",[201,2023,271],{}," away, ",[201,2026,2027],{},"identityControl: 'derived'","\nsends only events that carry an IP and a user agent, and\n",[201,2030,2031],{},"mixedIdentityBatch: false"," batches one visitor at a time. A ",[201,2034,1642],{}," sink\nnever receives bot traffic and shares a budget of 12 outbound requests per\nincoming request with the other sinks. ",[201,2037,2038],{},"send"," must not throw, since it runs\nafter the response. ",[201,2041,2042],{},"retryable: true"," earns one immediate repeat, so leave it\nfalse for any 4xx.",[182,2045,20],{"id":2046},"configuration",[246,2048,2049,2062],{},[249,2050,2051],{},[252,2052,2053,2056,2059],{},[255,2054,2055],{},"Key",[255,2057,2058],{},"Default",[255,2060,2061],{},"What it does",[262,2063,2064,2078,2092,2106,2124,2139,2152,2166,2188],{},[252,2065,2066,2071,2075],{},[267,2067,2068],{},[201,2069,2070],{},"analytics.enabled",[267,2072,2073],{},[201,2074,1211],{},[267,2076,2077],{},"Off, no analytics sink is bundled.",[252,2079,2080,2084,2089],{},[267,2081,2082],{},[201,2083,734],{},[267,2085,2086],{},[201,2087,2088],{},"['cloudflare']",[267,2090,2091],{},"Sinks every event goes to.",[252,2093,2094,2098,2103],{},[267,2095,2096],{},[201,2097,218],{},[267,2099,2100],{},[201,2101,2102],{},"'\u002Fapi\u002Fe'",[267,2104,2105],{},"The beacon route. Rename it per project.",[252,2107,2108,2112,2116],{},[267,2109,2110],{},[201,2111,684],{},[267,2113,2114],{},[201,2115,277],{},[267,2117,2118,337,2120,1529,2122,204],{},[201,2119,693],{},[201,2121,277],{},[201,2123,708],{},[252,2125,2126,2130,2134],{},[267,2127,2128],{},[201,2129,1095],{},[267,2131,2132],{},[201,2133,1103],{},[267,2135,2136,2138],{},[201,2137,1116],{}," holds analytics until the visitor accepts.",[252,2140,2141,2146,2149],{},[267,2142,2143],{},[201,2144,2145],{},"analytics.adapters",[267,2147,2148],{},"none",[267,2150,2151],{},"Credential keys of a sink you added.",[252,2153,2154,2159,2163],{},[267,2155,2156],{},[201,2157,2158],{},"marketing.enabled",[267,2160,2161],{},[201,2162,708],{},[267,2164,2165],{},"Turns on conversion sinks and the consent banner.",[252,2167,2168,2172,2177],{},[267,2169,2170],{},[201,2171,1532],{},[267,2173,2174],{},[201,2175,2176],{},"[]",[267,2178,2179,337,2181,337,2183,337,2185,2187],{},[201,2180,1273],{},[201,2182,1293],{},[201,2184,1312],{},[201,2186,1347],{}," or a sink you added.",[252,2189,2190,2195,2197],{},[267,2191,2192],{},[201,2193,2194],{},"marketing.adapters",[267,2196,2148],{},[267,2198,2199],{},"Credential keys of a conversion sink you added.",[178,2201,2202,2203,2206,2207,2210,2211,2214,2215,2217,2218,789,2220,2223,2224,2226,2227,2229],{},"These live in ",[201,2204,2205],{},"starter.config.ts",". In ",[201,2208,2209],{},"wrangler.jsonc",", the\n",[201,2212,2213],{},"analytics_engine_datasets"," entry binds ",[201,2216,765],{}," to\n",[201,2219,862],{},[201,2221,2222],{},"ratelimits"," holds ",[201,2225,1182],{},".\n",[201,2228,1076],{}," seeds the visitor id.",[182,2231,2233],{"id":2232},"good-to-know","Good to know",[687,2235,2236,2242,2257,2275,2285],{},[190,2237,2238,2239,2241],{},"Under ",[201,2240,851],{},", writes to Analytics Engine go nowhere. Use PostHog or Umami\nto watch events locally.",[190,2243,2244,2245,2248,2249,2256],{},"Keep the blob order in ",[201,2246,2247],{},"cloudflare.ts"," as it is. Analytics Engine never\nrewrites a row, so reordering changes the meaning of everything already\nstored. Add columns at the end.\n",[194,2250,2253],{"href":2251,"rel":2252,"title":199},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Ftest\u002Funit\u002Fanalytics-adapters.test.ts",[198],[201,2254,2255],{},"test\u002Funit\u002Fanalytics-adapters.test.ts","\npins the order.",[190,2258,2259,2260,2263,2264,2267,2268,1529,2271,2274],{},"A ",[201,2261,2262],{},"getUserSession"," call of your own on an anonymous request sets a session\ncookie, which makes the page uncacheable. Check ",[201,2265,2266],{},"requestCarriesSession(event)","\nfirst, or use ",[201,2269,2270],{},"readSession",[201,2272,2273],{},"verifySession",", which already do.",[190,2276,2277,2278,2280,2281,2284],{},"Conversions carry no revenue. Meta requires a value on ",[201,2279,1465],{},", so it gets\n",[201,2282,2283],{},"0 USD",", which supports count-based optimization only.",[190,2286,2287,2288,2290,2291,2293,2294,2297],{},"With neither ",[201,2289,1076],{}," nor ",[201,2292,1080],{}," set, events\ncarry no visitor or session id, and the Worker logs\n",[201,2295,2296],{},"[analytics] no salt available"," once.",[2299,2300,2301],"style",{},"html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":373,"searchDepth":17,"depth":17,"links":2303},[2304,2305,2306,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318],{"id":184,"depth":17,"text":185},{"id":243,"depth":17,"text":244},{"id":358,"depth":17,"text":359,"children":2307},[2308],{"id":603,"depth":22,"text":604},{"id":678,"depth":17,"text":679},{"id":727,"depth":17,"text":728},{"id":855,"depth":17,"text":856},{"id":1069,"depth":17,"text":1070},{"id":1089,"depth":17,"text":1090},{"id":1134,"depth":17,"text":1135},{"id":1186,"depth":17,"text":1187},{"id":1510,"depth":17,"text":1511},{"id":2046,"depth":17,"text":20},{"id":2232,"depth":17,"text":2233},[2320,2321],{"path":124,"title":125,"description":126,"navTitle":125,"group":116,"order":127},{"path":134,"title":135,"description":136,"navTitle":135,"group":116,"order":137}]