[{"data":1,"prerenderedAt":1847},["ShallowReactive",2],{"doc-page:\u002Fdocs\u002Fnuxt-vue-notes":3},{"doc":4,"prev":1807,"next":1814,"resolvedType":8,"readingMinutes":178,"audience":1821,"checklist":1825,"related":1829},{"path":5,"title":6,"description":7,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":12,"publishedAt":12,"icon":13,"body":14},"\u002Fdocs\u002Fnuxt-vue-notes","Vue 3 \u002F Nuxt 开发笔记","Vue 3 Composition API、Nuxt 3 常用模式、自动导入与部署配置","article",null,"programming-languages","编程语言","2026-02-27","i-carbon-application",{"type":15,"value":16,"toc":1781},"minimark",[17,21,25,29,32,70,73,77,81,414,417,506,510,686,690,900,904,907,1069,1072,1160,1163,1223,1227,1349,1353,1464,1467,1470,1478,1481,1493,1496,1499,1517,1520,1550,1553,1557,1560,1577,1583,1587,1590,1601,1604,1615,1618,1637,1640,1661,1664,1745,1748,1777],[18,19,6],"h1",{"id":20},"vue-3-nuxt-开发笔记",[22,23,24],"p",{},"这页适合已经会写基础前端、准备把 Vue 3 \u002F Nuxt 3 用顺手的人。阅读时可以把它当成“从组件开发到全栈页面交付”的中间层笔记，而不是单纯 API 速查。",[26,27,28],"h2",{"id":28},"推荐学习顺序",[22,30,31],{},"如果你是第一次系统接触 Vue 3 \u002F Nuxt，建议按这个顺序走：",[33,34,35,53,56,67],"ol",{},[36,37,38,39,43,44,43,47,43,50],"li",{},"先理解 ",[40,41,42],"code",{},"ref","、",[40,45,46],{},"reactive",[40,48,49],{},"computed",[40,51,52],{},"watch",[36,54,55],{},"再掌握组件通信、Composable 和页面级状态",[36,57,58,59,62,63,66],{},"然后理解 ",[40,60,61],{},"useFetch"," \u002F ",[40,64,65],{},"useAsyncData"," \u002F 服务端 API 的边界",[36,68,69],{},"最后补 SEO、路由中间件、部署和环境变量",[22,71,72],{},"先把“响应式”和“数据流”理顺，后面的 SSR、缓存、SEO 才不容易踩坑。",[26,74,76],{"id":75},"vue-3-composition-api","Vue 3 Composition API",[78,79,80],"h3",{"id":80},"响应式",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nconst count = ref(0);\nconst doubled = computed(() => count.value * 2);\n\nconst user = reactive({\n  name: \"Domi\",\n  age: 20,\n});\n\n\u002F\u002F 监听\nwatch(count, (newVal, oldVal) => {\n  console.log(`${oldVal} -> ${newVal}`);\n});\n\n\u002F\u002F 监听多个\nwatch([count, () => user.name], ([c, n]) => {\n  console.log(c, n);\n});\n\n\u002F\u002F 立即执行的副作用\nwatchEffect(() => {\n  console.log(`count is ${count.value}`);\n});\n\u003C\u002Fscript>\n","vue","",[40,89,90,120,146,176,183,199,211,222,228,233,240,266,292,297,302,308,336,346,351,356,362,374,399,404],{"__ignoreMap":87},[91,92,95,99,103,107,110,113,117],"span",{"class":93,"line":94},"line",1,[91,96,98],{"class":97},"sVt8B","\u003C",[91,100,102],{"class":101},"s9eBZ","script",[91,104,106],{"class":105},"sScJk"," setup",[91,108,109],{"class":105}," lang",[91,111,112],{"class":97},"=",[91,114,116],{"class":115},"sZZnC","\"ts\"",[91,118,119],{"class":97},">\n",[91,121,123,127,131,134,137,140,143],{"class":93,"line":122},2,[91,124,126],{"class":125},"szBVR","const",[91,128,130],{"class":129},"sj4cs"," count",[91,132,133],{"class":125}," =",[91,135,136],{"class":105}," ref",[91,138,139],{"class":97},"(",[91,141,142],{"class":129},"0",[91,144,145],{"class":97},");\n",[91,147,149,151,154,156,159,162,165,168,171,174],{"class":93,"line":148},3,[91,150,126],{"class":125},[91,152,153],{"class":129}," doubled",[91,155,133],{"class":125},[91,157,158],{"class":105}," computed",[91,160,161],{"class":97},"(() ",[91,163,164],{"class":125},"=>",[91,166,167],{"class":97}," count.value ",[91,169,170],{"class":125},"*",[91,172,173],{"class":129}," 2",[91,175,145],{"class":97},[91,177,179],{"class":93,"line":178},4,[91,180,182],{"emptyLinePlaceholder":181},true,"\n",[91,184,186,188,191,193,196],{"class":93,"line":185},5,[91,187,126],{"class":125},[91,189,190],{"class":129}," user",[91,192,133],{"class":125},[91,194,195],{"class":105}," reactive",[91,197,198],{"class":97},"({\n",[91,200,202,205,208],{"class":93,"line":201},6,[91,203,204],{"class":97},"  name: ",[91,206,207],{"class":115},"\"Domi\"",[91,209,210],{"class":97},",\n",[91,212,214,217,220],{"class":93,"line":213},7,[91,215,216],{"class":97},"  age: ",[91,218,219],{"class":129},"20",[91,221,210],{"class":97},[91,223,225],{"class":93,"line":224},8,[91,226,227],{"class":97},"});\n",[91,229,231],{"class":93,"line":230},9,[91,232,182],{"emptyLinePlaceholder":181},[91,234,236],{"class":93,"line":235},10,[91,237,239],{"class":238},"sJ8bj","\u002F\u002F 监听\n",[91,241,243,245,248,252,255,258,261,263],{"class":93,"line":242},11,[91,244,52],{"class":105},[91,246,247],{"class":97},"(count, (",[91,249,251],{"class":250},"s4XuR","newVal",[91,253,254],{"class":97},", ",[91,256,257],{"class":250},"oldVal",[91,259,260],{"class":97},") ",[91,262,164],{"class":125},[91,264,265],{"class":97}," {\n",[91,267,269,272,275,277,280,282,285,287,290],{"class":93,"line":268},12,[91,270,271],{"class":97},"  console.",[91,273,274],{"class":105},"log",[91,276,139],{"class":97},[91,278,279],{"class":115},"`${",[91,281,257],{"class":97},[91,283,284],{"class":115},"} -> ${",[91,286,251],{"class":97},[91,288,289],{"class":115},"}`",[91,291,145],{"class":97},[91,293,295],{"class":93,"line":294},13,[91,296,227],{"class":97},[91,298,300],{"class":93,"line":299},14,[91,301,182],{"emptyLinePlaceholder":181},[91,303,305],{"class":93,"line":304},15,[91,306,307],{"class":238},"\u002F\u002F 监听多个\n",[91,309,311,313,316,318,321,324,326,329,332,334],{"class":93,"line":310},16,[91,312,52],{"class":105},[91,314,315],{"class":97},"([count, () ",[91,317,164],{"class":125},[91,319,320],{"class":97}," user.name], ([",[91,322,323],{"class":250},"c",[91,325,254],{"class":97},[91,327,328],{"class":250},"n",[91,330,331],{"class":97},"]) ",[91,333,164],{"class":125},[91,335,265],{"class":97},[91,337,339,341,343],{"class":93,"line":338},17,[91,340,271],{"class":97},[91,342,274],{"class":105},[91,344,345],{"class":97},"(c, n);\n",[91,347,349],{"class":93,"line":348},18,[91,350,227],{"class":97},[91,352,354],{"class":93,"line":353},19,[91,355,182],{"emptyLinePlaceholder":181},[91,357,359],{"class":93,"line":358},20,[91,360,361],{"class":238},"\u002F\u002F 立即执行的副作用\n",[91,363,365,368,370,372],{"class":93,"line":364},21,[91,366,367],{"class":105},"watchEffect",[91,369,161],{"class":97},[91,371,164],{"class":125},[91,373,265],{"class":97},[91,375,377,379,381,383,386,389,392,395,397],{"class":93,"line":376},22,[91,378,271],{"class":97},[91,380,274],{"class":105},[91,382,139],{"class":97},[91,384,385],{"class":115},"`count is ${",[91,387,388],{"class":97},"count",[91,390,391],{"class":115},".",[91,393,394],{"class":97},"value",[91,396,289],{"class":115},[91,398,145],{"class":97},[91,400,402],{"class":93,"line":401},23,[91,403,227],{"class":97},[91,405,407,410,412],{"class":93,"line":406},24,[91,408,409],{"class":97},"\u003C\u002F",[91,411,102],{"class":101},[91,413,119],{"class":97},[78,415,416],{"id":416},"生命周期",[82,418,420],{"className":84,"code":419,"language":86,"meta":87,"style":87},"\u003Cscript setup lang=\"ts\">\nonMounted(() => {\n  \u002F* DOM 已挂载 *\u002F\n});\nonUnmounted(() => {\n  \u002F* 清理 *\u002F\n});\nonBeforeUpdate(() => {\n  \u002F* 更新前 *\u002F\n});\n\u003C\u002Fscript>\n",[40,421,422,438,449,454,458,469,474,478,489,494,498],{"__ignoreMap":87},[91,423,424,426,428,430,432,434,436],{"class":93,"line":94},[91,425,98],{"class":97},[91,427,102],{"class":101},[91,429,106],{"class":105},[91,431,109],{"class":105},[91,433,112],{"class":97},[91,435,116],{"class":115},[91,437,119],{"class":97},[91,439,440,443,445,447],{"class":93,"line":122},[91,441,442],{"class":105},"onMounted",[91,444,161],{"class":97},[91,446,164],{"class":125},[91,448,265],{"class":97},[91,450,451],{"class":93,"line":148},[91,452,453],{"class":238},"  \u002F* DOM 已挂载 *\u002F\n",[91,455,456],{"class":93,"line":178},[91,457,227],{"class":97},[91,459,460,463,465,467],{"class":93,"line":185},[91,461,462],{"class":105},"onUnmounted",[91,464,161],{"class":97},[91,466,164],{"class":125},[91,468,265],{"class":97},[91,470,471],{"class":93,"line":201},[91,472,473],{"class":238},"  \u002F* 清理 *\u002F\n",[91,475,476],{"class":93,"line":213},[91,477,227],{"class":97},[91,479,480,483,485,487],{"class":93,"line":224},[91,481,482],{"class":105},"onBeforeUpdate",[91,484,161],{"class":97},[91,486,164],{"class":125},[91,488,265],{"class":97},[91,490,491],{"class":93,"line":230},[91,492,493],{"class":238},"  \u002F* 更新前 *\u002F\n",[91,495,496],{"class":93,"line":235},[91,497,227],{"class":97},[91,499,500,502,504],{"class":93,"line":242},[91,501,409],{"class":97},[91,503,102],{"class":101},[91,505,119],{"class":97},[78,507,509],{"id":508},"组合式函数composable","组合式函数（Composable）",[82,511,515],{"className":512,"code":513,"language":514,"meta":87,"style":87},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F composables\u002FuseMouse.ts\nexport function useMouse() {\n  const x = ref(0);\n  const y = ref(0);\n\n  function update(e: MouseEvent) {\n    x.value = e.pageX;\n    y.value = e.pageY;\n  }\n\n  onMounted(() => window.addEventListener(\"mousemove\", update));\n  onUnmounted(() => window.removeEventListener(\"mousemove\", update));\n\n  return { x, y };\n}\n","typescript",[40,516,517,522,536,554,571,575,597,607,617,622,626,649,669,673,681],{"__ignoreMap":87},[91,518,519],{"class":93,"line":94},[91,520,521],{"class":238},"\u002F\u002F composables\u002FuseMouse.ts\n",[91,523,524,527,530,533],{"class":93,"line":122},[91,525,526],{"class":125},"export",[91,528,529],{"class":125}," function",[91,531,532],{"class":105}," useMouse",[91,534,535],{"class":97},"() {\n",[91,537,538,541,544,546,548,550,552],{"class":93,"line":148},[91,539,540],{"class":125},"  const",[91,542,543],{"class":129}," x",[91,545,133],{"class":125},[91,547,136],{"class":105},[91,549,139],{"class":97},[91,551,142],{"class":129},[91,553,145],{"class":97},[91,555,556,558,561,563,565,567,569],{"class":93,"line":178},[91,557,540],{"class":125},[91,559,560],{"class":129}," y",[91,562,133],{"class":125},[91,564,136],{"class":105},[91,566,139],{"class":97},[91,568,142],{"class":129},[91,570,145],{"class":97},[91,572,573],{"class":93,"line":185},[91,574,182],{"emptyLinePlaceholder":181},[91,576,577,580,583,585,588,591,594],{"class":93,"line":201},[91,578,579],{"class":125},"  function",[91,581,582],{"class":105}," update",[91,584,139],{"class":97},[91,586,587],{"class":250},"e",[91,589,590],{"class":125},":",[91,592,593],{"class":105}," MouseEvent",[91,595,596],{"class":97},") {\n",[91,598,599,602,604],{"class":93,"line":213},[91,600,601],{"class":97},"    x.value ",[91,603,112],{"class":125},[91,605,606],{"class":97}," e.pageX;\n",[91,608,609,612,614],{"class":93,"line":224},[91,610,611],{"class":97},"    y.value ",[91,613,112],{"class":125},[91,615,616],{"class":97}," e.pageY;\n",[91,618,619],{"class":93,"line":230},[91,620,621],{"class":97},"  }\n",[91,623,624],{"class":93,"line":235},[91,625,182],{"emptyLinePlaceholder":181},[91,627,628,631,633,635,638,641,643,646],{"class":93,"line":242},[91,629,630],{"class":105},"  onMounted",[91,632,161],{"class":97},[91,634,164],{"class":125},[91,636,637],{"class":97}," window.",[91,639,640],{"class":105},"addEventListener",[91,642,139],{"class":97},[91,644,645],{"class":115},"\"mousemove\"",[91,647,648],{"class":97},", update));\n",[91,650,651,654,656,658,660,663,665,667],{"class":93,"line":268},[91,652,653],{"class":105},"  onUnmounted",[91,655,161],{"class":97},[91,657,164],{"class":125},[91,659,637],{"class":97},[91,661,662],{"class":105},"removeEventListener",[91,664,139],{"class":97},[91,666,645],{"class":115},[91,668,648],{"class":97},[91,670,671],{"class":93,"line":294},[91,672,182],{"emptyLinePlaceholder":181},[91,674,675,678],{"class":93,"line":299},[91,676,677],{"class":125},"  return",[91,679,680],{"class":97}," { x, y };\n",[91,682,683],{"class":93,"line":304},[91,684,685],{"class":97},"}\n",[78,687,689],{"id":688},"props-与-emits","Props 与 Emits",[82,691,693],{"className":84,"code":692,"language":86,"meta":87,"style":87},"\u003Cscript setup lang=\"ts\">\nconst props = defineProps\u003C{\n  title: string;\n  count?: number;\n}>();\n\nconst emit = defineEmits\u003C{\n  update: [value: string];\n  close: [];\n}>();\n\n\u002F\u002F 带默认值\nconst props = withDefaults(\n  defineProps\u003C{\n    size?: \"sm\" | \"md\" | \"lg\";\n  }>(),\n  {\n    size: \"md\",\n  },\n);\n\u003C\u002Fscript>\n",[40,694,695,711,726,739,752,757,761,775,796,806,810,814,819,833,840,863,868,873,883,888,892],{"__ignoreMap":87},[91,696,697,699,701,703,705,707,709],{"class":93,"line":94},[91,698,98],{"class":97},[91,700,102],{"class":101},[91,702,106],{"class":105},[91,704,109],{"class":105},[91,706,112],{"class":97},[91,708,116],{"class":115},[91,710,119],{"class":97},[91,712,713,715,718,720,723],{"class":93,"line":122},[91,714,126],{"class":125},[91,716,717],{"class":129}," props",[91,719,133],{"class":125},[91,721,722],{"class":105}," defineProps",[91,724,725],{"class":97},"\u003C{\n",[91,727,728,731,733,736],{"class":93,"line":148},[91,729,730],{"class":250},"  title",[91,732,590],{"class":125},[91,734,735],{"class":129}," string",[91,737,738],{"class":97},";\n",[91,740,741,744,747,750],{"class":93,"line":178},[91,742,743],{"class":250},"  count",[91,745,746],{"class":125},"?:",[91,748,749],{"class":129}," number",[91,751,738],{"class":97},[91,753,754],{"class":93,"line":185},[91,755,756],{"class":97},"}>();\n",[91,758,759],{"class":93,"line":201},[91,760,182],{"emptyLinePlaceholder":181},[91,762,763,765,768,770,773],{"class":93,"line":213},[91,764,126],{"class":125},[91,766,767],{"class":129}," emit",[91,769,133],{"class":125},[91,771,772],{"class":105}," defineEmits",[91,774,725],{"class":97},[91,776,777,780,782,785,787,790,793],{"class":93,"line":224},[91,778,779],{"class":250},"  update",[91,781,590],{"class":125},[91,783,784],{"class":97}," [",[91,786,394],{"class":105},[91,788,789],{"class":97},": ",[91,791,792],{"class":129},"string",[91,794,795],{"class":97},"];\n",[91,797,798,801,803],{"class":93,"line":230},[91,799,800],{"class":250},"  close",[91,802,590],{"class":125},[91,804,805],{"class":97}," [];\n",[91,807,808],{"class":93,"line":235},[91,809,756],{"class":97},[91,811,812],{"class":93,"line":242},[91,813,182],{"emptyLinePlaceholder":181},[91,815,816],{"class":93,"line":268},[91,817,818],{"class":238},"\u002F\u002F 带默认值\n",[91,820,821,823,825,827,830],{"class":93,"line":294},[91,822,126],{"class":125},[91,824,717],{"class":129},[91,826,133],{"class":125},[91,828,829],{"class":105}," withDefaults",[91,831,832],{"class":97},"(\n",[91,834,835,838],{"class":93,"line":299},[91,836,837],{"class":105},"  defineProps",[91,839,725],{"class":97},[91,841,842,845,847,850,853,856,858,861],{"class":93,"line":304},[91,843,844],{"class":250},"    size",[91,846,746],{"class":125},[91,848,849],{"class":115}," \"sm\"",[91,851,852],{"class":125}," |",[91,854,855],{"class":115}," \"md\"",[91,857,852],{"class":125},[91,859,860],{"class":115}," \"lg\"",[91,862,738],{"class":97},[91,864,865],{"class":93,"line":310},[91,866,867],{"class":97},"  }>(),\n",[91,869,870],{"class":93,"line":338},[91,871,872],{"class":97},"  {\n",[91,874,875,878,881],{"class":93,"line":348},[91,876,877],{"class":97},"    size: ",[91,879,880],{"class":115},"\"md\"",[91,882,210],{"class":97},[91,884,885],{"class":93,"line":353},[91,886,887],{"class":97},"  },\n",[91,889,890],{"class":93,"line":358},[91,891,145],{"class":97},[91,893,894,896,898],{"class":93,"line":364},[91,895,409],{"class":97},[91,897,102],{"class":101},[91,899,119],{"class":97},[26,901,903],{"id":902},"nuxt-常用模式","Nuxt 常用模式",[78,905,906],{"id":906},"数据获取",[82,908,910],{"className":84,"code":909,"language":86,"meta":87,"style":87},"\u003Cscript setup lang=\"ts\">\n\u002F\u002F SSR 友好的数据获取\nconst { data, pending, error, refresh } = await useFetch(\"\u002Fapi\u002Fusers\");\n\n\u002F\u002F 带缓存键\nconst { data } = await useAsyncData(\"users\", () => $fetch(\"\u002Fapi\u002Fusers\"));\n\n\u002F\u002F 仅客户端\nconst { data } = await useFetch(\"\u002Fapi\u002Fdata\", { lazy: true });\n\u003C\u002Fscript>\n",[40,911,912,928,933,976,980,985,1022,1026,1031,1061],{"__ignoreMap":87},[91,913,914,916,918,920,922,924,926],{"class":93,"line":94},[91,915,98],{"class":97},[91,917,102],{"class":101},[91,919,106],{"class":105},[91,921,109],{"class":105},[91,923,112],{"class":97},[91,925,116],{"class":115},[91,927,119],{"class":97},[91,929,930],{"class":93,"line":122},[91,931,932],{"class":238},"\u002F\u002F SSR 友好的数据获取\n",[91,934,935,937,940,943,945,948,950,953,955,958,961,963,966,969,971,974],{"class":93,"line":148},[91,936,126],{"class":125},[91,938,939],{"class":97}," { ",[91,941,942],{"class":129},"data",[91,944,254],{"class":97},[91,946,947],{"class":129},"pending",[91,949,254],{"class":97},[91,951,952],{"class":129},"error",[91,954,254],{"class":97},[91,956,957],{"class":129},"refresh",[91,959,960],{"class":97}," } ",[91,962,112],{"class":125},[91,964,965],{"class":125}," await",[91,967,968],{"class":105}," useFetch",[91,970,139],{"class":97},[91,972,973],{"class":115},"\"\u002Fapi\u002Fusers\"",[91,975,145],{"class":97},[91,977,978],{"class":93,"line":178},[91,979,182],{"emptyLinePlaceholder":181},[91,981,982],{"class":93,"line":185},[91,983,984],{"class":238},"\u002F\u002F 带缓存键\n",[91,986,987,989,991,993,995,997,999,1002,1004,1007,1010,1012,1015,1017,1019],{"class":93,"line":201},[91,988,126],{"class":125},[91,990,939],{"class":97},[91,992,942],{"class":129},[91,994,960],{"class":97},[91,996,112],{"class":125},[91,998,965],{"class":125},[91,1000,1001],{"class":105}," useAsyncData",[91,1003,139],{"class":97},[91,1005,1006],{"class":115},"\"users\"",[91,1008,1009],{"class":97},", () ",[91,1011,164],{"class":125},[91,1013,1014],{"class":105}," $fetch",[91,1016,139],{"class":97},[91,1018,973],{"class":115},[91,1020,1021],{"class":97},"));\n",[91,1023,1024],{"class":93,"line":213},[91,1025,182],{"emptyLinePlaceholder":181},[91,1027,1028],{"class":93,"line":224},[91,1029,1030],{"class":238},"\u002F\u002F 仅客户端\n",[91,1032,1033,1035,1037,1039,1041,1043,1045,1047,1049,1052,1055,1058],{"class":93,"line":230},[91,1034,126],{"class":125},[91,1036,939],{"class":97},[91,1038,942],{"class":129},[91,1040,960],{"class":97},[91,1042,112],{"class":125},[91,1044,965],{"class":125},[91,1046,968],{"class":105},[91,1048,139],{"class":97},[91,1050,1051],{"class":115},"\"\u002Fapi\u002Fdata\"",[91,1053,1054],{"class":97},", { lazy: ",[91,1056,1057],{"class":129},"true",[91,1059,1060],{"class":97}," });\n",[91,1062,1063,1065,1067],{"class":93,"line":235},[91,1064,409],{"class":97},[91,1066,102],{"class":101},[91,1068,119],{"class":97},[78,1070,1071],{"id":1071},"路由与中间件",[82,1073,1075],{"className":512,"code":1074,"language":514,"meta":87,"style":87},"\u002F\u002F middleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware((to, from) => {\n  const user = useUserStore();\n  if (!user.isLoggedIn) {\n    return navigateTo(\"\u002Flogin\");\n  }\n});\n",[40,1076,1077,1082,1109,1123,1137,1152,1156],{"__ignoreMap":87},[91,1078,1079],{"class":93,"line":94},[91,1080,1081],{"class":238},"\u002F\u002F middleware\u002Fauth.ts\n",[91,1083,1084,1086,1089,1092,1095,1098,1100,1103,1105,1107],{"class":93,"line":122},[91,1085,526],{"class":125},[91,1087,1088],{"class":125}," default",[91,1090,1091],{"class":105}," defineNuxtRouteMiddleware",[91,1093,1094],{"class":97},"((",[91,1096,1097],{"class":250},"to",[91,1099,254],{"class":97},[91,1101,1102],{"class":250},"from",[91,1104,260],{"class":97},[91,1106,164],{"class":125},[91,1108,265],{"class":97},[91,1110,1111,1113,1115,1117,1120],{"class":93,"line":148},[91,1112,540],{"class":125},[91,1114,190],{"class":129},[91,1116,133],{"class":125},[91,1118,1119],{"class":105}," useUserStore",[91,1121,1122],{"class":97},"();\n",[91,1124,1125,1128,1131,1134],{"class":93,"line":178},[91,1126,1127],{"class":125},"  if",[91,1129,1130],{"class":97}," (",[91,1132,1133],{"class":125},"!",[91,1135,1136],{"class":97},"user.isLoggedIn) {\n",[91,1138,1139,1142,1145,1147,1150],{"class":93,"line":185},[91,1140,1141],{"class":125},"    return",[91,1143,1144],{"class":105}," navigateTo",[91,1146,139],{"class":97},[91,1148,1149],{"class":115},"\"\u002Flogin\"",[91,1151,145],{"class":97},[91,1153,1154],{"class":93,"line":201},[91,1155,621],{"class":97},[91,1157,1158],{"class":93,"line":213},[91,1159,227],{"class":97},[22,1161,1162],{},"页面中使用：",[82,1164,1166],{"className":84,"code":1165,"language":86,"meta":87,"style":87},"\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  middleware: \"auth\",\n  layout: \"admin\",\n});\n\u003C\u002Fscript>\n",[40,1167,1168,1184,1191,1201,1211,1215],{"__ignoreMap":87},[91,1169,1170,1172,1174,1176,1178,1180,1182],{"class":93,"line":94},[91,1171,98],{"class":97},[91,1173,102],{"class":101},[91,1175,106],{"class":105},[91,1177,109],{"class":105},[91,1179,112],{"class":97},[91,1181,116],{"class":115},[91,1183,119],{"class":97},[91,1185,1186,1189],{"class":93,"line":122},[91,1187,1188],{"class":105},"definePageMeta",[91,1190,198],{"class":97},[91,1192,1193,1196,1199],{"class":93,"line":148},[91,1194,1195],{"class":97},"  middleware: ",[91,1197,1198],{"class":115},"\"auth\"",[91,1200,210],{"class":97},[91,1202,1203,1206,1209],{"class":93,"line":178},[91,1204,1205],{"class":97},"  layout: ",[91,1207,1208],{"class":115},"\"admin\"",[91,1210,210],{"class":97},[91,1212,1213],{"class":93,"line":185},[91,1214,227],{"class":97},[91,1216,1217,1219,1221],{"class":93,"line":201},[91,1218,409],{"class":97},[91,1220,102],{"class":101},[91,1222,119],{"class":97},[78,1224,1226],{"id":1225},"seo","SEO",[82,1228,1230],{"className":84,"code":1229,"language":86,"meta":87,"style":87},"\u003Cscript setup lang=\"ts\">\nuseHead({\n  title: \"页面标题\",\n  meta: [{ name: \"description\", content: \"页面描述\" }],\n});\n\n\u002F\u002F 或使用组件\nuseSeoMeta({\n  title: \"页面标题\",\n  ogTitle: \"页面标题\",\n  description: \"页面描述\",\n  ogDescription: \"页面描述\",\n});\n\u003C\u002Fscript>\n",[40,1231,1232,1248,1255,1265,1282,1286,1290,1295,1302,1310,1319,1328,1337,1341],{"__ignoreMap":87},[91,1233,1234,1236,1238,1240,1242,1244,1246],{"class":93,"line":94},[91,1235,98],{"class":97},[91,1237,102],{"class":101},[91,1239,106],{"class":105},[91,1241,109],{"class":105},[91,1243,112],{"class":97},[91,1245,116],{"class":115},[91,1247,119],{"class":97},[91,1249,1250,1253],{"class":93,"line":122},[91,1251,1252],{"class":105},"useHead",[91,1254,198],{"class":97},[91,1256,1257,1260,1263],{"class":93,"line":148},[91,1258,1259],{"class":97},"  title: ",[91,1261,1262],{"class":115},"\"页面标题\"",[91,1264,210],{"class":97},[91,1266,1267,1270,1273,1276,1279],{"class":93,"line":178},[91,1268,1269],{"class":97},"  meta: [{ name: ",[91,1271,1272],{"class":115},"\"description\"",[91,1274,1275],{"class":97},", content: ",[91,1277,1278],{"class":115},"\"页面描述\"",[91,1280,1281],{"class":97}," }],\n",[91,1283,1284],{"class":93,"line":185},[91,1285,227],{"class":97},[91,1287,1288],{"class":93,"line":201},[91,1289,182],{"emptyLinePlaceholder":181},[91,1291,1292],{"class":93,"line":213},[91,1293,1294],{"class":238},"\u002F\u002F 或使用组件\n",[91,1296,1297,1300],{"class":93,"line":224},[91,1298,1299],{"class":105},"useSeoMeta",[91,1301,198],{"class":97},[91,1303,1304,1306,1308],{"class":93,"line":230},[91,1305,1259],{"class":97},[91,1307,1262],{"class":115},[91,1309,210],{"class":97},[91,1311,1312,1315,1317],{"class":93,"line":235},[91,1313,1314],{"class":97},"  ogTitle: ",[91,1316,1262],{"class":115},[91,1318,210],{"class":97},[91,1320,1321,1324,1326],{"class":93,"line":242},[91,1322,1323],{"class":97},"  description: ",[91,1325,1278],{"class":115},[91,1327,210],{"class":97},[91,1329,1330,1333,1335],{"class":93,"line":268},[91,1331,1332],{"class":97},"  ogDescription: ",[91,1334,1278],{"class":115},[91,1336,210],{"class":97},[91,1338,1339],{"class":93,"line":294},[91,1340,227],{"class":97},[91,1342,1343,1345,1347],{"class":93,"line":299},[91,1344,409],{"class":97},[91,1346,102],{"class":101},[91,1348,119],{"class":97},[78,1350,1352],{"id":1351},"服务端-api","服务端 API",[82,1354,1356],{"className":512,"code":1355,"language":514,"meta":87,"style":87},"\u002F\u002F server\u002Fapi\u002Fhello.get.ts\nexport default defineEventHandler((event) => {\n  return { message: \"Hello World\" };\n});\n\n\u002F\u002F server\u002Fapi\u002Fusers.post.ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  return { created: true };\n});\n",[40,1357,1358,1363,1383,1396,1400,1404,1409,1432,1449,1460],{"__ignoreMap":87},[91,1359,1360],{"class":93,"line":94},[91,1361,1362],{"class":238},"\u002F\u002F server\u002Fapi\u002Fhello.get.ts\n",[91,1364,1365,1367,1369,1372,1374,1377,1379,1381],{"class":93,"line":122},[91,1366,526],{"class":125},[91,1368,1088],{"class":125},[91,1370,1371],{"class":105}," defineEventHandler",[91,1373,1094],{"class":97},[91,1375,1376],{"class":250},"event",[91,1378,260],{"class":97},[91,1380,164],{"class":125},[91,1382,265],{"class":97},[91,1384,1385,1387,1390,1393],{"class":93,"line":148},[91,1386,677],{"class":125},[91,1388,1389],{"class":97}," { message: ",[91,1391,1392],{"class":115},"\"Hello World\"",[91,1394,1395],{"class":97}," };\n",[91,1397,1398],{"class":93,"line":178},[91,1399,227],{"class":97},[91,1401,1402],{"class":93,"line":185},[91,1403,182],{"emptyLinePlaceholder":181},[91,1405,1406],{"class":93,"line":201},[91,1407,1408],{"class":238},"\u002F\u002F server\u002Fapi\u002Fusers.post.ts\n",[91,1410,1411,1413,1415,1417,1419,1422,1424,1426,1428,1430],{"class":93,"line":213},[91,1412,526],{"class":125},[91,1414,1088],{"class":125},[91,1416,1371],{"class":105},[91,1418,139],{"class":97},[91,1420,1421],{"class":125},"async",[91,1423,1130],{"class":97},[91,1425,1376],{"class":250},[91,1427,260],{"class":97},[91,1429,164],{"class":125},[91,1431,265],{"class":97},[91,1433,1434,1436,1439,1441,1443,1446],{"class":93,"line":224},[91,1435,540],{"class":125},[91,1437,1438],{"class":129}," body",[91,1440,133],{"class":125},[91,1442,965],{"class":125},[91,1444,1445],{"class":105}," readBody",[91,1447,1448],{"class":97},"(event);\n",[91,1450,1451,1453,1456,1458],{"class":93,"line":230},[91,1452,677],{"class":125},[91,1454,1455],{"class":97}," { created: ",[91,1457,1057],{"class":129},[91,1459,1395],{"class":97},[91,1461,1462],{"class":93,"line":235},[91,1463,227],{"class":97},[26,1465,1466],{"id":1466},"常见目录约定",[22,1468,1469],{},"Nuxt 的目录约定本身就是开发效率的一部分，推荐至少记住下面这些高频目录：",[82,1471,1476],{"className":1472,"code":1474,"language":1475,"meta":87},[1473],"language-text","app\u002F\ncomponents\u002F      # 通用组件\ncomposables\u002F     # 复用逻辑\nlayouts\u002F         # 页面框架\nmiddleware\u002F      # 路由守卫\npages\u002F           # 文件路由\nserver\u002Fapi\u002F      # 服务端接口\nserver\u002Futils\u002F    # 服务端工具函数\ncontent\u002F         # 内容型站点数据\n","text",[40,1477,1474],{"__ignoreMap":87},[22,1479,1480],{},"经验上：",[1482,1483,1484,1487,1490],"ul",{},[36,1485,1486],{},"组件负责展示，Composable 负责复用逻辑",[36,1488,1489],{},"页面负责拼装数据，不要把所有请求都塞进组件",[36,1491,1492],{},"服务端接口负责保护密钥、聚合第三方请求、做输入校验",[26,1494,1495],{"id":1495},"数据获取怎么选",[22,1497,1498],{},"几个常见 API 的使用边界可以这样理解：",[1482,1500,1501,1506,1511],{},[36,1502,1503,1505],{},[40,1504,61],{},"：页面 \u002F 组件里最常用，支持 SSR，适合直接请求接口",[36,1507,1508,1510],{},[40,1509,65],{},"：更灵活，适合把请求逻辑封装成函数并复用缓存键",[36,1512,1513,1516],{},[40,1514,1515],{},"$fetch","：更像底层请求方法，适合在事件、Composable、服务端逻辑中手动调用",[22,1518,1519],{},"简单判断：",[1482,1521,1522,1533,1540],{},[36,1523,1524,1528,1529,62,1531],{},[1525,1526,1527],"strong",{},"首屏要展示的数据","：优先 ",[40,1530,61],{},[40,1532,65],{},[36,1534,1535,1528,1538],{},[1525,1536,1537],{},"点击按钮后才请求",[40,1539,1515],{},[36,1541,1542,1545,1546,1549],{},[1525,1543,1544],{},"依赖私密环境变量或第三方密钥","：优先在 ",[40,1547,1548],{},"server\u002Fapi"," 里中转",[26,1551,1552],{"id":1552},"常见坑",[78,1554,1556],{"id":1555},"hydration-mismatch","Hydration mismatch",[22,1558,1559],{},"服务端和客户端渲染结果不一致时，经常会出现 hydration 警告。高频原因包括：",[1482,1561,1562,1571,1574],{},[36,1563,1564,1565,43,1568],{},"模板里直接使用 ",[40,1566,1567],{},"window",[40,1569,1570],{},"document",[36,1572,1573],{},"服务端渲染时取不到浏览器 API",[36,1575,1576],{},"使用了带随机值、当前时间、屏幕尺寸的初始渲染内容",[22,1578,1579,1580,1582],{},"遇到这类问题时，可以把仅浏览器执行的逻辑放到 ",[40,1581,442],{}," 中，或者明确使用客户端组件策略。",[78,1584,1586],{"id":1585},"composable-里副作用过多","Composable 里副作用过多",[22,1588,1589],{},"Composable 最容易被写成“隐形全局状态”。建议尽量避免：",[1482,1591,1592,1595,1598],{},[36,1593,1594],{},"在导入时立即发请求",[36,1596,1597],{},"在函数外层创建会跨页面污染的可变状态",[36,1599,1600],{},"把页面级业务规则和通用逻辑硬绑在一起",[78,1602,1603],{"id":1603},"环境变量边界混乱",[22,1605,1606,1607,1610,1611,1614],{},"Nuxt 项目里，优先把公开配置放进 ",[40,1608,1609],{},"runtimeConfig.public","，私密配置放进 ",[40,1612,1613],{},"runtimeConfig","。不要把服务端密钥直接暴露给前端页面。",[26,1616,1617],{"id":1617},"部署前检查",[1482,1619,1620,1623,1626,1631,1634],{},[36,1621,1622],{},"路由命名是否清晰，动态参数页是否能正确处理空值",[36,1624,1625],{},"页面首屏数据是否支持 SSR",[36,1627,1628,1630],{},[40,1629,1299],{}," 是否覆盖标题、描述、开放图基础字段",[36,1632,1633],{},"环境变量是否区分本地、预览、生产",[36,1635,1636],{},"第三方接口是否经过服务端转发与兜底处理",[26,1638,1639],{"id":1639},"延伸阅读",[1482,1641,1642,1649,1655],{},[36,1643,1644],{},[1645,1646,1648],"a",{"href":1647},"\u002Fdocs\u002Fenv-variables","环境变量与配置管理",[36,1650,1651],{},[1645,1652,1654],{"href":1653},"\u002Fdocs\u002Fgithub-actions","GitHub Actions 入门",[36,1656,1657],{},[1645,1658,1660],{"href":1659},"\u002Fdocs\u002Flocal-setup","本地开发环境搭建",[26,1662,1663],{"id":1663},"常用库",[1665,1666,1667,1680],"table",{},[1668,1669,1670],"thead",{},[1671,1672,1673,1677],"tr",{},[1674,1675,1676],"th",{},"库",[1674,1678,1679],{},"说明",[1681,1682,1683,1697,1709,1721,1733],"tbody",{},[1671,1684,1685,1694],{},[1686,1687,1688],"td",{},[1645,1689,1693],{"href":1690,"rel":1691},"https:\u002F\u002Fvueuse.org\u002F",[1692],"nofollow","VueUse",[1686,1695,1696],{},"Vue 组合式工具集",[1671,1698,1699,1706],{},[1686,1700,1701],{},[1645,1702,1705],{"href":1703,"rel":1704},"https:\u002F\u002Fpinia.vuejs.org\u002F",[1692],"Pinia",[1686,1707,1708],{},"状态管理",[1671,1710,1711,1718],{},[1686,1712,1713],{},[1645,1714,1717],{"href":1715,"rel":1716},"https:\u002F\u002Funocss.dev\u002F",[1692],"UnoCSS",[1686,1719,1720],{},"原子化 CSS",[1671,1722,1723,1730],{},[1686,1724,1725],{},[1645,1726,1729],{"href":1727,"rel":1728},"https:\u002F\u002Fcontent.nuxt.com\u002F",[1692],"Nuxt Content",[1686,1731,1732],{},"内容管理",[1671,1734,1735,1742],{},[1686,1736,1737],{},[1645,1738,1741],{"href":1739,"rel":1740},"https:\u002F\u002Fimage.nuxt.com\u002F",[1692],"Nuxt Image",[1686,1743,1744],{},"图片优化",[26,1746,1747],{"id":1747},"参考链接",[1482,1749,1750,1758,1765,1771],{},[36,1751,1752,1757],{},[1645,1753,1756],{"href":1754,"rel":1755},"https:\u002F\u002Fvuejs.org\u002F",[1692],"Vue 3 文档"," — 官方文档",[36,1759,1760,1757],{},[1645,1761,1764],{"href":1762,"rel":1763},"https:\u002F\u002Fnuxt.com\u002F",[1692],"Nuxt 3 文档",[36,1766,1767,1770],{},[1645,1768,1693],{"href":1690,"rel":1769},[1692]," — 组合式工具集",[36,1772,1773,1776],{},[1645,1774,1705],{"href":1703,"rel":1775},[1692]," — 状态管理",[1778,1779,1780],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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":87,"searchDepth":122,"depth":122,"links":1782},[1783,1784,1790,1796,1797,1798,1803,1804,1805,1806],{"id":28,"depth":122,"text":28},{"id":75,"depth":122,"text":76,"children":1785},[1786,1787,1788,1789],{"id":80,"depth":148,"text":80},{"id":416,"depth":148,"text":416},{"id":508,"depth":148,"text":509},{"id":688,"depth":148,"text":689},{"id":902,"depth":122,"text":903,"children":1791},[1792,1793,1794,1795],{"id":906,"depth":148,"text":906},{"id":1071,"depth":148,"text":1071},{"id":1225,"depth":148,"text":1226},{"id":1351,"depth":148,"text":1352},{"id":1466,"depth":122,"text":1466},{"id":1495,"depth":122,"text":1495},{"id":1552,"depth":122,"text":1552,"children":1799},[1800,1801,1802],{"id":1555,"depth":148,"text":1556},{"id":1585,"depth":148,"text":1586},{"id":1603,"depth":148,"text":1603},{"id":1617,"depth":122,"text":1617},{"id":1639,"depth":122,"text":1639},{"id":1663,"depth":122,"text":1663},{"id":1747,"depth":122,"text":1747},{"path":1808,"title":1809,"description":1810,"docType":8,"resourceKind":9,"categoryId":1811,"categoryLabel":1812,"updatedAt":12,"publishedAt":12,"icon":1813},"\u002Fdocs\u002Fvps-init","VPS 初始化配置","Linux 服务器初始安全配置、用户管理、防火墙、Fail2ban 与常用工具安装","infra-deployment","服务器与部署","i-carbon-cloud",{"path":1815,"title":1816,"description":1817,"docType":8,"resourceKind":9,"categoryId":1818,"categoryLabel":1819,"updatedAt":12,"publishedAt":12,"icon":1820},"\u002Fdocs\u002Fwindows-backup","Windows 备份与恢复","系统备份、驱动备份、软件清单导出与重装后快速恢复方案","windows-system","Windows 系统","i-mdi-microsoft-windows",[1822,1823,1824],"希望把零散经验整理成长期可复用工作流的人","想先建立认知，再决定是否深入实践的人","希望阅读时顺手建立自己的操作清单或收藏体系的人",[1826,1827,1828],"先浏览标题、摘要和目录，带着问题阅读会更高效","顺手记录真正对你有用的命令、链接和注意事项，避免重复搜索","如果页面里提到相关文档，尽量一起打开对照，效果通常更完整",[1830,1835,1839,1843],{"path":1831,"title":1832,"description":1833,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1834,"publishedAt":1834,"icon":13},"\u002Fdocs\u002Ftesting-guide","前端测试指南","Vitest 单元测试、Playwright E2E 测试、测试策略与最佳实践","2026-02-28",{"path":1836,"title":1837,"description":1838,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1834,"publishedAt":1834,"icon":13},"\u002Fdocs\u002Fbun-deno","Bun 与 Deno 运行时","Bun 和 Deno 的安装使用、与 Node.js 对比、包管理与实用命令",{"path":1840,"title":1841,"description":1842,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1834,"publishedAt":1834,"icon":13},"\u002Fdocs\u002Fcss-tricks","CSS 实用技巧","现代 CSS 特性、布局技巧、动画、暗色模式与常用代码片段",{"path":1844,"title":1845,"description":1846,"docType":8,"resourceKind":9,"categoryId":10,"categoryLabel":11,"updatedAt":1834,"publishedAt":1834,"icon":13},"\u002Fdocs\u002Fredis-guide","Redis 使用指南","Redis 安装、数据类型、常用命令、缓存策略与 Node.js 集成",1776215713910]