[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-navigation":3,"docs-page-observability":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":1523,"linksToKit":544},{"path":134,"title":135,"description":136,"navTitle":135,"group":116,"seo":168,"body":170},{"title":169,"description":136},"Nuxt error tracking with Sentry or Better Stack",{"type":171,"value":172,"toc":1512},"minimark",[173,177,194,199,259,263,266,276,286,325,328,332,343,397,400,433,449,460,493,510,514,521,680,693,717,721,732,1046,1053,1116,1129,1133,1149,1238,1245,1249,1297,1300,1467,1471,1508],[174,175,135],"h1",{"id":176},"observability",[178,179,180,181,185,186,189,190,193],"p",{},"Every response from the Worker carries an ",[182,183,184],"code",{},"x-request-id"," header, and every\nserver fault is logged as one line under that id. With ",[182,187,188],{},"monitoring"," on, the same\nfaults also go to Sentry or Better Stack. ",[182,191,192],{},"GET \u002Fapi\u002Fhealth"," answers uptime\nchecks. You work here when you set up error tracking, report an error you\ncaught, or change the error page.",[195,196,198],"h2",{"id":197},"how-it-works","How it works",[200,201,202,223,241,252],"ol",{},[203,204,205,215,216,218,219,222],"li",{},[206,207,212],"a",{"href":208,"rel":209,"title":211},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fmiddleware\u002Frequest-id.ts",[210],"nofollow","Licensees only",[182,213,214],{},"server\u002Fmiddleware\u002Frequest-id.ts"," sets\n",[182,217,184],{}," before the handler runs: Cloudflare's ",[182,220,221],{},"cf-ray"," when the\nrequest has one, otherwise a random id.",[203,224,225,232,233,236,237,240],{},[206,226,229],{"href":227,"rel":228,"title":211},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fserver\u002Fplugins\u002Ferror-capture.ts",[210],[182,230,231],{},"server\u002Fplugins\u002Ferror-capture.ts","\nlistens on Nitro's ",[182,234,235],{},"error"," hook. A 5xx, or an error with no status, is\nwritten with ",[182,238,239],{},"console.error"," as one JSON line. A 4xx is neither logged nor\nreported.",[203,242,243,244,247,248,251],{},"The same hook passes the fault to every provider in ",[182,245,246],{},"monitoring.providers","\nthat has a DSN. For a request this runs in ",[182,249,250],{},"waitUntil",", after the log line,\nso a slow tracker never delays the error page.",[203,253,254,255,258],{},"Dead-lettered jobs and errors Nitro catches outside a request go through the\nsame hook. Scheduled tasks report through ",[182,256,257],{},"defineReportedTask",".",[195,260,262],{"id":261},"find-a-failed-request","Find a failed request",[178,264,265],{},"The log line holds the id, method, path, status and message, followed by the\nerror and its stack:",[267,268,274],"pre",{"className":269,"code":271,"language":272,"meta":273},[270],"language-text","{\"tag\":\"error\",\"requestId\":\"8f2a61c4bd9e3a10-FRA\",\"method\":\"POST\",\"path\":\"\u002Fapi\u002Fbilling\",\"status\":500,\"message\":\"...\"}\n","text","",[182,275,271],{"__ignoreMap":273},[178,277,278,279,281,282,285],{},"The path never includes the query string, because a query can carry a\nmagic-link or password-reset token. ",[182,280,176],{}," is on in ",[182,283,284],{},"wrangler.jsonc",",\nso the lines land in Workers Logs in the dashboard. To follow them live:",[267,287,291],{"className":288,"code":289,"language":290,"meta":273,"style":273},"language-bash shiki shiki-themes github-light github-light github-dark","pnpm exec wrangler tail \u003Cworker>\n","bash",[182,292,293],{"__ignoreMap":273},[294,295,297,301,305,308,311,315,318,322],"span",{"class":296,"line":12},"line",[294,298,300],{"class":299},"snPdu","pnpm",[294,302,304],{"class":303},"sIIMD"," exec",[294,306,307],{"class":303}," wrangler",[294,309,310],{"class":303}," tail",[294,312,314],{"class":313},"s8jYJ"," \u003C",[294,316,317],{"class":303},"worke",[294,319,321],{"class":320},"sxrX7","r",[294,323,324],{"class":313},">\n",[178,326,327],{},"A 5xx error page shows the same id as \"Reference 8f2a61c4bd9e3a10-FRA\", so a\nscreenshot from a user leads straight to the log line and the tracker event.",[195,329,331],{"id":330},"send-faults-to-sentry-or-better-stack","Send faults to Sentry or Better Stack",[178,333,334,335,342],{},"Turn monitoring on in ",[206,336,339],{"href":337,"rel":338,"title":211},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fstarter.config.ts",[210],[182,340,341],{},"starter.config.ts",". It ships off:",[267,344,348],{"className":345,"code":346,"language":347,"meta":273,"style":273},"language-ts shiki shiki-themes github-light github-light github-dark","monitoring: {\n  enabled: true,\n  providers: ['sentry', 'betterstack'],\n},\n","ts",[182,349,350,357,372,392],{"__ignoreMap":273},[294,351,352,354],{"class":296,"line":12},[294,353,188],{"class":299},[294,355,356],{"class":320},": {\n",[294,358,359,362,365,369],{"class":296,"line":17},[294,360,361],{"class":299},"  enabled",[294,363,364],{"class":320},": ",[294,366,368],{"class":367},"sBjJW","true",[294,370,371],{"class":320},",\n",[294,373,374,377,380,383,386,389],{"class":296,"line":22},[294,375,376],{"class":299},"  providers",[294,378,379],{"class":320},": [",[294,381,382],{"class":303},"'sentry'",[294,384,385],{"class":320},", ",[294,387,388],{"class":303},"'betterstack'",[294,390,391],{"class":320},"],\n",[294,393,394],{"class":296,"line":27},[294,395,396],{"class":320},"},\n",[178,398,399],{},"Then give each provider its DSN as a Worker secret:",[267,401,403],{"className":288,"code":402,"language":290,"meta":273,"style":273},"pnpm exec wrangler secret put NUXT_MONITORING_SENTRY_DSN --name \u003Cworker>\n",[182,404,405],{"__ignoreMap":273},[294,406,407,409,411,413,416,419,422,425,427,429,431],{"class":296,"line":12},[294,408,300],{"class":299},[294,410,304],{"class":303},[294,412,307],{"class":303},[294,414,415],{"class":303}," secret",[294,417,418],{"class":303}," put",[294,420,421],{"class":303}," NUXT_MONITORING_SENTRY_DSN",[294,423,424],{"class":367}," --name",[294,426,314],{"class":313},[294,428,317],{"class":303},[294,430,321],{"class":320},[294,432,324],{"class":313},[434,435,436,443],"ul",{},[203,437,438,439,442],{},"Sentry: Project Settings > Client Keys (DSN). A self-hosted Sentry or a\nGlitchTip DSN works as ",[182,440,441],{},"sentry"," too.",[203,444,445,446,258],{},"Better Stack: Errors > Applications > your application > Ingest shows the\ntoken and host for ",[182,447,448],{},"https:\u002F\u002F\u003Capplication-token>@\u003Cingesting-host>\u002F\u003Capplication-id>",[178,450,451,452,455,456,459],{},"A listed provider without a DSN is skipped, and the Worker's ",[182,453,454],{},"[env]"," line at\nstartup names it. There is no SDK: the kit posts Sentry's envelope format with\n",[182,457,458],{},"fetch",", which Better Stack accepts as well, and an unlisted provider adds\nnothing to the bundle.",[178,461,462,463,466,467,371,470,473,474,477,478,385,481,484,485,488,489,492],{},"Each event carries the error's type, message and stack, its causes oldest\nfirst, ",[182,464,465],{},"METHOD \u002Fpath"," as the transaction, and three tags: ",[182,468,469],{},"request_id",[182,471,472],{},"status",", and ",[182,475,476],{},"capture"," (such as ",[182,479,480],{},"request",[182,482,483],{},"queue,dead-letter"," or\n",[182,486,487],{},"cron,\u003Ctask>","). It carries no query string, headers, cookies, body, IP address\nor user, and asks Sentry not to take the Worker's own address for a user's, so\na report counts no users. When a Drizzle query fails, the values it bound are\nreplaced with ",[182,490,491],{},"params: [redacted]"," before anything logs or sends the error.",[178,494,495,498,499,502,503,506,507,258],{},[182,496,497],{},"NUXT_MONITORING_ENVIRONMENT"," names the environment. Unset, it is ",[182,500,501],{},"production","\non a deploy and ",[182,504,505],{},"development"," under ",[182,508,509],{},"pnpm dev",[195,511,513],{"id":512},"report-an-error-you-caught","Report an error you caught",[178,515,516,517,520],{},"An error your code catches never reaches the hook. Report it with\n",[182,518,519],{},"reportFault",":",[267,522,524],{"className":345,"code":523,"language":347,"meta":273,"style":273},"import { reportFault } from '#server\u002Fmonitoring'\n\ntry {\n  await syncContact(user)\n} catch (error) {\n  event.waitUntil(\n    reportFault(\n      {\n        error: error instanceof Error ? error : new Error(String(error)),\n        tags: ['crm-sync'],\n        at: new Date(),\n      },\n      event,\n    ),\n  )\n}\n",[182,525,526,540,546,554,565,576,586,593,598,631,641,655,660,665,670,675],{"__ignoreMap":273},[294,527,528,531,534,537],{"class":296,"line":12},[294,529,530],{"class":313},"import",[294,532,533],{"class":320}," { reportFault } ",[294,535,536],{"class":313},"from",[294,538,539],{"class":303}," '#server\u002Fmonitoring'\n",[294,541,542],{"class":296,"line":17},[294,543,545],{"emptyLinePlaceholder":544},true,"\n",[294,547,548,551],{"class":296,"line":22},[294,549,550],{"class":313},"try",[294,552,553],{"class":320}," {\n",[294,555,556,559,562],{"class":296,"line":27},[294,557,558],{"class":313},"  await",[294,560,561],{"class":299}," syncContact",[294,563,564],{"class":320},"(user)\n",[294,566,567,570,573],{"class":296,"line":32},[294,568,569],{"class":320},"} ",[294,571,572],{"class":313},"catch",[294,574,575],{"class":320}," (error) {\n",[294,577,578,581,583],{"class":296,"line":37},[294,579,580],{"class":320},"  event.",[294,582,250],{"class":299},[294,584,585],{"class":320},"(\n",[294,587,588,591],{"class":296,"line":46},[294,589,590],{"class":299},"    reportFault",[294,592,585],{"class":320},[294,594,595],{"class":296,"line":51},[294,596,597],{"class":320},"      {\n",[294,599,600,603,606,609,612,615,617,620,622,625,628],{"class":296,"line":56},[294,601,602],{"class":320},"        error: error ",[294,604,605],{"class":313},"instanceof",[294,607,608],{"class":299}," Error",[294,610,611],{"class":313}," ?",[294,613,614],{"class":320}," error ",[294,616,520],{"class":313},[294,618,619],{"class":313}," new",[294,621,608],{"class":299},[294,623,624],{"class":320},"(",[294,626,627],{"class":299},"String",[294,629,630],{"class":320},"(error)),\n",[294,632,633,636,639],{"class":296,"line":64},[294,634,635],{"class":320},"        tags: [",[294,637,638],{"class":303},"'crm-sync'",[294,640,391],{"class":320},[294,642,643,646,649,652],{"class":296,"line":73},[294,644,645],{"class":320},"        at: ",[294,647,648],{"class":313},"new",[294,650,651],{"class":299}," Date",[294,653,654],{"class":320},"(),\n",[294,656,657],{"class":296,"line":78},[294,658,659],{"class":320},"      },\n",[294,661,662],{"class":296,"line":83},[294,663,664],{"class":320},"      event,\n",[294,666,667],{"class":296,"line":88},[294,668,669],{"class":320},"    ),\n",[294,671,672],{"class":296,"line":93},[294,673,674],{"class":320},"  )\n",[294,676,677],{"class":296,"line":98},[294,678,679],{"class":320},"}\n",[178,681,682,684,685,688,689,692],{},[182,683,519],{}," never rejects and does nothing while monitoring is off. To log a\ncaught query error yourself, pass it through ",[182,686,687],{},"redactBoundValues(error)"," from\n",[182,690,691],{},"#server\u002Fmonitoring\u002Fbound-values"," first, or the log line prints the values the\nquery bound.",[178,694,695,696,698,699,701,702,705,706,709,710,713,714,716],{},"Nitro runs no ",[182,697,235],{}," hook for scheduled tasks. Define them with\n",[182,700,257],{}," from ",[182,703,704],{},"#server\u002Fmonitoring\u002Ftask"," instead of ",[182,707,708],{},"defineTask",": a\nthrow is reported, tagged ",[182,711,712],{},"cron"," and the task's name, then rethrown\n(",[206,715,76],{"href":75},").",[195,718,720],{"id":719},"add-a-tracker-of-your-own","Add a tracker of your own",[178,722,723,724,727,728,731],{},"Drop a file in ",[182,725,726],{},"server\u002Fmonitoring\u002Fadapters\u002Fcustom\u002F",". Its name is the provider\nid, one lowercase word. ",[182,729,730],{},"server\u002Fmonitoring\u002Fadapters\u002Fcustom\u002Fslack.ts"," posts each\nfault to a Slack incoming webhook:",[267,733,735],{"className":345,"code":734,"language":347,"meta":273,"style":273},"import type { MonitoringAdapter } from '#server\u002Fmonitoring\u002Ftypes'\n\nexport default {\n  id: 'slack',\n\n  async send(fault, { dsn, environment }) {\n    try {\n      const response = await fetch(dsn, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fjson' },\n        body: JSON.stringify({\n          text: `[${environment}] ${fault.error.name}: ${fault.error.message} ${fault.request?.requestId ?? ''}`,\n        }),\n      })\n\n      return response.ok\n        ? { outcome: 'sent' }\n        : { outcome: 'refused', reason: `answered ${response.status}` }\n    } catch (error) {\n      return { outcome: 'refused', reason: String(error) }\n    }\n  },\n} satisfies MonitoringAdapter\n",[182,736,737,752,756,766,776,780,808,815,835,845,861,877,938,943,948,952,960,974,1002,1011,1026,1031,1036],{"__ignoreMap":273},[294,738,739,741,744,747,749],{"class":296,"line":12},[294,740,530],{"class":313},[294,742,743],{"class":313}," type",[294,745,746],{"class":320}," { MonitoringAdapter } ",[294,748,536],{"class":313},[294,750,751],{"class":303}," '#server\u002Fmonitoring\u002Ftypes'\n",[294,753,754],{"class":296,"line":17},[294,755,545],{"emptyLinePlaceholder":544},[294,757,758,761,764],{"class":296,"line":22},[294,759,760],{"class":313},"export",[294,762,763],{"class":313}," default",[294,765,553],{"class":320},[294,767,768,771,774],{"class":296,"line":27},[294,769,770],{"class":320},"  id: ",[294,772,773],{"class":303},"'slack'",[294,775,371],{"class":320},[294,777,778],{"class":296,"line":32},[294,779,545],{"emptyLinePlaceholder":544},[294,781,782,785,788,790,794,797,800,802,805],{"class":296,"line":37},[294,783,784],{"class":313},"  async",[294,786,787],{"class":299}," send",[294,789,624],{"class":320},[294,791,793],{"class":792},"sP4rz","fault",[294,795,796],{"class":320},", { ",[294,798,799],{"class":792},"dsn",[294,801,385],{"class":320},[294,803,804],{"class":792},"environment",[294,806,807],{"class":320}," }) {\n",[294,809,810,813],{"class":296,"line":46},[294,811,812],{"class":313},"    try",[294,814,553],{"class":320},[294,816,817,820,823,826,829,832],{"class":296,"line":51},[294,818,819],{"class":313},"      const",[294,821,822],{"class":367}," response",[294,824,825],{"class":313}," =",[294,827,828],{"class":313}," await",[294,830,831],{"class":299}," fetch",[294,833,834],{"class":320},"(dsn, {\n",[294,836,837,840,843],{"class":296,"line":56},[294,838,839],{"class":320},"        method: ",[294,841,842],{"class":303},"'POST'",[294,844,371],{"class":320},[294,846,847,850,853,855,858],{"class":296,"line":64},[294,848,849],{"class":320},"        headers: { ",[294,851,852],{"class":303},"'Content-Type'",[294,854,364],{"class":320},[294,856,857],{"class":303},"'application\u002Fjson'",[294,859,860],{"class":320}," },\n",[294,862,863,866,869,871,874],{"class":296,"line":73},[294,864,865],{"class":320},"        body: ",[294,867,868],{"class":367},"JSON",[294,870,258],{"class":320},[294,872,873],{"class":299},"stringify",[294,875,876],{"class":320},"({\n",[294,878,879,882,885,887,890,892,894,896,898,901,904,906,908,910,912,915,918,920,922,924,927,930,933,936],{"class":296,"line":78},[294,880,881],{"class":320},"          text: ",[294,883,884],{"class":303},"`[${",[294,886,804],{"class":320},[294,888,889],{"class":303},"}] ${",[294,891,793],{"class":320},[294,893,258],{"class":303},[294,895,235],{"class":320},[294,897,258],{"class":303},[294,899,900],{"class":320},"name",[294,902,903],{"class":303},"}: ${",[294,905,793],{"class":320},[294,907,258],{"class":303},[294,909,235],{"class":320},[294,911,258],{"class":303},[294,913,914],{"class":320},"message",[294,916,917],{"class":303},"} ${",[294,919,793],{"class":320},[294,921,258],{"class":303},[294,923,480],{"class":320},[294,925,926],{"class":303},"?.",[294,928,929],{"class":320},"requestId",[294,931,932],{"class":313}," ??",[294,934,935],{"class":303}," ''}`",[294,937,371],{"class":320},[294,939,940],{"class":296,"line":83},[294,941,942],{"class":320},"        }),\n",[294,944,945],{"class":296,"line":88},[294,946,947],{"class":320},"      })\n",[294,949,950],{"class":296,"line":93},[294,951,545],{"emptyLinePlaceholder":544},[294,953,954,957],{"class":296,"line":98},[294,955,956],{"class":313},"      return",[294,958,959],{"class":320}," response.ok\n",[294,961,962,965,968,971],{"class":296,"line":103},[294,963,964],{"class":313},"        ?",[294,966,967],{"class":320}," { outcome: ",[294,969,970],{"class":303},"'sent'",[294,972,973],{"class":320}," }\n",[294,975,976,979,981,984,987,990,993,995,997,1000],{"class":296,"line":108},[294,977,978],{"class":313},"        :",[294,980,967],{"class":320},[294,982,983],{"class":303},"'refused'",[294,985,986],{"class":320},", reason: ",[294,988,989],{"class":303},"`answered ${",[294,991,992],{"class":320},"response",[294,994,258],{"class":303},[294,996,472],{"class":320},[294,998,999],{"class":303},"}`",[294,1001,973],{"class":320},[294,1003,1004,1007,1009],{"class":296,"line":113},[294,1005,1006],{"class":320},"    } ",[294,1008,572],{"class":313},[294,1010,575],{"class":320},[294,1012,1013,1015,1017,1019,1021,1023],{"class":296,"line":122},[294,1014,956],{"class":313},[294,1016,967],{"class":320},[294,1018,983],{"class":303},[294,1020,986],{"class":320},[294,1022,627],{"class":299},[294,1024,1025],{"class":320},"(error) }\n",[294,1027,1028],{"class":296,"line":127},[294,1029,1030],{"class":320},"    }\n",[294,1032,1033],{"class":296,"line":132},[294,1034,1035],{"class":320},"  },\n",[294,1037,1038,1040,1043],{"class":296,"line":137},[294,1039,569],{"class":320},[294,1041,1042],{"class":313},"satisfies",[294,1044,1045],{"class":299}," MonitoringAdapter\n",[178,1047,1048,1049,1052],{},"List it and declare its credential, then set ",[182,1050,1051],{},"NUXT_MONITORING_SLACK_DSN"," to\nthe webhook URL:",[267,1054,1056],{"className":345,"code":1055,"language":347,"meta":273,"style":273},"monitoring: {\n  enabled: true,\n  providers: ['sentry', 'slack'],\n  adapters: { slack: { credentials: ['dsn'] } },\n},\n",[182,1057,1058,1064,1074,1088,1112],{"__ignoreMap":273},[294,1059,1060,1062],{"class":296,"line":12},[294,1061,188],{"class":299},[294,1063,356],{"class":320},[294,1065,1066,1068,1070,1072],{"class":296,"line":17},[294,1067,361],{"class":299},[294,1069,364],{"class":320},[294,1071,368],{"class":367},[294,1073,371],{"class":320},[294,1075,1076,1078,1080,1082,1084,1086],{"class":296,"line":22},[294,1077,376],{"class":299},[294,1079,379],{"class":320},[294,1081,382],{"class":303},[294,1083,385],{"class":320},[294,1085,773],{"class":303},[294,1087,391],{"class":320},[294,1089,1090,1093,1096,1099,1101,1104,1106,1109],{"class":296,"line":27},[294,1091,1092],{"class":299},"  adapters",[294,1094,1095],{"class":320},": { ",[294,1097,1098],{"class":299},"slack",[294,1100,1095],{"class":320},[294,1102,1103],{"class":299},"credentials",[294,1105,379],{"class":320},[294,1107,1108],{"class":303},"'dsn'",[294,1110,1111],{"class":320},"] } },\n",[294,1113,1114],{"class":296,"line":32},[294,1115,396],{"class":320},[178,1117,1118,1119,1121,1122,1124,1125,1128],{},"An adapter receives only ",[182,1120,799],{}," and ",[182,1123,804],{},", must not throw, and should not\nretry. A refusal is logged as a ",[182,1126,1127],{},"[monitoring]"," warning.",[195,1130,1132],{"id":1131},"check-health","Check health",[267,1134,1136],{"className":288,"code":1135,"language":290,"meta":273,"style":273},"curl -i https:\u002F\u002Fexample.com\u002Fapi\u002Fhealth\n",[182,1137,1138],{"__ignoreMap":273},[294,1139,1140,1143,1146],{"class":296,"line":12},[294,1141,1142],{"class":299},"curl",[294,1144,1145],{"class":367}," -i",[294,1147,1148],{"class":303}," https:\u002F\u002Fexample.com\u002Fapi\u002Fhealth\n",[267,1150,1154],{"className":1151,"code":1152,"language":1153,"meta":273,"style":273},"language-json shiki shiki-themes github-light github-light github-dark","{\n  \"ok\": true,\n  \"checks\": { \"database\": true },\n  \"requestId\": \"8f2a61c4bd9e3a10-FRA\",\n  \"features\": { \"auth\": true, \"payments\": true, \"email\": true }\n}\n","json",[182,1155,1156,1161,1172,1188,1200,1234],{"__ignoreMap":273},[294,1157,1158],{"class":296,"line":12},[294,1159,1160],{"class":320},"{\n",[294,1162,1163,1166,1168,1170],{"class":296,"line":17},[294,1164,1165],{"class":367},"  \"ok\"",[294,1167,364],{"class":320},[294,1169,368],{"class":367},[294,1171,371],{"class":320},[294,1173,1174,1177,1179,1182,1184,1186],{"class":296,"line":22},[294,1175,1176],{"class":367},"  \"checks\"",[294,1178,1095],{"class":320},[294,1180,1181],{"class":367},"\"database\"",[294,1183,364],{"class":320},[294,1185,368],{"class":367},[294,1187,860],{"class":320},[294,1189,1190,1193,1195,1198],{"class":296,"line":27},[294,1191,1192],{"class":367},"  \"requestId\"",[294,1194,364],{"class":320},[294,1196,1197],{"class":303},"\"8f2a61c4bd9e3a10-FRA\"",[294,1199,371],{"class":320},[294,1201,1202,1205,1207,1210,1212,1214,1216,1219,1221,1223,1225,1228,1230,1232],{"class":296,"line":32},[294,1203,1204],{"class":367},"  \"features\"",[294,1206,1095],{"class":320},[294,1208,1209],{"class":367},"\"auth\"",[294,1211,364],{"class":320},[294,1213,368],{"class":367},[294,1215,385],{"class":320},[294,1217,1218],{"class":367},"\"payments\"",[294,1220,364],{"class":320},[294,1222,368],{"class":367},[294,1224,385],{"class":320},[294,1226,1227],{"class":367},"\"email\"",[294,1229,364],{"class":320},[294,1231,368],{"class":367},[294,1233,973],{"class":320},[294,1235,1236],{"class":296,"line":37},[294,1237,679],{"class":320},[178,1239,1240,1241,1244],{},"It answers 200 when Postgres returns a one-row select and 503 when it doesn't,\nwith ",[182,1242,1243],{},"cache-control: no-store",", and needs no sign-in. An uptime monitor on it\ntells a Worker that is down from a Worker whose database is down.",[195,1246,1248],{"id":1247},"change-the-error-page","Change the error page",[178,1250,1251,1258,1259,1262,1263,1270,1271,1274,1275,1278,1279,1282,1283,1286,1287,1121,1290,1293,1294,1296],{},[206,1252,1255],{"href":1253,"rel":1254,"title":211},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fapp\u002Ferror.vue",[210],[182,1256,1257],{},"app\u002Ferror.vue"," renders every failure, and the status picks\nthe copy. ",[182,1260,1261],{},"errorCopy()"," in\n",[206,1264,1267],{"href":1265,"rel":1266,"title":211},"https:\u002F\u002Fgithub.com\u002Fnuxter-app\u002Fkit\u002Fblob\u002Fmain\u002Fapp\u002Futils\u002Ferror-copy.ts",[210],[182,1268,1269],{},"app\u002Futils\u002Ferror-copy.ts"," gives 400, 401, 403,\n404, 410 and 429 their own copy, reads any other 4xx as ",[182,1272,1273],{},"client"," and everything\nelse as ",[182,1276,1277],{},"server",". To give another status its own page, add it to\n",[182,1280,1281],{},"COPY_FOR_STATUSES_WITH_THEIR_OWN_MESSAGE",", give it an icon in ",[182,1284,1285],{},"ERROR_ICONS",",\nand add ",[182,1288,1289],{},"errors.status.\u003Cname>.title",[182,1291,1292],{},".description"," to every locale. Only\nthe ",[182,1295,1277],{}," copy shows the request id.",[195,1298,20],{"id":1299},"configuration",[1301,1302,1303,1322],"table",{},[1304,1305,1306],"thead",{},[1307,1308,1309,1313,1316,1319],"tr",{},[1310,1311,1312],"th",{},"Key or variable",[1310,1314,1315],{},"Where",[1310,1317,1318],{},"Default",[1310,1320,1321],{},"What it does",[1323,1324,1325,1345,1363,1380,1395,1409,1431,1449],"tbody",{},[1307,1326,1327,1333,1337,1342],{},[1328,1329,1330],"td",{},[182,1331,1332],{},"monitoring.enabled",[1328,1334,1335],{},[182,1336,341],{},[1328,1338,1339],{},[182,1340,1341],{},"false",[1328,1343,1344],{},"Forwards faults to trackers. Off, they are only logged",[1307,1346,1347,1351,1355,1360],{},[1328,1348,1349],{},[182,1350,246],{},[1328,1352,1353],{},[182,1354,341],{},[1328,1356,1357],{},[182,1358,1359],{},"['sentry']",[1328,1361,1362],{},"Every fault goes to each of these that has a DSN",[1307,1364,1365,1370,1374,1377],{},[1328,1366,1367],{},[182,1368,1369],{},"monitoring.adapters",[1328,1371,1372],{},[182,1373,341],{},[1328,1375,1376],{},"none",[1328,1378,1379],{},"Credentials of an adapter you added",[1307,1381,1382,1387,1390,1392],{},[1328,1383,1384],{},[182,1385,1386],{},"NUXT_MONITORING_SENTRY_DSN",[1328,1388,1389],{},"Worker secret",[1328,1391,1376],{},[1328,1393,1394],{},"Sentry's DSN",[1307,1396,1397,1402,1404,1406],{},[1328,1398,1399],{},[182,1400,1401],{},"NUXT_MONITORING_BETTERSTACK_DSN",[1328,1403,1389],{},[1328,1405,1376],{},[1328,1407,1408],{},"Better Stack's DSN",[1307,1410,1411,1415,1418,1428],{},[1328,1412,1413],{},[182,1414,497],{},[1328,1416,1417],{},"Worker variable",[1328,1419,1420,1422,1423,1425,1426],{},[182,1421,501],{},", or ",[182,1424,505],{}," in ",[182,1427,509],{},[1328,1429,1430],{},"The environment every event is tagged with",[1307,1432,1433,1438,1442,1446],{},[1328,1434,1435],{},[182,1436,1437],{},"observability.enabled",[1328,1439,1440],{},[182,1441,284],{},[1328,1443,1444],{},[182,1445,368],{},[1328,1447,1448],{},"Keeps Worker logs in the dashboard",[1307,1450,1451,1456,1460,1464],{},[1328,1452,1453],{},[182,1454,1455],{},"upload_source_maps",[1328,1457,1458],{},[182,1459,284],{},[1328,1461,1462],{},[182,1463,368],{},[1328,1465,1466],{},"Maps stack traces in Cloudflare's logs back to source",[195,1468,1470],{"id":1469},"good-to-know","Good to know",[434,1472,1473,1476,1483,1486,1489,1492],{},[203,1474,1475],{},"Stack frames in Sentry and Better Stack point at the minified Worker bundle,\nnot your TypeScript. Only Cloudflare's own logs are source-mapped. Triage by\ntype, message, route and request id.",[203,1477,1478,1479,1482],{},"Nothing is retried. A 429 or an ",[182,1480,1481],{},"X-Sentry-Rate-Limits"," header pauses that DSN\nfor as long as it asks, or a minute when it names no time. The pause lives in\nthe isolate, and events dropped during it aren't logged.",[203,1484,1485],{},"A tracker that hasn't answered within three seconds is given up on, and its\nevent is dropped with a warning in the log. Some faults are reported while a\nrequest waits, such as Polar refusing the kit's API version during a\ncheckout, and a stalled tracker must not hold that request.",[203,1487,1488],{},"Each forward spends one subrequest per provider from the failed request's own\nbudget. A request that failed by running out of subrequests reaches only the\nlog.",[203,1490,1491],{},"Errors thrown in the browser never reach the server, so no tracker sees them.",[203,1493,1494,1495,1498,1499,1425,1502,1505,1506,716],{},"A route's ",[182,1496,1497],{},"statusMessage"," is English for logs and API clients, never for\npeople. The browser words a refusal from the ",[182,1500,1501],{},"reason",[182,1503,1504],{},"data","\n(",[206,1507,149],{"href":148},[1509,1510,1511],"style",{},"html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":273,"searchDepth":17,"depth":17,"links":1513},[1514,1515,1516,1517,1518,1519,1520,1521,1522],{"id":197,"depth":17,"text":198},{"id":261,"depth":17,"text":262},{"id":330,"depth":17,"text":331},{"id":512,"depth":17,"text":513},{"id":719,"depth":17,"text":720},{"id":1131,"depth":17,"text":1132},{"id":1247,"depth":17,"text":1248},{"id":1299,"depth":17,"text":20},{"id":1469,"depth":17,"text":1470},[1524,1525],{"path":129,"title":130,"description":131,"navTitle":130,"group":116,"order":132},{"path":143,"title":144,"description":145,"navTitle":144,"group":140,"order":146}]