[{"data":1,"prerenderedAt":1668},["ShallowReactive",2],{"page-\u002Fblog\u002Fcloudflare-hyperdrive-to-supabase-zh-CN":3,"\u002Fblog\u002Fcloudflare-hyperdrive-to-supabase-zh-CN-surround":1657},{"id":4,"title":5,"body":6,"description":12,"extension":1642,"lang":1643,"meta":1644,"navigation":236,"path":1646,"seo":1647,"sitemap":1648,"stem":1649,"__hash__":1650,"date":1651,"category":1652,"tags":1653},"article\u002Fblog\u002Fcloudflare-hyperdrive-to-supabase\u002Findex.zh-CN.md","Cloudflare Pages 如何通过 Hyperdrive 连接到 Supabase",{"type":7,"value":8,"toc":1631},"minimark",[9,13,16,19,22,25,29,34,37,40,43,47,58,61,64,68,75,78,122,127,130,136,1340,1345,1586,1589,1598,1601,1604,1613,1616,1624,1627],[10,11,12],"p",{},"起因还要从我正在做的第二个项目说起。",[10,14,15],{},"我一直想要有一款软件：可以记录自己的待办，记录自己的零碎想法，能够很方便的做计划，然后还可以定期复盘。我找了很久，试过滴答清单、flomo、wolai、flowus、obsidian，还有各种习惯打卡的 app。但是每个软件都没有使用很久，总会有一些不符合我习惯的别扭感。最后我决定自己写一个。",[10,17,18],{},"但我是个前端，涉及后端的东西我并不擅长。好在有AI帮忙出技术方案，最终选定用 Supabase 做后端数据库（免费额度够用，还自带用户认证），前端用 Cloudflare Pages 部署（免费额度比 Vercel 大方太多），ORM 用 Drizzle，查询写起来更符合 TypeScript 的风格。",[10,20,21],{},"本地开发一切顺利，两个月后 MVP 版本出炉。我兴冲冲地部署上线，然后 —— 连不上服务器。",[10,23,24],{},"这一卡，就是半个月。",[26,27,28],"h2",{"id":28},"核心问题",[30,31,33],"h3",{"id":32},"_1-cloudflare-pages-的-serverless-function","1. Cloudflare Pages 的 serverless function",[10,35,36],{},"Cloudflare Pages 的 serverless function 是基于 Cloudflare Worker 的。Worker 运行在 Cloudflare 的边缘节点上，和传统的 Node.js 环境有很大不同。Worker 默认情况下是没有 Node.js 的内置模块，默认不保持长连接，每次请求独立。",[10,38,39],{},"Drizzle 通过 pg 模块使用 TCP 协议连接数据库，而 pg 依赖的长连接池机制与 Cloudflare Worker 的运行时环境不兼容 —— Worker 的每个请求都是独立短生命周期的，无法维持持久的 TCP 连接。",[10,41,42],{},"后来折腾出一种方式可以短暂的链接成功：那就是每个请求建立一个单独的链接，然后在请求结束后关闭链接。这样就不会有长连接的问题了。但是这样做的性能非常差，每次请求都要建立和关闭连接，开销很大。随便访问几次页面就会碰到 cloudflare 的 cpu 限制，导致请求被中断。",[30,44,46],{"id":45},"_2-什么是-hyperdrive","2. 什么是 Hyperdrive",[48,49,54],"pre",{"className":50,"code":52,"language":53},[51],"language-text","Cloudflare Hyperdrive 是一项专为 Cloudflare Workers 设计的服务，旨在大幅加速其访问传统数据库（如 PostgreSQL 和 MySQL）的速度。它通过在全球边缘网络建立连接池和智能缓存，大幅度提升数据库的访问效率。\n\n- 显著降低延迟：消除冗余网络往返，可为每次查询节省数百毫秒\n- 减少数据库负载：通过连接复用和查询缓存，有效降低源数据库的 CPU 和连接数压力\n- 无需修改代码：它兼容现有的数据库驱动和库，你只需更换连接字符串，无需重写查询\n","text",[55,56,52],"code",{"__ignoreMap":57},"",[10,59,60],{},"看来事件出现了转机。于是我开始折腾基于 Hyperdrive 的链接方式。这需要我即保留本地开发环境原本的链接方式，在线上环境又要使用 Hyperdrive 的链接方式。",[26,62,63],{"id":63},"最终答案",[30,65,67],{"id":66},"cloudflare-的配置","Cloudflare 的配置",[10,69,70,71,74],{},"配置有几种方式可以选择。可以把相关的变量配置写到代码中，比如用 ",[55,72,73],{},"wrangler.toml"," 配置文件。如果本地有这个文件，部署到线上后，cloudflare pages 会自动读取这个文件中的配置。好处就是要修改什么配置不用去管理后台了，直接代码里操作。反正每次修改配置，都要重新部署一遍的。缺点也很明显：如果都放这里的话，数据库的密码就暴漏在代码里了。另外一种就是在 cloudflare pages 的后台配置环境变量，这样不会暴露数据库密码。",[10,76,77],{},"首先，要创建一个 Hyperdrive binding。这个 binding 是一个 cloudflare pages 的资源，绑定了一个数据库的连接。具体创建方式我就不赘述了，网上很多资料。创建好后，要去自己的 page 项目中，打开 settings。",[79,80,81,89,99],"ul",{},[82,83,84,85],"li",{},"在 Bindings 的栏目里进行绑定刚才创建的 Hyperdrive。",[86,87,88],"strong",{},"注意，绑定时，设定的 name 名称要跟代码里的名称一致。",[82,90,91,92,95,96],{},"在 Compatibility Flags 里，添加 ",[55,93,94],{},"nodejs_compat"," 和 ",[55,97,98],{},"no_nodejs_compat_v2",[82,100,101,102,105,106,109,110,95,113,116,117],{},"此时 ",[55,103,104],{},"Variables and secrets"," 原来的环境变量会清空，需要重新添加 ",[55,107,108],{},"secrets","。主要是：",[55,111,112],{},"SUPABASE_KEY",[55,114,115],{},"SUPABASE_URL","。",[79,118,119],{},[82,120,121],{},"因为前面开启了那两个 flag 后，要求所有的环境变量都要是 secret 才能使用。secrets 也是环境变量，只是值不再能查看，只会在服务端运行时可以获取到，安全性更高而已。",[10,123,124],{},[86,125,126],{},"PS：Cloudflare pages 默认是支持 production 和 preview 两个环境的，想要两边都使用，需要分别配置。",[30,128,129],{"id":129},"代码",[10,131,132,135],{},[55,133,134],{},"client.ts"," drizzle 连接数据库的入口文件",[48,137,141],{"className":138,"code":139,"language":140,"meta":57,"style":57},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { drizzle, type PostgresJsDatabase } from \"drizzle-orm\u002Fpostgres-js\";\nimport postgres from \"postgres\";\nimport { tables } from \".\u002Fschema\";\n\ntype BaseCloudflareHyperdriveBinding = {\n  connectionString: string;\n};\n\ntype BaseCloudflareRuntimeEnv = Record\u003Cstring, unknown>;\ntype BaseHyperdriveGlobalScope = typeof globalThis & {\n  __DAYFLOWY_HYPERDRIVE_CONNECTION_STRING__?: string;\n};\n\nexport type DrizzleDb = PostgresJsDatabase\u003Ctypeof tables>;\n\n\u002F\u002F 全局变量的引用\nconst hyperdriveGlobalScope = globalThis as BaseHyperdriveGlobalScope;\n\nconst resolveCloudflareBindingConnectionString = (): string | undefined => {\n  \u002F\u002F 从全局变量上取 hyperdrive 的连接字符串\n  const connectionString =\n    hyperdriveGlobalScope.__DAYFLOWY_HYPERDRIVE_CONNECTION_STRING__;\n\n  return typeof connectionString === \"string\" &&\n    connectionString.trim().length > 0\n    ? connectionString.trim()\n    : undefined;\n};\n\n\u002F\u002F 获取最终的数据库链接地址\nconst resolveDatabaseUrl = (): string | undefined => {\n  return (resolveCloudflareBindingConnectionString() ||\n    process.env.DATABASE_URL ||\n    process.env.SUPABASE_DB_URL) as string | undefined;\n};\n\n\u002F\u002F drizzle 链接数据库的函数，业务方可以通过返回的实例来操作数据库\nexport function useDb(): DrizzleDb {\n  const databaseUrl = resolveDatabaseUrl();\n\n  if (!databaseUrl) {\n    throw new Error(\n      \"Missing database connection. Configure Hyperdrive binding or DATABASE_URL\u002FSUPABASE_DB_URL.\",\n    );\n  }\n\n  if (\n    !databaseUrl.startsWith(\"postgres:\u002F\u002F\") &&\n    !databaseUrl.startsWith(\"postgresql:\u002F\u002F\")\n  ) {\n    throw new Error(\n      \"Invalid DATABASE_URL\u002FSUPABASE_DB_URL for Drizzle client. Expected postgres:\u002F\u002F... or postgresql:\u002F\u002F...\",\n    );\n  }\n\n  const sql = postgres(databaseUrl, {\n    prepare: false,\n  });\n\n  return drizzle(sql, {\n    schema: tables,\n    casing: \"snake_case\",\n  });\n}\n\n\u002F\u002F 最关键的是这里，从环境变量中获取 hyperdrive 的连接字符串\nexport const cacheHyperdriveConnectionString = (\n  env: BaseCloudflareRuntimeEnv | undefined,\n): void => {\n  \u002F\u002F env 的入参是在线上环境中 cloudflare pages 的 serverless function 的环境变量对象，里面包含了 hyperdrive 的 binding\n\n  if (!env) {\n    return;\n  }\n\n  \u002F\u002F 这里的 HYPERDRIVE_BINDING_NAME 是在 cloudflare pages 的环境变量中配置的，指向 hyperdrive 的 binding 名称\n  const bindingName = (\n    process.env.HYPERDRIVE_BINDING_NAME || \"HYPERDRIVE\"\n  ).trim();\n\n  \u002F\u002F 根据配置的名称，从线上环境变量里获取 hyperdrive 的 binding 对象\n  const binding = env[bindingName] as\n    | BaseCloudflareHyperdriveBinding\n    | undefined;\n\n  \u002F\u002F 从 binding 对象中获取连接字符串 - hyperdrive 连接数据库的连接\n  const connectionString = binding?.connectionString;\n\n  if (\n    typeof connectionString !== \"string\" ||\n    connectionString.trim().length === 0\n  ) {\n    return;\n  }\n\n  \u002F\u002F 将连接字符串缓存到全局变量中，供后续使用\n  hyperdriveGlobalScope.__DAYFLOWY_HYPERDRIVE_CONNECTION_STRING__ =\n    connectionString.trim();\n};\n","ts",[55,142,143,188,208,231,238,255,270,276,281,308,329,342,347,352,374,379,386,407,412,438,444,456,470,475,497,524,539,548,553,558,564,586,603,622,649,654,659,665,683,700,705,724,739,753,761,767,772,780,808,831,839,850,862,869,874,879,900,914,925,930,946,958,975,984,990,995,1001,1016,1033,1047,1053,1058,1073,1081,1086,1091,1097,1109,1134,1148,1153,1159,1184,1193,1200,1205,1211,1230,1235,1242,1261,1280,1287,1294,1299,1304,1310,1322,1335],{"__ignoreMap":57},[144,145,148,152,156,160,163,166,169,172,175,178,182,185],"span",{"class":146,"line":147},"line",1,[144,149,151],{"class":150},"s7zQu","import",[144,153,155],{"class":154},"sMK4o"," {",[144,157,159],{"class":158},"sTEyZ"," drizzle",[144,161,162],{"class":154},",",[144,164,165],{"class":150}," type",[144,167,168],{"class":158}," PostgresJsDatabase",[144,170,171],{"class":154}," }",[144,173,174],{"class":150}," from",[144,176,177],{"class":154}," \"",[144,179,181],{"class":180},"sfazB","drizzle-orm\u002Fpostgres-js",[144,183,184],{"class":154},"\"",[144,186,187],{"class":154},";\n",[144,189,191,193,196,199,201,204,206],{"class":146,"line":190},2,[144,192,151],{"class":150},[144,194,195],{"class":158}," postgres ",[144,197,198],{"class":150},"from",[144,200,177],{"class":154},[144,202,203],{"class":180},"postgres",[144,205,184],{"class":154},[144,207,187],{"class":154},[144,209,211,213,215,218,220,222,224,227,229],{"class":146,"line":210},3,[144,212,151],{"class":150},[144,214,155],{"class":154},[144,216,217],{"class":158}," tables",[144,219,171],{"class":154},[144,221,174],{"class":150},[144,223,177],{"class":154},[144,225,226],{"class":180},".\u002Fschema",[144,228,184],{"class":154},[144,230,187],{"class":154},[144,232,234],{"class":146,"line":233},4,[144,235,237],{"emptyLinePlaceholder":236},true,"\n",[144,239,241,245,249,252],{"class":146,"line":240},5,[144,242,244],{"class":243},"spNyl","type",[144,246,248],{"class":247},"sBMFI"," BaseCloudflareHyperdriveBinding",[144,250,251],{"class":154}," =",[144,253,254],{"class":154}," {\n",[144,256,258,262,265,268],{"class":146,"line":257},6,[144,259,261],{"class":260},"swJcz","  connectionString",[144,263,264],{"class":154},":",[144,266,267],{"class":247}," string",[144,269,187],{"class":154},[144,271,273],{"class":146,"line":272},7,[144,274,275],{"class":154},"};\n",[144,277,279],{"class":146,"line":278},8,[144,280,237],{"emptyLinePlaceholder":236},[144,282,284,286,289,291,294,297,300,302,305],{"class":146,"line":283},9,[144,285,244],{"class":243},[144,287,288],{"class":247}," BaseCloudflareRuntimeEnv",[144,290,251],{"class":154},[144,292,293],{"class":247}," Record",[144,295,296],{"class":154},"\u003C",[144,298,299],{"class":247},"string",[144,301,162],{"class":154},[144,303,304],{"class":247}," unknown",[144,306,307],{"class":154},">;\n",[144,309,311,313,316,318,321,324,327],{"class":146,"line":310},10,[144,312,244],{"class":243},[144,314,315],{"class":247}," BaseHyperdriveGlobalScope",[144,317,251],{"class":154},[144,319,320],{"class":154}," typeof",[144,322,323],{"class":158}," globalThis ",[144,325,326],{"class":154},"&",[144,328,254],{"class":154},[144,330,332,335,338,340],{"class":146,"line":331},11,[144,333,334],{"class":260},"  __DAYFLOWY_HYPERDRIVE_CONNECTION_STRING__",[144,336,337],{"class":154},"?:",[144,339,267],{"class":247},[144,341,187],{"class":154},[144,343,345],{"class":146,"line":344},12,[144,346,275],{"class":154},[144,348,350],{"class":146,"line":349},13,[144,351,237],{"emptyLinePlaceholder":236},[144,353,355,358,360,363,365,367,370,372],{"class":146,"line":354},14,[144,356,357],{"class":150},"export",[144,359,165],{"class":243},[144,361,362],{"class":247}," DrizzleDb",[144,364,251],{"class":154},[144,366,168],{"class":247},[144,368,369],{"class":154},"\u003Ctypeof",[144,371,217],{"class":158},[144,373,307],{"class":154},[144,375,377],{"class":146,"line":376},15,[144,378,237],{"emptyLinePlaceholder":236},[144,380,382],{"class":146,"line":381},16,[144,383,385],{"class":384},"sHwdD","\u002F\u002F 全局变量的引用\n",[144,387,389,392,395,398,400,403,405],{"class":146,"line":388},17,[144,390,391],{"class":243},"const",[144,393,394],{"class":158}," hyperdriveGlobalScope ",[144,396,397],{"class":154},"=",[144,399,323],{"class":158},[144,401,402],{"class":150},"as",[144,404,315],{"class":247},[144,406,187],{"class":154},[144,408,410],{"class":146,"line":409},18,[144,411,237],{"emptyLinePlaceholder":236},[144,413,415,417,420,422,425,427,430,433,436],{"class":146,"line":414},19,[144,416,391],{"class":243},[144,418,419],{"class":158}," resolveCloudflareBindingConnectionString ",[144,421,397],{"class":154},[144,423,424],{"class":154}," ():",[144,426,267],{"class":247},[144,428,429],{"class":154}," |",[144,431,432],{"class":247}," undefined",[144,434,435],{"class":243}," =>",[144,437,254],{"class":154},[144,439,441],{"class":146,"line":440},20,[144,442,443],{"class":384},"  \u002F\u002F 从全局变量上取 hyperdrive 的连接字符串\n",[144,445,447,450,453],{"class":146,"line":446},21,[144,448,449],{"class":243},"  const",[144,451,452],{"class":158}," connectionString",[144,454,455],{"class":154}," =\n",[144,457,459,462,465,468],{"class":146,"line":458},22,[144,460,461],{"class":158},"    hyperdriveGlobalScope",[144,463,464],{"class":154},".",[144,466,467],{"class":158},"__DAYFLOWY_HYPERDRIVE_CONNECTION_STRING__",[144,469,187],{"class":154},[144,471,473],{"class":146,"line":472},23,[144,474,237],{"emptyLinePlaceholder":236},[144,476,478,481,483,485,488,490,492,494],{"class":146,"line":477},24,[144,479,480],{"class":150},"  return",[144,482,320],{"class":154},[144,484,452],{"class":158},[144,486,487],{"class":154}," ===",[144,489,177],{"class":154},[144,491,299],{"class":180},[144,493,184],{"class":154},[144,495,496],{"class":154}," &&\n",[144,498,500,503,505,509,512,514,517,520],{"class":146,"line":499},25,[144,501,502],{"class":158},"    connectionString",[144,504,464],{"class":154},[144,506,508],{"class":507},"s2Zo4","trim",[144,510,511],{"class":260},"()",[144,513,464],{"class":154},[144,515,516],{"class":158},"length",[144,518,519],{"class":154}," >",[144,521,523],{"class":522},"sbssI"," 0\n",[144,525,527,530,532,534,536],{"class":146,"line":526},26,[144,528,529],{"class":154},"    ?",[144,531,452],{"class":158},[144,533,464],{"class":154},[144,535,508],{"class":507},[144,537,538],{"class":260},"()\n",[144,540,542,545],{"class":146,"line":541},27,[144,543,544],{"class":154},"    :",[144,546,547],{"class":154}," undefined;\n",[144,549,551],{"class":146,"line":550},28,[144,552,275],{"class":154},[144,554,556],{"class":146,"line":555},29,[144,557,237],{"emptyLinePlaceholder":236},[144,559,561],{"class":146,"line":560},30,[144,562,563],{"class":384},"\u002F\u002F 获取最终的数据库链接地址\n",[144,565,567,569,572,574,576,578,580,582,584],{"class":146,"line":566},31,[144,568,391],{"class":243},[144,570,571],{"class":158}," resolveDatabaseUrl ",[144,573,397],{"class":154},[144,575,424],{"class":154},[144,577,267],{"class":247},[144,579,429],{"class":154},[144,581,432],{"class":247},[144,583,435],{"class":243},[144,585,254],{"class":154},[144,587,589,591,594,597,600],{"class":146,"line":588},32,[144,590,480],{"class":150},[144,592,593],{"class":260}," (",[144,595,596],{"class":507},"resolveCloudflareBindingConnectionString",[144,598,599],{"class":260},"() ",[144,601,602],{"class":154},"||\n",[144,604,606,609,611,614,616,619],{"class":146,"line":605},33,[144,607,608],{"class":158},"    process",[144,610,464],{"class":154},[144,612,613],{"class":158},"env",[144,615,464],{"class":154},[144,617,618],{"class":158},"DATABASE_URL",[144,620,621],{"class":154}," ||\n",[144,623,625,627,629,631,633,636,639,641,643,645,647],{"class":146,"line":624},34,[144,626,608],{"class":158},[144,628,464],{"class":154},[144,630,613],{"class":158},[144,632,464],{"class":154},[144,634,635],{"class":158},"SUPABASE_DB_URL",[144,637,638],{"class":260},") ",[144,640,402],{"class":150},[144,642,267],{"class":247},[144,644,429],{"class":154},[144,646,432],{"class":247},[144,648,187],{"class":154},[144,650,652],{"class":146,"line":651},35,[144,653,275],{"class":154},[144,655,657],{"class":146,"line":656},36,[144,658,237],{"emptyLinePlaceholder":236},[144,660,662],{"class":146,"line":661},37,[144,663,664],{"class":384},"\u002F\u002F drizzle 链接数据库的函数，业务方可以通过返回的实例来操作数据库\n",[144,666,668,670,673,676,679,681],{"class":146,"line":667},38,[144,669,357],{"class":150},[144,671,672],{"class":243}," function",[144,674,675],{"class":507}," useDb",[144,677,678],{"class":154},"():",[144,680,362],{"class":247},[144,682,254],{"class":154},[144,684,686,688,691,693,696,698],{"class":146,"line":685},39,[144,687,449],{"class":243},[144,689,690],{"class":158}," databaseUrl",[144,692,251],{"class":154},[144,694,695],{"class":507}," resolveDatabaseUrl",[144,697,511],{"class":260},[144,699,187],{"class":154},[144,701,703],{"class":146,"line":702},40,[144,704,237],{"emptyLinePlaceholder":236},[144,706,708,711,713,716,719,721],{"class":146,"line":707},41,[144,709,710],{"class":150},"  if",[144,712,593],{"class":260},[144,714,715],{"class":154},"!",[144,717,718],{"class":158},"databaseUrl",[144,720,638],{"class":260},[144,722,723],{"class":154},"{\n",[144,725,727,730,733,736],{"class":146,"line":726},42,[144,728,729],{"class":150},"    throw",[144,731,732],{"class":154}," new",[144,734,735],{"class":507}," Error",[144,737,738],{"class":260},"(\n",[144,740,742,745,748,750],{"class":146,"line":741},43,[144,743,744],{"class":154},"      \"",[144,746,747],{"class":180},"Missing database connection. Configure Hyperdrive binding or DATABASE_URL\u002FSUPABASE_DB_URL.",[144,749,184],{"class":154},[144,751,752],{"class":154},",\n",[144,754,756,759],{"class":146,"line":755},44,[144,757,758],{"class":260},"    )",[144,760,187],{"class":154},[144,762,764],{"class":146,"line":763},45,[144,765,766],{"class":154},"  }\n",[144,768,770],{"class":146,"line":769},46,[144,771,237],{"emptyLinePlaceholder":236},[144,773,775,777],{"class":146,"line":774},47,[144,776,710],{"class":150},[144,778,779],{"class":260}," (\n",[144,781,783,786,788,790,793,796,798,801,803,805],{"class":146,"line":782},48,[144,784,785],{"class":154},"    !",[144,787,718],{"class":158},[144,789,464],{"class":154},[144,791,792],{"class":507},"startsWith",[144,794,795],{"class":260},"(",[144,797,184],{"class":154},[144,799,800],{"class":180},"postgres:\u002F\u002F",[144,802,184],{"class":154},[144,804,638],{"class":260},[144,806,807],{"class":154},"&&\n",[144,809,811,813,815,817,819,821,823,826,828],{"class":146,"line":810},49,[144,812,785],{"class":154},[144,814,718],{"class":158},[144,816,464],{"class":154},[144,818,792],{"class":507},[144,820,795],{"class":260},[144,822,184],{"class":154},[144,824,825],{"class":180},"postgresql:\u002F\u002F",[144,827,184],{"class":154},[144,829,830],{"class":260},")\n",[144,832,834,837],{"class":146,"line":833},50,[144,835,836],{"class":260},"  ) ",[144,838,723],{"class":154},[144,840,842,844,846,848],{"class":146,"line":841},51,[144,843,729],{"class":150},[144,845,732],{"class":154},[144,847,735],{"class":507},[144,849,738],{"class":260},[144,851,853,855,858,860],{"class":146,"line":852},52,[144,854,744],{"class":154},[144,856,857],{"class":180},"Invalid DATABASE_URL\u002FSUPABASE_DB_URL for Drizzle client. Expected postgres:\u002F\u002F... or postgresql:\u002F\u002F...",[144,859,184],{"class":154},[144,861,752],{"class":154},[144,863,865,867],{"class":146,"line":864},53,[144,866,758],{"class":260},[144,868,187],{"class":154},[144,870,872],{"class":146,"line":871},54,[144,873,766],{"class":154},[144,875,877],{"class":146,"line":876},55,[144,878,237],{"emptyLinePlaceholder":236},[144,880,882,884,887,889,892,894,896,898],{"class":146,"line":881},56,[144,883,449],{"class":243},[144,885,886],{"class":158}," sql",[144,888,251],{"class":154},[144,890,891],{"class":507}," postgres",[144,893,795],{"class":260},[144,895,718],{"class":158},[144,897,162],{"class":154},[144,899,254],{"class":154},[144,901,903,906,908,912],{"class":146,"line":902},57,[144,904,905],{"class":260},"    prepare",[144,907,264],{"class":154},[144,909,911],{"class":910},"sfNiH"," false",[144,913,752],{"class":154},[144,915,917,920,923],{"class":146,"line":916},58,[144,918,919],{"class":154},"  }",[144,921,922],{"class":260},")",[144,924,187],{"class":154},[144,926,928],{"class":146,"line":927},59,[144,929,237],{"emptyLinePlaceholder":236},[144,931,933,935,937,939,942,944],{"class":146,"line":932},60,[144,934,480],{"class":150},[144,936,159],{"class":507},[144,938,795],{"class":260},[144,940,941],{"class":158},"sql",[144,943,162],{"class":154},[144,945,254],{"class":154},[144,947,949,952,954,956],{"class":146,"line":948},61,[144,950,951],{"class":260},"    schema",[144,953,264],{"class":154},[144,955,217],{"class":158},[144,957,752],{"class":154},[144,959,961,964,966,968,971,973],{"class":146,"line":960},62,[144,962,963],{"class":260},"    casing",[144,965,264],{"class":154},[144,967,177],{"class":154},[144,969,970],{"class":180},"snake_case",[144,972,184],{"class":154},[144,974,752],{"class":154},[144,976,978,980,982],{"class":146,"line":977},63,[144,979,919],{"class":154},[144,981,922],{"class":260},[144,983,187],{"class":154},[144,985,987],{"class":146,"line":986},64,[144,988,989],{"class":154},"}\n",[144,991,993],{"class":146,"line":992},65,[144,994,237],{"emptyLinePlaceholder":236},[144,996,998],{"class":146,"line":997},66,[144,999,1000],{"class":384},"\u002F\u002F 最关键的是这里，从环境变量中获取 hyperdrive 的连接字符串\n",[144,1002,1004,1006,1009,1012,1014],{"class":146,"line":1003},67,[144,1005,357],{"class":150},[144,1007,1008],{"class":243}," const",[144,1010,1011],{"class":158}," cacheHyperdriveConnectionString ",[144,1013,397],{"class":154},[144,1015,779],{"class":158},[144,1017,1019,1023,1025,1027,1029,1031],{"class":146,"line":1018},68,[144,1020,1022],{"class":1021},"sHdIc","  env",[144,1024,264],{"class":154},[144,1026,288],{"class":247},[144,1028,429],{"class":154},[144,1030,432],{"class":247},[144,1032,752],{"class":154},[144,1034,1036,1038,1040,1043,1045],{"class":146,"line":1035},69,[144,1037,922],{"class":158},[144,1039,264],{"class":154},[144,1041,1042],{"class":247}," void",[144,1044,435],{"class":243},[144,1046,254],{"class":154},[144,1048,1050],{"class":146,"line":1049},70,[144,1051,1052],{"class":384},"  \u002F\u002F env 的入参是在线上环境中 cloudflare pages 的 serverless function 的环境变量对象，里面包含了 hyperdrive 的 binding\n",[144,1054,1056],{"class":146,"line":1055},71,[144,1057,237],{"emptyLinePlaceholder":236},[144,1059,1061,1063,1065,1067,1069,1071],{"class":146,"line":1060},72,[144,1062,710],{"class":150},[144,1064,593],{"class":260},[144,1066,715],{"class":154},[144,1068,613],{"class":158},[144,1070,638],{"class":260},[144,1072,723],{"class":154},[144,1074,1076,1079],{"class":146,"line":1075},73,[144,1077,1078],{"class":150},"    return",[144,1080,187],{"class":154},[144,1082,1084],{"class":146,"line":1083},74,[144,1085,766],{"class":154},[144,1087,1089],{"class":146,"line":1088},75,[144,1090,237],{"emptyLinePlaceholder":236},[144,1092,1094],{"class":146,"line":1093},76,[144,1095,1096],{"class":384},"  \u002F\u002F 这里的 HYPERDRIVE_BINDING_NAME 是在 cloudflare pages 的环境变量中配置的，指向 hyperdrive 的 binding 名称\n",[144,1098,1100,1102,1105,1107],{"class":146,"line":1099},77,[144,1101,449],{"class":243},[144,1103,1104],{"class":158}," bindingName",[144,1106,251],{"class":154},[144,1108,779],{"class":260},[144,1110,1112,1114,1116,1118,1120,1123,1126,1128,1131],{"class":146,"line":1111},78,[144,1113,608],{"class":158},[144,1115,464],{"class":154},[144,1117,613],{"class":158},[144,1119,464],{"class":154},[144,1121,1122],{"class":158},"HYPERDRIVE_BINDING_NAME",[144,1124,1125],{"class":154}," ||",[144,1127,177],{"class":154},[144,1129,1130],{"class":180},"HYPERDRIVE",[144,1132,1133],{"class":154},"\"\n",[144,1135,1137,1140,1142,1144,1146],{"class":146,"line":1136},79,[144,1138,1139],{"class":260},"  )",[144,1141,464],{"class":154},[144,1143,508],{"class":507},[144,1145,511],{"class":260},[144,1147,187],{"class":154},[144,1149,1151],{"class":146,"line":1150},80,[144,1152,237],{"emptyLinePlaceholder":236},[144,1154,1156],{"class":146,"line":1155},81,[144,1157,1158],{"class":384},"  \u002F\u002F 根据配置的名称，从线上环境变量里获取 hyperdrive 的 binding 对象\n",[144,1160,1162,1164,1167,1169,1172,1175,1178,1181],{"class":146,"line":1161},82,[144,1163,449],{"class":243},[144,1165,1166],{"class":158}," binding",[144,1168,251],{"class":154},[144,1170,1171],{"class":158}," env",[144,1173,1174],{"class":260},"[",[144,1176,1177],{"class":158},"bindingName",[144,1179,1180],{"class":260},"] ",[144,1182,1183],{"class":150},"as\n",[144,1185,1187,1190],{"class":146,"line":1186},83,[144,1188,1189],{"class":154},"    |",[144,1191,1192],{"class":158}," BaseCloudflareHyperdriveBinding\n",[144,1194,1196,1198],{"class":146,"line":1195},84,[144,1197,1189],{"class":154},[144,1199,547],{"class":154},[144,1201,1203],{"class":146,"line":1202},85,[144,1204,237],{"emptyLinePlaceholder":236},[144,1206,1208],{"class":146,"line":1207},86,[144,1209,1210],{"class":384},"  \u002F\u002F 从 binding 对象中获取连接字符串 - hyperdrive 连接数据库的连接\n",[144,1212,1214,1216,1218,1220,1222,1225,1228],{"class":146,"line":1213},87,[144,1215,449],{"class":243},[144,1217,452],{"class":158},[144,1219,251],{"class":154},[144,1221,1166],{"class":158},[144,1223,1224],{"class":154},"?.",[144,1226,1227],{"class":158},"connectionString",[144,1229,187],{"class":154},[144,1231,1233],{"class":146,"line":1232},88,[144,1234,237],{"emptyLinePlaceholder":236},[144,1236,1238,1240],{"class":146,"line":1237},89,[144,1239,710],{"class":150},[144,1241,779],{"class":260},[144,1243,1245,1248,1250,1253,1255,1257,1259],{"class":146,"line":1244},90,[144,1246,1247],{"class":154},"    typeof",[144,1249,452],{"class":158},[144,1251,1252],{"class":154}," !==",[144,1254,177],{"class":154},[144,1256,299],{"class":180},[144,1258,184],{"class":154},[144,1260,621],{"class":154},[144,1262,1264,1266,1268,1270,1272,1274,1276,1278],{"class":146,"line":1263},91,[144,1265,502],{"class":158},[144,1267,464],{"class":154},[144,1269,508],{"class":507},[144,1271,511],{"class":260},[144,1273,464],{"class":154},[144,1275,516],{"class":158},[144,1277,487],{"class":154},[144,1279,523],{"class":522},[144,1281,1283,1285],{"class":146,"line":1282},92,[144,1284,836],{"class":260},[144,1286,723],{"class":154},[144,1288,1290,1292],{"class":146,"line":1289},93,[144,1291,1078],{"class":150},[144,1293,187],{"class":154},[144,1295,1297],{"class":146,"line":1296},94,[144,1298,766],{"class":154},[144,1300,1302],{"class":146,"line":1301},95,[144,1303,237],{"emptyLinePlaceholder":236},[144,1305,1307],{"class":146,"line":1306},96,[144,1308,1309],{"class":384},"  \u002F\u002F 将连接字符串缓存到全局变量中，供后续使用\n",[144,1311,1313,1316,1318,1320],{"class":146,"line":1312},97,[144,1314,1315],{"class":158},"  hyperdriveGlobalScope",[144,1317,464],{"class":154},[144,1319,467],{"class":158},[144,1321,455],{"class":154},[144,1323,1325,1327,1329,1331,1333],{"class":146,"line":1324},98,[144,1326,502],{"class":158},[144,1328,464],{"class":154},[144,1330,508],{"class":507},[144,1332,511],{"class":260},[144,1334,187],{"class":154},[144,1336,1338],{"class":146,"line":1337},99,[144,1339,275],{"class":154},[10,1341,1342],{},[55,1343,1344],{},"server\u002Fmiddlewares\u002Fhyperdirve-context.ts",[48,1346,1348],{"className":138,"code":1347,"language":140,"meta":57,"style":57},"\u002F\u002F 在服务端增加一个中间件，每次请求时，都获取线上的环境变量，并缓存 hyperdrive 的连接字符串到全局变量中，供 drizzle 链接数据库时使用\nimport { cacheHyperdriveConnectionString } from \"PATH_TO_CLIENT\u002Fclient.ts\";\n\nexport default defineEventHandler((event) => {\n  const contextEnv = (\n    event.context.cloudflare as { env?: Record\u003Cstring, unknown> } | undefined\n  )?.env;\n  const runtimeEnv = (\n    event.req as {\n      runtime?: { cloudflare?: { env?: Record\u003Cstring, unknown> } };\n    }\n  ).runtime?.cloudflare?.env;\n\n  cacheHyperdriveConnectionString(contextEnv ?? runtimeEnv);\n});\n\n",[55,1349,1350,1355,1377,1381,1404,1415,1459,1469,1480,1493,1530,1535,1554,1558,1577],{"__ignoreMap":57},[144,1351,1352],{"class":146,"line":147},[144,1353,1354],{"class":384},"\u002F\u002F 在服务端增加一个中间件，每次请求时，都获取线上的环境变量，并缓存 hyperdrive 的连接字符串到全局变量中，供 drizzle 链接数据库时使用\n",[144,1356,1357,1359,1361,1364,1366,1368,1370,1373,1375],{"class":146,"line":190},[144,1358,151],{"class":150},[144,1360,155],{"class":154},[144,1362,1363],{"class":158}," cacheHyperdriveConnectionString",[144,1365,171],{"class":154},[144,1367,174],{"class":150},[144,1369,177],{"class":154},[144,1371,1372],{"class":180},"PATH_TO_CLIENT\u002Fclient.ts",[144,1374,184],{"class":154},[144,1376,187],{"class":154},[144,1378,1379],{"class":146,"line":210},[144,1380,237],{"emptyLinePlaceholder":236},[144,1382,1383,1385,1388,1391,1393,1395,1398,1400,1402],{"class":146,"line":233},[144,1384,357],{"class":150},[144,1386,1387],{"class":150}," default",[144,1389,1390],{"class":507}," defineEventHandler",[144,1392,795],{"class":158},[144,1394,795],{"class":154},[144,1396,1397],{"class":1021},"event",[144,1399,922],{"class":154},[144,1401,435],{"class":243},[144,1403,254],{"class":154},[144,1405,1406,1408,1411,1413],{"class":146,"line":240},[144,1407,449],{"class":243},[144,1409,1410],{"class":158}," contextEnv",[144,1412,251],{"class":154},[144,1414,779],{"class":260},[144,1416,1417,1420,1422,1425,1427,1430,1433,1435,1437,1439,1441,1443,1445,1447,1449,1452,1454,1456],{"class":146,"line":257},[144,1418,1419],{"class":158},"    event",[144,1421,464],{"class":154},[144,1423,1424],{"class":158},"context",[144,1426,464],{"class":154},[144,1428,1429],{"class":158},"cloudflare",[144,1431,1432],{"class":150}," as",[144,1434,155],{"class":154},[144,1436,1171],{"class":260},[144,1438,337],{"class":154},[144,1440,293],{"class":247},[144,1442,296],{"class":154},[144,1444,299],{"class":247},[144,1446,162],{"class":154},[144,1448,304],{"class":247},[144,1450,1451],{"class":154},">",[144,1453,171],{"class":154},[144,1455,429],{"class":154},[144,1457,1458],{"class":247}," undefined\n",[144,1460,1461,1463,1465,1467],{"class":146,"line":272},[144,1462,1139],{"class":260},[144,1464,1224],{"class":154},[144,1466,613],{"class":158},[144,1468,187],{"class":154},[144,1470,1471,1473,1476,1478],{"class":146,"line":278},[144,1472,449],{"class":243},[144,1474,1475],{"class":158}," runtimeEnv",[144,1477,251],{"class":154},[144,1479,779],{"class":260},[144,1481,1482,1484,1486,1489,1491],{"class":146,"line":283},[144,1483,1419],{"class":158},[144,1485,464],{"class":154},[144,1487,1488],{"class":158},"req",[144,1490,1432],{"class":150},[144,1492,254],{"class":154},[144,1494,1495,1498,1500,1502,1505,1507,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527],{"class":146,"line":310},[144,1496,1497],{"class":260},"      runtime",[144,1499,337],{"class":154},[144,1501,155],{"class":154},[144,1503,1504],{"class":260}," cloudflare",[144,1506,337],{"class":154},[144,1508,155],{"class":154},[144,1510,1171],{"class":260},[144,1512,337],{"class":154},[144,1514,293],{"class":247},[144,1516,296],{"class":154},[144,1518,299],{"class":247},[144,1520,162],{"class":154},[144,1522,304],{"class":247},[144,1524,1451],{"class":154},[144,1526,171],{"class":154},[144,1528,1529],{"class":154}," };\n",[144,1531,1532],{"class":146,"line":331},[144,1533,1534],{"class":154},"    }\n",[144,1536,1537,1539,1541,1544,1546,1548,1550,1552],{"class":146,"line":344},[144,1538,1139],{"class":260},[144,1540,464],{"class":154},[144,1542,1543],{"class":158},"runtime",[144,1545,1224],{"class":154},[144,1547,1429],{"class":158},[144,1549,1224],{"class":154},[144,1551,613],{"class":158},[144,1553,187],{"class":154},[144,1555,1556],{"class":146,"line":349},[144,1557,237],{"emptyLinePlaceholder":236},[144,1559,1560,1563,1565,1568,1571,1573,1575],{"class":146,"line":354},[144,1561,1562],{"class":507},"  cacheHyperdriveConnectionString",[144,1564,795],{"class":260},[144,1566,1567],{"class":158},"contextEnv",[144,1569,1570],{"class":154}," ??",[144,1572,1475],{"class":158},[144,1574,922],{"class":260},[144,1576,187],{"class":154},[144,1578,1579,1582,1584],{"class":146,"line":376},[144,1580,1581],{"class":154},"}",[144,1583,922],{"class":158},[144,1585,187],{"class":154},[10,1587,1588],{},"配置弄好，代码改好后，就可以部署到线上了。访问页面，终于可以正常访问数据库了。Hyperdrive 的 metrics 中也终于可以正常显示数据流量了：",[10,1590,1591],{},[1592,1593],"img",{":zoom":1594,"alt":1595,"loading":1596,"src":1597},"false","Hyperdrive Metrics","lazy","\u002Fimages\u002Fillustration\u002Fhyperdrive-metrics.webp",[26,1599,1600],{"id":1600},"结语",[10,1602,1603],{},"这个事件让我意识到一个很严重的事情：",[1605,1606,1608],"blog-style-highlight",{"status":1607},"error",[10,1609,1610],{},[86,1611,1612],{},"在不知不觉中，我已经养成了过度依赖AI的习惯，自己几乎停止了学习和思考，总是希望这个黑盒能帮我解决所有问题。",[10,1614,1615],{},"这可不是一个好现象。思来想去，我的决定是：",[1605,1617,1619],{"status":1618},"info",[10,1620,1621],{},[86,1622,1623],{},"不管 AI 多厉害，多强大，知识还是要掌握在自己脑袋里，才是最夯实的。永远要保证自己对问题的理解和解决方案的掌控，而不是完全依赖 AI 的输出。",[10,1625,1626],{},"前几天找来了一些界面设计、产品设计相关的资料，每天还是要安排固定的时间学习才行。",[1628,1629,1630],"style",{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .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);}",{"title":57,"searchDepth":190,"depth":190,"links":1632},[1633,1637,1641],{"id":28,"depth":190,"text":28,"children":1634},[1635,1636],{"id":32,"depth":210,"text":33},{"id":45,"depth":210,"text":46},{"id":63,"depth":190,"text":63,"children":1638},[1639,1640],{"id":66,"depth":210,"text":67},{"id":129,"depth":210,"text":129},{"id":1600,"depth":190,"text":1600},"md","zh-CN",{"artifactId":1645,"lastmod":1645,"backgroundImg":1645,"ogImage":1645,"listPreviewImg":-1},null,"\u002Fblog\u002Fcloudflare-hyperdrive-to-supabase\u002Findex.zh-cn",{"title":5,"description":12},{"loc":1646},"blog\u002Fcloudflare-hyperdrive-to-supabase\u002Findex.zh-CN","Bm5h8-0MGrdsUNXqK2Kco08neDkrh3li6JGvlFwj-q8","2026-06-23","tech_and_career",[1654,1655,1429,1656],"troubleshooting","deployment","supabase",[1658,1663],{"title":1659,"path":1660,"description":1661,"date":1662},"Nuxt Content 多语言文件管理，我换了一种组织方式","\u002Fblog\u002Fnuxt-content-i18n-architecture-refactor","我的博客网站使用的是 Nuxt 的框架。由于支持了多语言，原本比较简单的问题，在多语言的前提下会变得复杂一些。比如：文件的管理。","2026-06-26",{"title":1664,"path":1665,"description":1666,"date":1667},"音乐会 - 黑神话：悟空 - 随乐西行，且听天命","\u002Fblog\u002Fconcert-black-myth-wukong-2026","黑猴的音乐会，从去年起我就满心期待。今年终于开到了长沙。","2026-06-21",1782739123243]