[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-author":3,"blog-en-\u002Fblog\u002Fnuxt-cloudflare-workers-bindings":4},null,{"id":5,"title":6,"author":3,"body":7,"createdAt":1837,"description":1838,"draft":1839,"extension":1840,"image":1841,"meta":1844,"navigation":419,"path":1845,"readingTime":151,"seo":1846,"sitemap":1847,"stem":1848,"tags":1849,"updatedAt":1837,"__hash__":1854},"blog_en\u002Fblog\u002Fnuxt-cloudflare-workers-bindings.md","Nuxt 3 on Cloudflare Workers: getting bindings right",{"type":8,"value":9,"toc":1825},"minimark",[10,15,19,31,35,53,164,263,298,302,305,374,377,557,564,939,942,1232,1243,1250,1253,1277,1299,1397,1420,1424,1449,1452,1509,1524,1530,1537,1559,1566,1614,1621,1625,1632,1665,1668,1735,1745,1751,1755,1815,1821],[11,12,14],"h2",{"id":13},"the-one-thing-that-makes-workers-worth-it","The one thing that makes Workers worth it",[16,17,18],"p",{},"You can host a Nuxt app almost anywhere. The reason to put it on Cloudflare Workers is not the CDN — it's the bindings. A KV namespace, a D1 database, an R2 bucket, or a Durable Object handed to your server routes as a live object, with no connection string, no pooling, and no cold-start handshake.",[16,20,21,22,26,27,30],{},"The catch is that bindings don't exist in the Node.js world Nuxt normally runs in. They arrive per-request, attached to the Workers execution context. Getting them into your ",[23,24,25],"code",{},"server\u002Fapi"," handlers — and having them work in ",[23,28,29],{},"nuxt dev"," — is the first real piece of plumbing in any Nuxt 3 + Workers project.",[11,32,34],{"id":33},"configure-the-preset-and-the-wrangler-file","Configure the preset and the Wrangler file",[16,36,37,38,41,42,45,46,49,50,52],{},"Nitro has two Cloudflare targets that matter. ",[23,39,40],{},"cloudflare_module"," builds a Worker with a module-style entry (",[23,43,44],{},"export default { fetch }",") and serves your static output through the Workers Assets binding. ",[23,47,48],{},"cloudflare-pages"," targets Pages Functions. For new projects, prefer ",[23,51,40],{},": Workers Assets is where Cloudflare is investing, and you get the full Worker feature set (Durable Objects, cron triggers, queues) instead of the Pages subset.",[54,55,60],"pre",{"className":56,"code":57,"language":58,"meta":59,"style":59},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['nitro-cloudflare-dev'],\n  nitro: {\n    preset: 'cloudflare_module',\n  },\n})\n","ts","",[23,61,62,71,93,121,132,149,155],{"__ignoreMap":59},[63,64,67],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"sHwdD","\u002F\u002F nuxt.config.ts\n",[63,72,74,78,81,85,89],{"class":65,"line":73},2,[63,75,77],{"class":76},"s7zQu","export",[63,79,80],{"class":76}," default",[63,82,84],{"class":83},"s2Zo4"," defineNuxtConfig",[63,86,88],{"class":87},"sTEyZ","(",[63,90,92],{"class":91},"sMK4o","{\n",[63,94,96,100,103,106,109,113,115,118],{"class":65,"line":95},3,[63,97,99],{"class":98},"swJcz","  modules",[63,101,102],{"class":91},":",[63,104,105],{"class":87}," [",[63,107,108],{"class":91},"'",[63,110,112],{"class":111},"sfazB","nitro-cloudflare-dev",[63,114,108],{"class":91},[63,116,117],{"class":87},"]",[63,119,120],{"class":91},",\n",[63,122,124,127,129],{"class":65,"line":123},4,[63,125,126],{"class":98},"  nitro",[63,128,102],{"class":91},[63,130,131],{"class":91}," {\n",[63,133,135,138,140,143,145,147],{"class":65,"line":134},5,[63,136,137],{"class":98},"    preset",[63,139,102],{"class":91},[63,141,142],{"class":91}," '",[63,144,40],{"class":111},[63,146,108],{"class":91},[63,148,120],{"class":91},[63,150,152],{"class":65,"line":151},6,[63,153,154],{"class":91},"  },\n",[63,156,158,161],{"class":65,"line":157},7,[63,159,160],{"class":91},"}",[63,162,163],{"class":87},")\n",[54,165,169],{"className":166,"code":167,"language":168,"meta":59,"style":59},"language-jsonc shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F wrangler.jsonc\n{\n  \"name\": \"nuxt-cf-starter\",\n  \"main\": \".\u002F.output\u002Fserver\u002Findex.mjs\",\n  \"compatibility_date\": \"2025-04-01\",\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"assets\": {\n    \"directory\": \".\u002F.output\u002Fpublic\u002F\",\n    \"binding\": \"ASSETS\",\n  },\n  \"kv_namespaces\": [\n    { \"binding\": \"CACHE\", \"id\": \"3f9c...\", \"preview_id\": \"8a12...\" },\n  ],\n  \"d1_databases\": [\n    { \"binding\": \"DB\", \"database_name\": \"app\", \"database_id\": \"b41e...\" },\n  ],\n}\n","jsonc",[23,170,171,176,180,185,190,195,200,205,211,217,222,228,234,240,246,252,257],{"__ignoreMap":59},[63,172,173],{"class":65,"line":66},[63,174,175],{},"\u002F\u002F wrangler.jsonc\n",[63,177,178],{"class":65,"line":73},[63,179,92],{},[63,181,182],{"class":65,"line":95},[63,183,184],{},"  \"name\": \"nuxt-cf-starter\",\n",[63,186,187],{"class":65,"line":123},[63,188,189],{},"  \"main\": \".\u002F.output\u002Fserver\u002Findex.mjs\",\n",[63,191,192],{"class":65,"line":134},[63,193,194],{},"  \"compatibility_date\": \"2025-04-01\",\n",[63,196,197],{"class":65,"line":151},[63,198,199],{},"  \"compatibility_flags\": [\"nodejs_compat\"],\n",[63,201,202],{"class":65,"line":157},[63,203,204],{},"  \"assets\": {\n",[63,206,208],{"class":65,"line":207},8,[63,209,210],{},"    \"directory\": \".\u002F.output\u002Fpublic\u002F\",\n",[63,212,214],{"class":65,"line":213},9,[63,215,216],{},"    \"binding\": \"ASSETS\",\n",[63,218,220],{"class":65,"line":219},10,[63,221,154],{},[63,223,225],{"class":65,"line":224},11,[63,226,227],{},"  \"kv_namespaces\": [\n",[63,229,231],{"class":65,"line":230},12,[63,232,233],{},"    { \"binding\": \"CACHE\", \"id\": \"3f9c...\", \"preview_id\": \"8a12...\" },\n",[63,235,237],{"class":65,"line":236},13,[63,238,239],{},"  ],\n",[63,241,243],{"class":65,"line":242},14,[63,244,245],{},"  \"d1_databases\": [\n",[63,247,249],{"class":65,"line":248},15,[63,250,251],{},"    { \"binding\": \"DB\", \"database_name\": \"app\", \"database_id\": \"b41e...\" },\n",[63,253,255],{"class":65,"line":254},16,[63,256,239],{},[63,258,260],{"class":65,"line":259},17,[63,261,262],{},"}\n",[16,264,265,266,269,270,273,274,277,278,281,282,285,286,289,290,293,294,297],{},"Two notes on that file. ",[23,267,268],{},"nodejs_compat"," is almost always required — Nitro's output, and most npm packages you'll pull in, expect ",[23,271,272],{},"node:buffer",", ",[23,275,276],{},"node:crypto",", or ",[23,279,280],{},"node:async_hooks"," to exist. And ",[23,283,284],{},"main"," points at build output, so ",[23,287,288],{},"nuxt build"," has to run before ",[23,291,292],{},"wrangler deploy",". A ",[23,295,296],{},"\"deploy\": \"nuxt build && wrangler deploy\""," script keeps that honest.",[11,299,301],{"id":300},"reading-bindings-inside-a-server-route","Reading bindings inside a server route",[16,303,304],{},"Nitro exposes the Workers environment on the H3 event:",[54,306,308],{"className":56,"code":307,"language":58,"meta":59,"style":59},"event.context.cloudflare.env \u002F\u002F your bindings + vars + secrets\nevent.context.cloudflare.context \u002F\u002F ExecutionContext (waitUntil, passThroughOnException)\nevent.context.cloudflare.request \u002F\u002F the raw Request\n",[23,309,310,334,354],{"__ignoreMap":59},[63,311,312,315,318,321,323,326,328,331],{"class":65,"line":66},[63,313,314],{"class":87},"event",[63,316,317],{"class":91},".",[63,319,320],{"class":87},"context",[63,322,317],{"class":91},[63,324,325],{"class":87},"cloudflare",[63,327,317],{"class":91},[63,329,330],{"class":87},"env ",[63,332,333],{"class":69},"\u002F\u002F your bindings + vars + secrets\n",[63,335,336,338,340,342,344,346,348,351],{"class":65,"line":73},[63,337,314],{"class":87},[63,339,317],{"class":91},[63,341,320],{"class":87},[63,343,317],{"class":91},[63,345,325],{"class":87},[63,347,317],{"class":91},[63,349,350],{"class":87},"context ",[63,352,353],{"class":69},"\u002F\u002F ExecutionContext (waitUntil, passThroughOnException)\n",[63,355,356,358,360,362,364,366,368,371],{"class":65,"line":95},[63,357,314],{"class":87},[63,359,317],{"class":91},[63,361,320],{"class":87},[63,363,317],{"class":91},[63,365,325],{"class":87},[63,367,317],{"class":91},[63,369,370],{"class":87},"request ",[63,372,373],{"class":69},"\u002F\u002F the raw Request\n",[16,375,376],{},"Wrap that access once so every route fails the same way when something is misconfigured:",[54,378,380],{"className":56,"code":379,"language":58,"meta":59,"style":59},"\u002F\u002F server\u002Futils\u002Fcloudflare.ts\nimport type { H3Event } from 'h3'\n\nexport function cf(event: H3Event) {\n  const cloudflare = event.context.cloudflare\n  if (!cloudflare) {\n    throw createError({\n      statusCode: 500,\n      statusMessage:\n        'Cloudflare bindings unavailable — is nitro-cloudflare-dev enabled?',\n    })\n  }\n  return cloudflare\n}\n",[23,381,382,387,415,421,447,470,488,500,513,521,533,540,545,553],{"__ignoreMap":59},[63,383,384],{"class":65,"line":66},[63,385,386],{"class":69},"\u002F\u002F server\u002Futils\u002Fcloudflare.ts\n",[63,388,389,392,395,398,401,404,407,409,412],{"class":65,"line":73},[63,390,391],{"class":76},"import",[63,393,394],{"class":76}," type",[63,396,397],{"class":91}," {",[63,399,400],{"class":87}," H3Event",[63,402,403],{"class":91}," }",[63,405,406],{"class":76}," from",[63,408,142],{"class":91},[63,410,411],{"class":111},"h3",[63,413,414],{"class":91},"'\n",[63,416,417],{"class":65,"line":95},[63,418,420],{"emptyLinePlaceholder":419},true,"\n",[63,422,423,425,429,432,434,437,439,442,445],{"class":65,"line":123},[63,424,77],{"class":76},[63,426,428],{"class":427},"spNyl"," function",[63,430,431],{"class":83}," cf",[63,433,88],{"class":91},[63,435,314],{"class":436},"sHdIc",[63,438,102],{"class":91},[63,440,400],{"class":441},"sBMFI",[63,443,444],{"class":91},")",[63,446,131],{"class":91},[63,448,449,452,455,458,461,463,465,467],{"class":65,"line":134},[63,450,451],{"class":427},"  const",[63,453,454],{"class":87}," cloudflare",[63,456,457],{"class":91}," =",[63,459,460],{"class":87}," event",[63,462,317],{"class":91},[63,464,320],{"class":87},[63,466,317],{"class":91},[63,468,469],{"class":87},"cloudflare\n",[63,471,472,475,478,481,483,486],{"class":65,"line":151},[63,473,474],{"class":76},"  if",[63,476,477],{"class":98}," (",[63,479,480],{"class":91},"!",[63,482,325],{"class":87},[63,484,485],{"class":98},") ",[63,487,92],{"class":91},[63,489,490,493,496,498],{"class":65,"line":157},[63,491,492],{"class":76},"    throw",[63,494,495],{"class":83}," createError",[63,497,88],{"class":98},[63,499,92],{"class":91},[63,501,502,505,507,511],{"class":65,"line":207},[63,503,504],{"class":98},"      statusCode",[63,506,102],{"class":91},[63,508,510],{"class":509},"sbssI"," 500",[63,512,120],{"class":91},[63,514,515,518],{"class":65,"line":213},[63,516,517],{"class":98},"      statusMessage",[63,519,520],{"class":91},":\n",[63,522,523,526,529,531],{"class":65,"line":219},[63,524,525],{"class":91},"        '",[63,527,528],{"class":111},"Cloudflare bindings unavailable — is nitro-cloudflare-dev enabled?",[63,530,108],{"class":91},[63,532,120],{"class":91},[63,534,535,538],{"class":65,"line":224},[63,536,537],{"class":91},"    }",[63,539,163],{"class":98},[63,541,542],{"class":65,"line":230},[63,543,544],{"class":91},"  }\n",[63,546,547,550],{"class":65,"line":236},[63,548,549],{"class":76},"  return",[63,551,552],{"class":87}," cloudflare\n",[63,554,555],{"class":65,"line":242},[63,556,262],{"class":91},[16,558,559,560,563],{},"A KV-backed cache for a slow upstream API, using ",[23,561,562],{},"waitUntil"," so the write doesn't block the response:",[54,565,567],{"className":56,"code":566,"language":58,"meta":59,"style":59},"\u002F\u002F server\u002Fapi\u002Frates.get.ts\nexport default defineEventHandler(async (event) => {\n  const { env, context } = cf(event)\n  const key = 'rates:usd'\n\n  const cached = await env.CACHE.get(key, 'json')\n  if (cached) {\n    setHeader(event, 'x-cache', 'HIT')\n    return cached\n  }\n\n  const fresh = await $fetch\u003C{ base: string; rates: Record\u003Cstring, number> }>(\n    'https:\u002F\u002Fapi.example.com\u002Fv1\u002Frates\u002Fusd',\n  )\n\n  context.waitUntil(\n    env.CACHE.put(key, JSON.stringify(fresh), { expirationTtl: 300 }),\n  )\n\n  setHeader(event, 'x-cache', 'MISS')\n  return fresh\n})\n",[23,568,569,574,599,626,642,646,686,699,728,736,740,744,800,812,817,821,832,885,890,895,924,932],{"__ignoreMap":59},[63,570,571],{"class":65,"line":66},[63,572,573],{"class":69},"\u002F\u002F server\u002Fapi\u002Frates.get.ts\n",[63,575,576,578,580,583,585,588,590,592,594,597],{"class":65,"line":73},[63,577,77],{"class":76},[63,579,80],{"class":76},[63,581,582],{"class":83}," defineEventHandler",[63,584,88],{"class":87},[63,586,587],{"class":427},"async",[63,589,477],{"class":91},[63,591,314],{"class":436},[63,593,444],{"class":91},[63,595,596],{"class":427}," =>",[63,598,131],{"class":91},[63,600,601,603,605,608,611,614,616,618,620,622,624],{"class":65,"line":95},[63,602,451],{"class":427},[63,604,397],{"class":91},[63,606,607],{"class":87}," env",[63,609,610],{"class":91},",",[63,612,613],{"class":87}," context",[63,615,403],{"class":91},[63,617,457],{"class":91},[63,619,431],{"class":83},[63,621,88],{"class":98},[63,623,314],{"class":87},[63,625,163],{"class":98},[63,627,628,630,633,635,637,640],{"class":65,"line":123},[63,629,451],{"class":427},[63,631,632],{"class":87}," key",[63,634,457],{"class":91},[63,636,142],{"class":91},[63,638,639],{"class":111},"rates:usd",[63,641,414],{"class":91},[63,643,644],{"class":65,"line":134},[63,645,420],{"emptyLinePlaceholder":419},[63,647,648,650,653,655,658,660,662,665,667,670,672,675,677,679,682,684],{"class":65,"line":151},[63,649,451],{"class":427},[63,651,652],{"class":87}," cached",[63,654,457],{"class":91},[63,656,657],{"class":76}," await",[63,659,607],{"class":87},[63,661,317],{"class":91},[63,663,664],{"class":87},"CACHE",[63,666,317],{"class":91},[63,668,669],{"class":83},"get",[63,671,88],{"class":98},[63,673,674],{"class":87},"key",[63,676,610],{"class":91},[63,678,142],{"class":91},[63,680,681],{"class":111},"json",[63,683,108],{"class":91},[63,685,163],{"class":98},[63,687,688,690,692,695,697],{"class":65,"line":157},[63,689,474],{"class":76},[63,691,477],{"class":98},[63,693,694],{"class":87},"cached",[63,696,485],{"class":98},[63,698,92],{"class":91},[63,700,701,704,706,708,710,712,715,717,719,721,724,726],{"class":65,"line":207},[63,702,703],{"class":83},"    setHeader",[63,705,88],{"class":98},[63,707,314],{"class":87},[63,709,610],{"class":91},[63,711,142],{"class":91},[63,713,714],{"class":111},"x-cache",[63,716,108],{"class":91},[63,718,610],{"class":91},[63,720,142],{"class":91},[63,722,723],{"class":111},"HIT",[63,725,108],{"class":91},[63,727,163],{"class":98},[63,729,730,733],{"class":65,"line":213},[63,731,732],{"class":76},"    return",[63,734,735],{"class":87}," cached\n",[63,737,738],{"class":65,"line":219},[63,739,544],{"class":91},[63,741,742],{"class":65,"line":224},[63,743,420],{"emptyLinePlaceholder":419},[63,745,746,748,751,753,755,758,761,764,766,769,772,775,777,780,783,786,788,791,794,797],{"class":65,"line":230},[63,747,451],{"class":427},[63,749,750],{"class":87}," fresh",[63,752,457],{"class":91},[63,754,657],{"class":76},[63,756,757],{"class":83}," $fetch",[63,759,760],{"class":91},"\u003C{",[63,762,763],{"class":98}," base",[63,765,102],{"class":91},[63,767,768],{"class":441}," string",[63,770,771],{"class":91},";",[63,773,774],{"class":98}," rates",[63,776,102],{"class":91},[63,778,779],{"class":441}," Record",[63,781,782],{"class":91},"\u003C",[63,784,785],{"class":441},"string",[63,787,610],{"class":91},[63,789,790],{"class":441}," number",[63,792,793],{"class":91},">",[63,795,796],{"class":91}," }>",[63,798,799],{"class":98},"(\n",[63,801,802,805,808,810],{"class":65,"line":236},[63,803,804],{"class":91},"    '",[63,806,807],{"class":111},"https:\u002F\u002Fapi.example.com\u002Fv1\u002Frates\u002Fusd",[63,809,108],{"class":91},[63,811,120],{"class":91},[63,813,814],{"class":65,"line":242},[63,815,816],{"class":98},"  )\n",[63,818,819],{"class":65,"line":248},[63,820,420],{"emptyLinePlaceholder":419},[63,822,823,826,828,830],{"class":65,"line":254},[63,824,825],{"class":87},"  context",[63,827,317],{"class":91},[63,829,562],{"class":83},[63,831,799],{"class":98},[63,833,834,837,839,841,843,846,848,850,852,855,857,860,862,865,867,869,871,874,876,879,881,883],{"class":65,"line":259},[63,835,836],{"class":87},"    env",[63,838,317],{"class":91},[63,840,664],{"class":87},[63,842,317],{"class":91},[63,844,845],{"class":83},"put",[63,847,88],{"class":98},[63,849,674],{"class":87},[63,851,610],{"class":91},[63,853,854],{"class":87}," JSON",[63,856,317],{"class":91},[63,858,859],{"class":83},"stringify",[63,861,88],{"class":98},[63,863,864],{"class":87},"fresh",[63,866,444],{"class":98},[63,868,610],{"class":91},[63,870,397],{"class":91},[63,872,873],{"class":98}," expirationTtl",[63,875,102],{"class":91},[63,877,878],{"class":509}," 300",[63,880,403],{"class":91},[63,882,444],{"class":98},[63,884,120],{"class":91},[63,886,888],{"class":65,"line":887},18,[63,889,816],{"class":98},[63,891,893],{"class":65,"line":892},19,[63,894,420],{"emptyLinePlaceholder":419},[63,896,898,901,903,905,907,909,911,913,915,917,920,922],{"class":65,"line":897},20,[63,899,900],{"class":83},"  setHeader",[63,902,88],{"class":98},[63,904,314],{"class":87},[63,906,610],{"class":91},[63,908,142],{"class":91},[63,910,714],{"class":111},[63,912,108],{"class":91},[63,914,610],{"class":91},[63,916,142],{"class":91},[63,918,919],{"class":111},"MISS",[63,921,108],{"class":91},[63,923,163],{"class":98},[63,925,927,929],{"class":65,"line":926},21,[63,928,549],{"class":76},[63,930,931],{"class":87}," fresh\n",[63,933,935,937],{"class":65,"line":934},22,[63,936,160],{"class":91},[63,938,163],{"class":87},[16,940,941],{},"And D1, which uses prepared statements with positional binding:",[54,943,945],{"className":56,"code":944,"language":58,"meta":59,"style":59},"\u002F\u002F server\u002Fapi\u002Fposts.get.ts\ninterface PostRow {\n  id: number\n  title: string\n  created_at: string\n}\n\nexport default defineEventHandler(async (event) => {\n  const { env } = cf(event)\n  const { limit = '20' } = getQuery\u003C{ limit?: string }>(event)\n\n  const { results } = await env.DB.prepare(\n    `select id, title, created_at\n         from posts\n        where published = 1\n        order by created_at desc\n        limit ?1`,\n  )\n    .bind(Math.min(Number(limit) || 20, 100))\n    .all\u003CPostRow>()\n\n  return results\n})\n",[23,946,947,952,962,972,982,991,995,999,1021,1041,1083,1087,1116,1124,1129,1134,1139,1149,1153,1197,1214,1218,1225],{"__ignoreMap":59},[63,948,949],{"class":65,"line":66},[63,950,951],{"class":69},"\u002F\u002F server\u002Fapi\u002Fposts.get.ts\n",[63,953,954,957,960],{"class":65,"line":73},[63,955,956],{"class":427},"interface",[63,958,959],{"class":441}," PostRow",[63,961,131],{"class":91},[63,963,964,967,969],{"class":65,"line":95},[63,965,966],{"class":98},"  id",[63,968,102],{"class":91},[63,970,971],{"class":441}," number\n",[63,973,974,977,979],{"class":65,"line":123},[63,975,976],{"class":98},"  title",[63,978,102],{"class":91},[63,980,981],{"class":441}," string\n",[63,983,984,987,989],{"class":65,"line":134},[63,985,986],{"class":98},"  created_at",[63,988,102],{"class":91},[63,990,981],{"class":441},[63,992,993],{"class":65,"line":151},[63,994,262],{"class":91},[63,996,997],{"class":65,"line":157},[63,998,420],{"emptyLinePlaceholder":419},[63,1000,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019],{"class":65,"line":207},[63,1002,77],{"class":76},[63,1004,80],{"class":76},[63,1006,582],{"class":83},[63,1008,88],{"class":87},[63,1010,587],{"class":427},[63,1012,477],{"class":91},[63,1014,314],{"class":436},[63,1016,444],{"class":91},[63,1018,596],{"class":427},[63,1020,131],{"class":91},[63,1022,1023,1025,1027,1029,1031,1033,1035,1037,1039],{"class":65,"line":213},[63,1024,451],{"class":427},[63,1026,397],{"class":91},[63,1028,607],{"class":87},[63,1030,403],{"class":91},[63,1032,457],{"class":91},[63,1034,431],{"class":83},[63,1036,88],{"class":98},[63,1038,314],{"class":87},[63,1040,163],{"class":98},[63,1042,1043,1045,1047,1050,1052,1054,1057,1059,1061,1063,1066,1068,1070,1073,1075,1077,1079,1081],{"class":65,"line":219},[63,1044,451],{"class":427},[63,1046,397],{"class":91},[63,1048,1049],{"class":87}," limit",[63,1051,457],{"class":91},[63,1053,142],{"class":91},[63,1055,1056],{"class":111},"20",[63,1058,108],{"class":91},[63,1060,403],{"class":91},[63,1062,457],{"class":91},[63,1064,1065],{"class":83}," getQuery",[63,1067,760],{"class":91},[63,1069,1049],{"class":98},[63,1071,1072],{"class":91},"?:",[63,1074,768],{"class":441},[63,1076,796],{"class":91},[63,1078,88],{"class":98},[63,1080,314],{"class":87},[63,1082,163],{"class":98},[63,1084,1085],{"class":65,"line":224},[63,1086,420],{"emptyLinePlaceholder":419},[63,1088,1089,1091,1093,1096,1098,1100,1102,1104,1106,1109,1111,1114],{"class":65,"line":230},[63,1090,451],{"class":427},[63,1092,397],{"class":91},[63,1094,1095],{"class":87}," results",[63,1097,403],{"class":91},[63,1099,457],{"class":91},[63,1101,657],{"class":76},[63,1103,607],{"class":87},[63,1105,317],{"class":91},[63,1107,1108],{"class":87},"DB",[63,1110,317],{"class":91},[63,1112,1113],{"class":83},"prepare",[63,1115,799],{"class":98},[63,1117,1118,1121],{"class":65,"line":236},[63,1119,1120],{"class":91},"    `",[63,1122,1123],{"class":111},"select id, title, created_at\n",[63,1125,1126],{"class":65,"line":242},[63,1127,1128],{"class":111},"         from posts\n",[63,1130,1131],{"class":65,"line":248},[63,1132,1133],{"class":111},"        where published = 1\n",[63,1135,1136],{"class":65,"line":254},[63,1137,1138],{"class":111},"        order by created_at desc\n",[63,1140,1141,1144,1147],{"class":65,"line":259},[63,1142,1143],{"class":111},"        limit ?1",[63,1145,1146],{"class":91},"`",[63,1148,120],{"class":91},[63,1150,1151],{"class":65,"line":887},[63,1152,816],{"class":98},[63,1154,1155,1158,1161,1163,1166,1168,1171,1173,1176,1178,1181,1183,1186,1189,1191,1194],{"class":65,"line":892},[63,1156,1157],{"class":91},"    .",[63,1159,1160],{"class":83},"bind",[63,1162,88],{"class":98},[63,1164,1165],{"class":87},"Math",[63,1167,317],{"class":91},[63,1169,1170],{"class":83},"min",[63,1172,88],{"class":98},[63,1174,1175],{"class":83},"Number",[63,1177,88],{"class":98},[63,1179,1180],{"class":87},"limit",[63,1182,485],{"class":98},[63,1184,1185],{"class":91},"||",[63,1187,1188],{"class":509}," 20",[63,1190,610],{"class":91},[63,1192,1193],{"class":509}," 100",[63,1195,1196],{"class":98},"))\n",[63,1198,1199,1201,1204,1206,1209,1211],{"class":65,"line":897},[63,1200,1157],{"class":91},[63,1202,1203],{"class":83},"all",[63,1205,782],{"class":91},[63,1207,1208],{"class":441},"PostRow",[63,1210,793],{"class":91},[63,1212,1213],{"class":98},"()\n",[63,1215,1216],{"class":65,"line":926},[63,1217,420],{"emptyLinePlaceholder":419},[63,1219,1220,1222],{"class":65,"line":934},[63,1221,549],{"class":76},[63,1223,1224],{"class":87}," results\n",[63,1226,1228,1230],{"class":65,"line":1227},23,[63,1229,160],{"class":91},[63,1231,163],{"class":87},[16,1233,1234,1235,1238,1239,1242],{},"Never build SQL by string concatenation here. D1 supports ",[23,1236,1237],{},".batch()"," for multiple statements in one round trip, which matters more than you'd think: each ",[23,1240,1241],{},"await"," against D1 is a network hop from your Worker's location to the database's primary region.",[11,1244,1246,1247],{"id":1245},"getting-types-instead-of-any","Getting types instead of ",[23,1248,1249],{},"any",[16,1251,1252],{},"Wrangler generates types from your config. Add it to your dev flow:",[54,1254,1258],{"className":1255,"code":1256,"language":1257,"meta":59,"style":59},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx wrangler types --env-interface CloudflareEnv\n","bash",[23,1259,1260],{"__ignoreMap":59},[63,1261,1262,1265,1268,1271,1274],{"class":65,"line":66},[63,1263,1264],{"class":441},"npx",[63,1266,1267],{"class":111}," wrangler",[63,1269,1270],{"class":111}," types",[63,1272,1273],{"class":111}," --env-interface",[63,1275,1276],{"class":111}," CloudflareEnv\n",[16,1278,1279,1280,1283,1284,1287,1288,273,1291,1294,1295,1298],{},"That writes ",[23,1281,1282],{},"worker-configuration.d.ts"," containing a ",[23,1285,1286],{},"CloudflareEnv"," interface with ",[23,1289,1290],{},"CACHE: KVNamespace",[23,1292,1293],{},"DB: D1Database",", and every ",[23,1296,1297],{},"var"," you declared. Wire it into the event context:",[54,1300,1302],{"className":56,"code":1301,"language":58,"meta":59,"style":59},"\u002F\u002F server\u002Ftypes\u002Fh3.d.ts\ndeclare module 'h3' {\n  interface H3EventContext {\n    cloudflare: {\n      request: Request\n      env: CloudflareEnv\n      context: ExecutionContext\n    }\n  }\n}\n\nexport {}\n",[23,1303,1304,1309,1325,1335,1344,1354,1363,1373,1378,1382,1386,1390],{"__ignoreMap":59},[63,1305,1306],{"class":65,"line":66},[63,1307,1308],{"class":69},"\u002F\u002F server\u002Ftypes\u002Fh3.d.ts\n",[63,1310,1311,1314,1317,1319,1321,1323],{"class":65,"line":73},[63,1312,1313],{"class":427},"declare",[63,1315,1316],{"class":427}," module",[63,1318,142],{"class":91},[63,1320,411],{"class":111},[63,1322,108],{"class":91},[63,1324,131],{"class":91},[63,1326,1327,1330,1333],{"class":65,"line":95},[63,1328,1329],{"class":427},"  interface",[63,1331,1332],{"class":441}," H3EventContext",[63,1334,131],{"class":91},[63,1336,1337,1340,1342],{"class":65,"line":123},[63,1338,1339],{"class":98},"    cloudflare",[63,1341,102],{"class":91},[63,1343,131],{"class":91},[63,1345,1346,1349,1351],{"class":65,"line":134},[63,1347,1348],{"class":98},"      request",[63,1350,102],{"class":91},[63,1352,1353],{"class":441}," Request\n",[63,1355,1356,1359,1361],{"class":65,"line":151},[63,1357,1358],{"class":98},"      env",[63,1360,102],{"class":91},[63,1362,1276],{"class":441},[63,1364,1365,1368,1370],{"class":65,"line":157},[63,1366,1367],{"class":98},"      context",[63,1369,102],{"class":91},[63,1371,1372],{"class":441}," ExecutionContext\n",[63,1374,1375],{"class":65,"line":207},[63,1376,1377],{"class":91},"    }\n",[63,1379,1380],{"class":65,"line":213},[63,1381,544],{"class":91},[63,1383,1384],{"class":65,"line":219},[63,1385,262],{"class":91},[63,1387,1388],{"class":65,"line":224},[63,1389,420],{"emptyLinePlaceholder":419},[63,1391,1392,1394],{"class":65,"line":230},[63,1393,77],{"class":76},[63,1395,1396],{"class":91}," {}\n",[16,1398,1399,1400,1403,1404,1407,1408,1411,1412,1415,1416,1419],{},"Now ",[23,1401,1402],{},"env.DB.prepare()"," autocompletes and a typo in a binding name is a build error. Re-run ",[23,1405,1406],{},"wrangler types"," whenever you edit ",[23,1409,1410],{},"wrangler.jsonc"," — a ",[23,1413,1414],{},"postinstall"," script or a ",[23,1417,1418],{},"dev"," script prefix is the usual place.",[11,1421,1423],{"id":1422},"local-development-that-actually-uses-bindings","Local development that actually uses bindings",[16,1425,1426,1428,1429,1432,1433,1435,1436,1439,1440,1443,1444,1448],{},[23,1427,112],{}," calls Wrangler's ",[23,1430,1431],{},"getPlatformProxy()"," and injects real bindings into ",[23,1434,29],{},". They're backed by Miniflare (the same ",[23,1437,1438],{},"workerd"," runtime Cloudflare runs in production) with state persisted under ",[23,1441,1442],{},".wrangler\u002Fstate",". So local KV is a real KV implementation, and local D1 is a real SQLite file — but a ",[1445,1446,1447],"em",{},"different"," one from production.",[16,1450,1451],{},"That means migrations have to be applied twice:",[54,1453,1455],{"className":1255,"code":1454,"language":1257,"meta":59,"style":59},"# local sqlite used by nuxt dev\nnpx wrangler d1 migrations apply app --local\n\n# the actual database\nnpx wrangler d1 migrations apply app --remote\n",[23,1456,1457,1462,1483,1487,1492],{"__ignoreMap":59},[63,1458,1459],{"class":65,"line":66},[63,1460,1461],{"class":69},"# local sqlite used by nuxt dev\n",[63,1463,1464,1466,1468,1471,1474,1477,1480],{"class":65,"line":73},[63,1465,1264],{"class":441},[63,1467,1267],{"class":111},[63,1469,1470],{"class":111}," d1",[63,1472,1473],{"class":111}," migrations",[63,1475,1476],{"class":111}," apply",[63,1478,1479],{"class":111}," app",[63,1481,1482],{"class":111}," --local\n",[63,1484,1485],{"class":65,"line":95},[63,1486,420],{"emptyLinePlaceholder":419},[63,1488,1489],{"class":65,"line":123},[63,1490,1491],{"class":69},"# the actual database\n",[63,1493,1494,1496,1498,1500,1502,1504,1506],{"class":65,"line":134},[63,1495,1264],{"class":441},[63,1497,1267],{"class":111},[63,1499,1470],{"class":111},[63,1501,1473],{"class":111},[63,1503,1476],{"class":111},[63,1505,1479],{"class":111},[63,1507,1508],{"class":111}," --remote\n",[16,1510,1511,1512,1515,1516,1519,1520,1523],{},"Add ",[23,1513,1514],{},".wrangler"," to ",[23,1517,1518],{},".gitignore",". When you need to debug against production data, ",[23,1521,1522],{},"wrangler dev --remote"," runs your built Worker against real remote bindings — useful, and also a very good way to write to production by accident. Keep it to a deliberate, named script.",[16,1525,1526,1527,1529],{},"The trade-off with ",[23,1528,112],{},": it adds a second runtime to your dev process, so startup is slower and a small number of Vite\u002FNitro edge cases behave differently than in Nuxt's default Node dev server. The alternative is skipping it and guarding every binding access, which trades a couple of seconds of boot time for \"works locally, 500s in production.\" Take the slower boot.",[11,1531,1533,1534],{"id":1532},"secrets-vars-and-useruntimeconfig","Secrets, vars, and ",[23,1535,1536],{},"useRuntimeConfig",[16,1538,1539,1540,1542,1543,1546,1547,1550,1551,1554,1555,1558],{},"Non-secret values go in ",[23,1541,1410],{}," under ",[23,1544,1545],{},"\"vars\"",". Secrets go in ",[23,1548,1549],{},"wrangler secret put API_TOKEN"," and never touch the repo. Both land on ",[23,1552,1553],{},"env"," — not on ",[23,1556,1557],{},"process.env",", which barely exists on Workers.",[16,1560,1561,1562,1565],{},"If you use ",[23,1563,1564],{},"runtimeConfig",", this detail will bite you exactly once:",[54,1567,1569],{"className":56,"code":1568,"language":58,"meta":59,"style":59},"\u002F\u002F ✅ env overrides (NUXT_*) are applied\nconst config = useRuntimeConfig(event)\n\n\u002F\u002F ❌ on Workers, returns build-time defaults only\nconst config = useRuntimeConfig()\n",[23,1570,1571,1576,1593,1597,1602],{"__ignoreMap":59},[63,1572,1573],{"class":65,"line":66},[63,1574,1575],{"class":69},"\u002F\u002F ✅ env overrides (NUXT_*) are applied\n",[63,1577,1578,1581,1584,1587,1590],{"class":65,"line":73},[63,1579,1580],{"class":427},"const",[63,1582,1583],{"class":87}," config ",[63,1585,1586],{"class":91},"=",[63,1588,1589],{"class":83}," useRuntimeConfig",[63,1591,1592],{"class":87},"(event)\n",[63,1594,1595],{"class":65,"line":95},[63,1596,420],{"emptyLinePlaceholder":419},[63,1598,1599],{"class":65,"line":123},[63,1600,1601],{"class":69},"\u002F\u002F ❌ on Workers, returns build-time defaults only\n",[63,1603,1604,1606,1608,1610,1612],{"class":65,"line":134},[63,1605,1580],{"class":427},[63,1607,1583],{"class":87},[63,1609,1586],{"class":91},[63,1611,1589],{"class":83},[63,1613,1213],{"class":87},[16,1615,1616,1617,1620],{},"Because the environment is request-scoped, Nitro can only apply ",[23,1618,1619],{},"NUXT_","-prefixed overrides when it can see the event. Always pass it.",[11,1622,1624],{"id":1623},"what-not-to-do","What not to do",[16,1626,1627,1631],{},[1628,1629,1630],"strong",{},"Don't touch bindings at module scope."," This looks fine and is broken:",[54,1633,1635],{"className":56,"code":1634,"language":58,"meta":59,"style":59},"\u002F\u002F server\u002Futils\u002Fdb.ts — DO NOT DO THIS\nconst db = useCloudflareEnv().DB \u002F\u002F no request, no env\n",[23,1636,1637,1642],{"__ignoreMap":59},[63,1638,1639],{"class":65,"line":66},[63,1640,1641],{"class":69},"\u002F\u002F server\u002Futils\u002Fdb.ts — DO NOT DO THIS\n",[63,1643,1644,1646,1649,1651,1654,1657,1659,1662],{"class":65,"line":73},[63,1645,1580],{"class":427},[63,1647,1648],{"class":87}," db ",[63,1650,1586],{"class":91},[63,1652,1653],{"class":83}," useCloudflareEnv",[63,1655,1656],{"class":87},"()",[63,1658,317],{"class":91},[63,1660,1661],{"class":87},"DB ",[63,1663,1664],{"class":69},"\u002F\u002F no request, no env\n",[16,1666,1667],{},"Module initialization happens once per isolate, before any request exists. Pass the event (or the env) down into your data layer instead. If you're using Drizzle, that means constructing the client per request — it's cheap, since there's no connection to open:",[54,1669,1671],{"className":56,"code":1670,"language":58,"meta":59,"style":59},"export function useDb(event: H3Event) {\n  return drizzle(cf(event).env.DB, { schema })\n}\n",[23,1672,1673,1694,1731],{"__ignoreMap":59},[63,1674,1675,1677,1679,1682,1684,1686,1688,1690,1692],{"class":65,"line":66},[63,1676,77],{"class":76},[63,1678,428],{"class":427},[63,1680,1681],{"class":83}," useDb",[63,1683,88],{"class":91},[63,1685,314],{"class":436},[63,1687,102],{"class":91},[63,1689,400],{"class":441},[63,1691,444],{"class":91},[63,1693,131],{"class":91},[63,1695,1696,1698,1701,1703,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1727,1729],{"class":65,"line":73},[63,1697,549],{"class":76},[63,1699,1700],{"class":83}," drizzle",[63,1702,88],{"class":98},[63,1704,1705],{"class":83},"cf",[63,1707,88],{"class":98},[63,1709,314],{"class":87},[63,1711,444],{"class":98},[63,1713,317],{"class":91},[63,1715,1553],{"class":87},[63,1717,317],{"class":91},[63,1719,1108],{"class":87},[63,1721,610],{"class":91},[63,1723,397],{"class":91},[63,1725,1726],{"class":87}," schema",[63,1728,403],{"class":91},[63,1730,163],{"class":98},[63,1732,1733],{"class":65,"line":95},[63,1734,262],{"class":91},[16,1736,1737,1740,1741,1744],{},[1628,1738,1739],{},"Don't cache request-scoped values in module-level variables."," Isolates are reused across requests from different users. A module-scoped ",[23,1742,1743],{},"let currentUser"," is a data leak.",[16,1746,1747,1750],{},[1628,1748,1749],{},"Don't reach for KV as a database."," KV is eventually consistent — a write can take up to about a minute to be visible everywhere, and reads are cached at the edge with a 60-second minimum TTL. It's excellent for config, feature flags, sessions, and rendered-fragment caches. It's wrong for anything read-after-write. That's D1 (relational, one primary region, optional read replicas) or a Durable Object (strongly consistent, single-threaded per instance).",[11,1752,1754],{"id":1753},"a-working-checklist","A working checklist",[1756,1757,1758,1771,1780,1791,1798,1809],"ol",{},[1759,1760,1761,273,1764,1766,1767,1770],"li",{},[23,1762,1763],{},"nitro.preset = 'cloudflare_module'",[23,1765,268],{}," on, ",[23,1768,1769],{},"assets"," binding declared.",[1759,1772,1773,1774,1776,1777,1779],{},"Bindings declared in ",[23,1775,1410],{},"; ",[23,1778,1406],{}," run and committed to the type-check step.",[1759,1781,1782,1784,1785,273,1788,1790],{},[23,1783,112],{}," in ",[23,1786,1787],{},"modules",[23,1789,1514],{}," gitignored.",[1759,1792,1793,1794,1797],{},"One ",[23,1795,1796],{},"cf(event)"," helper; no module-scope binding access.",[1759,1799,1800,1801,1804,1805,1808],{},"Separate ",[23,1802,1803],{},"--local"," and ",[23,1806,1807],{},"--remote"," migration scripts.",[1759,1810,1811,1814],{},[23,1812,1813],{},"useRuntimeConfig(event)"," everywhere, always with the event.",[16,1816,1817,1818,1820],{},"Get those six right and the rest of the Workers platform — queues, cron triggers, Durable Objects, R2 — is just another entry in the same config file and another property on the same ",[23,1819,1553],{}," object.",[1822,1823,1824],"style",{},"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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":59,"searchDepth":73,"depth":73,"links":1826},[1827,1828,1829,1830,1832,1833,1835,1836],{"id":13,"depth":73,"text":14},{"id":33,"depth":73,"text":34},{"id":300,"depth":73,"text":301},{"id":1245,"depth":73,"text":1831},"Getting types instead of any",{"id":1422,"depth":73,"text":1423},{"id":1532,"depth":73,"text":1834},"Secrets, vars, and useRuntimeConfig",{"id":1623,"depth":73,"text":1624},{"id":1753,"depth":73,"text":1754},"2026-08-10","How to wire KV, D1, and R2 bindings into Nuxt 3 server routes on Cloudflare Workers — typed env, local Miniflare dev, and the mistakes that break production.",false,"md",{"src":1842,"alt":1843},"https:\u002F\u002Fimagedelivery.net\u002FcUFrXwQQ4FworoGhQKTMlw\u002F6157f2b7-8c1b-41b3-218c-5692d538fb00\u002Fw=1200,h=630,fit=cover","An isometric illustration of a glowing server block with three colored storage modules plugged into it by streams of light on a dark grid background.",{},"\u002Fblog\u002Fnuxt-cloudflare-workers-bindings",{"title":6,"description":1838},{"loc":1845,"lastmod":1837},"blog\u002Fnuxt-cloudflare-workers-bindings",[1850,325,1851,1852,1853],"nuxt","workers","d1","kv","XQT0fCa6myPx5bnznMOfaqIwmPxigco1QQbMJPUOpms"]