[{"data":1,"prerenderedAt":1565},["ShallowReactive",2],{"blog:\u002Fblog\u002Fnuxt\u002Fuse-nuxt-app-payload-error":3},{"id":4,"title":5,"author":6,"body":7,"category":1550,"date":1551,"description":1552,"draft":1553,"extension":1554,"image":1555,"meta":1556,"navigation":280,"path":1557,"seo":1558,"series":1555,"seriesOrder":1555,"seriesTitle":1555,"stem":1559,"tags":1560,"updatedAt":1555,"__hash__":1564},"blog\u002Fblog\u002Fnuxt\u002Fuse-nuxt-app-payload-error.md","用 useNuxtApp().payload.error 在 SPA 模式除錯 API 500","charles",{"type":8,"value":9,"toc":1536},"minimark",[10,14,23,57,72,76,91,160,171,175,181,209,219,222,225,443,447,454,459,462,519,596,600,606,901,904,1227,1235,1238,1450,1456,1459,1464,1487,1507,1510,1532],[11,12,13],"h2",{"id":13},"情境",[15,16,17,18,22],"p",{},"在一個 ",[19,20,21],"code",{},"ssr: false"," 的 Nuxt 專案除錯時，API 明明回 500，但瀏覽器 console 完全沒有錯誤訊息。抱著死馬當活馬醫的心態打開 DevTools 敲：",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-js shiki shiki-themes material-theme-lighter github-light github-dark","useNuxtApp().payload.error\n","js","",[19,31,32],{"__ignoreMap":29},[33,34,37,41,45,49,52,54],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sGLFI","useNuxtApp",[33,42,44],{"class":43},"su5hD","()",[33,46,48],{"class":47},"sP7_E",".",[33,50,51],{"class":43},"payload",[33,53,48],{"class":47},[33,55,56],{"class":43},"error\n",[15,58,59,60,63,64,67,68,71],{},"結果完整的錯誤物件就躺在裡面——status、message、甚至 ",[19,61,62],{},"data"," 欄位裡的 ",[19,65,66],{},"why"," \u002F ",[19,69,70],{},"fix"," 都清清楚楚。這才想起這是 Nuxt 預設行為的結果。",[11,73,75],{"id":74},"為什麼-console-看不到","為什麼 console 看不到",[15,77,78,79,67,82,85,86,90],{},"關鍵在 Nuxt 的 ",[19,80,81],{},"useFetch",[19,83,84],{},"useAsyncData","：",[87,88,89],"strong",{},"它們預設會把錯誤當作資料吞起來，不丟到 console","。",[24,92,96],{"className":93,"code":94,"language":95,"meta":29,"style":29},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","const { data, error } = await useFetch('\u002Fapi\u002Fxxx')\n\u002F\u002F API 回 500 → error.value 有完整錯誤\n\u002F\u002F 但 console 一片安靜\n","ts",[19,97,98,147,154],{"__ignoreMap":29},[33,99,100,104,107,111,114,117,120,124,128,131,134,138,142,144],{"class":35,"line":36},[33,101,103],{"class":102},"sbsja","const",[33,105,106],{"class":47}," {",[33,108,110],{"class":109},"s_hVV"," data",[33,112,113],{"class":47},",",[33,115,116],{"class":109}," error",[33,118,119],{"class":47}," }",[33,121,123],{"class":122},"smGrS"," =",[33,125,127],{"class":126},"sVHd0"," await",[33,129,130],{"class":39}," useFetch",[33,132,133],{"class":43},"(",[33,135,137],{"class":136},"sjJ54","'",[33,139,141],{"class":140},"s_sjI","\u002Fapi\u002Fxxx",[33,143,137],{"class":136},[33,145,146],{"class":43},")\n",[33,148,150],{"class":35,"line":149},2,[33,151,153],{"class":152},"sutJx","\u002F\u002F API 回 500 → error.value 有完整錯誤\n",[33,155,157],{"class":35,"line":156},3,[33,158,159],{"class":152},"\u002F\u002F 但 console 一片安靜\n",[15,161,162,163,166,167,170],{},"這是刻意設計：fetch error 是「狀態」而不是「例外」，交給開發者自己決定要不要顯示。搭配 server 端用 ",[19,164,165],{},"createError({ statusCode, statusMessage, data })"," 拋出的結構化錯誤，就會被序列化到 ",[19,168,169],{},"payload.error","，變成跨越 server\u002Fclient 的除錯窗口。",[11,172,174],{"id":173},"payloaderror-在-spa-也能用嗎","payload.error 在 SPA 也能用嗎",[15,176,177,178,180],{},"會有點意外，畢竟 ",[19,179,21],{}," 聽起來像「沒有 server 渲染，何來 payload」。實際上：",[182,183,184,190,201,206],"ul",{},[185,186,187,189],"li",{},[19,188,51],{}," 不是 SSR 的專屬產物，是 Nuxt 執行期的共用狀態容器",[185,191,192,67,195,198,199],{},[19,193,194],{},"showError()",[19,196,197],{},"createError()"," 拋出的錯誤會流向 ",[19,200,169],{},[185,202,203,205],{},[19,204,81],{}," 捕獲的 server 端錯誤也會進到這裡",[185,207,208],{},"所以即使純 SPA，Nuxt 仍然幫你統一收錯誤",[15,210,211,212,215,216,218],{},"這就是為什麼 ",[19,213,214],{},"useNuxtApp().payload.error"," 在 ",[19,217,21],{}," 下還是有料可看。",[11,220,221],{"id":221},"實際使用情境",[15,223,224],{},"除了臨時 debug，也能做成常駐的錯誤監看：",[24,226,230],{"className":227,"code":228,"language":229,"meta":29,"style":29},"language-vue shiki shiki-themes material-theme-lighter github-light github-dark","\u003Cscript setup lang=\"ts\">\nconst nuxtApp = useNuxtApp()\n\n\u002F\u002F 開發環境暴露到 window 方便 console 查\nif (import.meta.dev) {\n  watchEffect(() => {\n    if (nuxtApp.payload.error) {\n      console.group('[Payload Error]')\n      console.log(nuxtApp.payload.error)\n      console.groupEnd()\n    }\n  })\n}\n\u003C\u002Fscript>\n","vue",[19,231,232,261,276,282,288,313,330,355,377,401,413,419,427,433],{"__ignoreMap":29},[33,233,234,237,241,245,248,251,254,256,258],{"class":35,"line":36},[33,235,236],{"class":47},"\u003C",[33,238,240],{"class":239},"sQzsp","script",[33,242,244],{"class":243},"s9AJx"," setup",[33,246,247],{"class":243}," lang",[33,249,250],{"class":47},"=",[33,252,253],{"class":136},"\"",[33,255,95],{"class":140},[33,257,253],{"class":136},[33,259,260],{"class":47},">\n",[33,262,263,265,268,270,273],{"class":35,"line":149},[33,264,103],{"class":102},[33,266,267],{"class":109}," nuxtApp",[33,269,123],{"class":122},[33,271,272],{"class":39}," useNuxtApp",[33,274,275],{"class":43},"()\n",[33,277,278],{"class":35,"line":156},[33,279,281],{"emptyLinePlaceholder":280},true,"\n",[33,283,285],{"class":35,"line":284},4,[33,286,287],{"class":152},"\u002F\u002F 開發環境暴露到 window 方便 console 查\n",[33,289,291,294,297,300,302,305,307,310],{"class":35,"line":290},5,[33,292,293],{"class":126},"if",[33,295,296],{"class":43}," (",[33,298,299],{"class":126},"import",[33,301,48],{"class":47},[33,303,304],{"class":109},"meta",[33,306,48],{"class":47},[33,308,309],{"class":43},"dev) ",[33,311,312],{"class":47},"{\n",[33,314,316,319,322,324,327],{"class":35,"line":315},6,[33,317,318],{"class":39},"  watchEffect",[33,320,133],{"class":321},"skxfh",[33,323,44],{"class":47},[33,325,326],{"class":102}," =>",[33,328,329],{"class":47}," {\n",[33,331,333,336,338,341,343,345,347,350,353],{"class":35,"line":332},7,[33,334,335],{"class":126},"    if",[33,337,296],{"class":321},[33,339,340],{"class":43},"nuxtApp",[33,342,48],{"class":47},[33,344,51],{"class":43},[33,346,48],{"class":47},[33,348,349],{"class":43},"error",[33,351,352],{"class":321},") ",[33,354,312],{"class":47},[33,356,358,361,363,366,368,370,373,375],{"class":35,"line":357},8,[33,359,360],{"class":43},"      console",[33,362,48],{"class":47},[33,364,365],{"class":39},"group",[33,367,133],{"class":321},[33,369,137],{"class":136},[33,371,372],{"class":140},"[Payload Error]",[33,374,137],{"class":136},[33,376,146],{"class":321},[33,378,380,382,384,387,389,391,393,395,397,399],{"class":35,"line":379},9,[33,381,360],{"class":43},[33,383,48],{"class":47},[33,385,386],{"class":39},"log",[33,388,133],{"class":321},[33,390,340],{"class":43},[33,392,48],{"class":47},[33,394,51],{"class":43},[33,396,48],{"class":47},[33,398,349],{"class":43},[33,400,146],{"class":321},[33,402,404,406,408,411],{"class":35,"line":403},10,[33,405,360],{"class":43},[33,407,48],{"class":47},[33,409,410],{"class":39},"groupEnd",[33,412,275],{"class":321},[33,414,416],{"class":35,"line":415},11,[33,417,418],{"class":47},"    }\n",[33,420,422,425],{"class":35,"line":421},12,[33,423,424],{"class":47},"  }",[33,426,146],{"class":321},[33,428,430],{"class":35,"line":429},13,[33,431,432],{"class":47},"}\n",[33,434,436,439,441],{"class":35,"line":435},14,[33,437,438],{"class":47},"\u003C\u002F",[33,440,240],{"class":239},[33,442,260],{"class":47},[11,444,446],{"id":445},"純-vue-怎麼復刻","純 Vue 怎麼復刻",[15,448,449,450,453],{},"純 Vue 專案沒有 ",[19,451,452],{},"useNuxtApp()","，但同樣的體驗可以靠三個方向做到。",[455,456,458],"h3",{"id":457},"方向一選用會把-error-存成-ref-的-data-fetching-library","方向一：選用會把 error 存成 ref 的 data fetching library",[15,460,461],{},"重點是避免自己寫一堆 try\u002Fcatch——讓 library 幫你統一接錯誤：",[463,464,465,478],"table",{},[466,467,468],"thead",{},[469,470,471,475],"tr",{},[472,473,474],"th",{},"Library",[472,476,477],{},"等價欄位",[479,480,481,495,506],"tbody",{},[469,482,483,487],{},[484,485,486],"td",{},"Pinia Colada（Vue 官方生態）",[484,488,489,492,493],{},[19,490,491],{},"useQuery()"," 的 ",[19,494,349],{},[469,496,497,500],{},[484,498,499],{},"TanStack Query (Vue)",[484,501,502,492,504],{},[19,503,491],{},[19,505,349],{},[469,507,508,513],{},[484,509,510,511],{},"VueUse ",[19,512,81],{},[484,514,515,516,518],{},"回傳 ",[19,517,349],{}," ref",[24,520,522],{"className":93,"code":521,"language":95,"meta":29,"style":29},"import { useFetch } from '@vueuse\u002Fcore'\n\nconst { data, error, statusCode } = useFetch('\u002Fapi\u002Fxxx').json()\n\u002F\u002F error.value 有完整錯誤，console 不會噴\n",[19,523,524,546,550,591],{"__ignoreMap":29},[33,525,526,528,530,532,534,537,540,543],{"class":35,"line":36},[33,527,299],{"class":126},[33,529,106],{"class":47},[33,531,130],{"class":43},[33,533,119],{"class":47},[33,535,536],{"class":126}," from",[33,538,539],{"class":136}," '",[33,541,542],{"class":140},"@vueuse\u002Fcore",[33,544,545],{"class":136},"'\n",[33,547,548],{"class":35,"line":149},[33,549,281],{"emptyLinePlaceholder":280},[33,551,552,554,556,558,560,562,564,567,569,571,573,575,577,579,581,584,586,589],{"class":35,"line":156},[33,553,103],{"class":102},[33,555,106],{"class":47},[33,557,110],{"class":109},[33,559,113],{"class":47},[33,561,116],{"class":109},[33,563,113],{"class":47},[33,565,566],{"class":109}," statusCode",[33,568,119],{"class":47},[33,570,123],{"class":122},[33,572,130],{"class":39},[33,574,133],{"class":43},[33,576,137],{"class":136},[33,578,141],{"class":140},[33,580,137],{"class":136},[33,582,583],{"class":43},")",[33,585,48],{"class":47},[33,587,588],{"class":39},"json",[33,590,275],{"class":43},[33,592,593],{"class":35,"line":284},[33,594,595],{"class":152},"\u002F\u002F error.value 有完整錯誤，console 不會噴\n",[455,597,599],{"id":598},"方向二自建全域-error-store","方向二：自建全域 error store",[15,601,602,603,605],{},"最接近 ",[19,604,169],{}," 的體驗——開著 Pinia DevTools 就能即時看所有錯誤。",[24,607,609],{"className":93,"code":608,"language":95,"meta":29,"style":29},"\u002F\u002F stores\u002Fdebug-errors.ts\nexport const useDebugErrors = defineStore('debug-errors', () => {\n  const errors = ref\u003CArray\u003C{ url: string; status?: number; body?: unknown; time: number }>>([])\n  const last = computed(() => errors.value.at(-1))\n\n  function push(entry: Omit\u003C(typeof errors.value)[number], 'time'>) {\n    errors.value.push({ ...entry, time: Date.now() })\n  }\n\n  return { errors, last, push }\n})\n",[19,610,611,616,650,719,761,765,820,866,871,875,895],{"__ignoreMap":29},[33,612,613],{"class":35,"line":36},[33,614,615],{"class":152},"\u002F\u002F stores\u002Fdebug-errors.ts\n",[33,617,618,621,624,627,629,632,634,636,639,641,643,646,648],{"class":35,"line":149},[33,619,620],{"class":126},"export",[33,622,623],{"class":102}," const",[33,625,626],{"class":109}," useDebugErrors",[33,628,123],{"class":122},[33,630,631],{"class":39}," defineStore",[33,633,133],{"class":43},[33,635,137],{"class":136},[33,637,638],{"class":140},"debug-errors",[33,640,137],{"class":136},[33,642,113],{"class":47},[33,644,645],{"class":47}," ()",[33,647,326],{"class":102},[33,649,329],{"class":47},[33,651,652,655,658,660,662,664,668,671,675,678,682,685,688,691,694,696,699,701,704,706,709,711,713,716],{"class":35,"line":156},[33,653,654],{"class":102},"  const",[33,656,657],{"class":109}," errors",[33,659,123],{"class":122},[33,661,518],{"class":39},[33,663,236],{"class":47},[33,665,667],{"class":666},"sbgvK","Array",[33,669,670],{"class":47},"\u003C{",[33,672,674],{"class":673},"sucvu"," url",[33,676,677],{"class":122},":",[33,679,681],{"class":680},"sZMiF"," string",[33,683,684],{"class":47},";",[33,686,687],{"class":673}," status",[33,689,690],{"class":122},"?:",[33,692,693],{"class":680}," number",[33,695,684],{"class":47},[33,697,698],{"class":673}," body",[33,700,690],{"class":122},[33,702,703],{"class":680}," unknown",[33,705,684],{"class":47},[33,707,708],{"class":673}," time",[33,710,677],{"class":122},[33,712,693],{"class":680},[33,714,715],{"class":47}," }>>",[33,717,718],{"class":321},"([])\n",[33,720,721,723,726,728,731,733,735,737,739,741,744,746,749,751,754,758],{"class":35,"line":284},[33,722,654],{"class":102},[33,724,725],{"class":109}," last",[33,727,123],{"class":122},[33,729,730],{"class":39}," computed",[33,732,133],{"class":321},[33,734,44],{"class":47},[33,736,326],{"class":102},[33,738,657],{"class":43},[33,740,48],{"class":47},[33,742,743],{"class":43},"value",[33,745,48],{"class":47},[33,747,748],{"class":39},"at",[33,750,133],{"class":321},[33,752,753],{"class":122},"-",[33,755,757],{"class":756},"srdBf","1",[33,759,760],{"class":321},"))\n",[33,762,763],{"class":35,"line":290},[33,764,281],{"emptyLinePlaceholder":280},[33,766,767,770,773,775,779,781,784,786,788,791,793,795,797,800,803,806,808,810,813,815,818],{"class":35,"line":315},[33,768,769],{"class":102},"  function",[33,771,772],{"class":39}," push",[33,774,133],{"class":47},[33,776,778],{"class":777},"s99_P","entry",[33,780,677],{"class":122},[33,782,783],{"class":666}," Omit",[33,785,236],{"class":47},[33,787,133],{"class":321},[33,789,790],{"class":122},"typeof",[33,792,657],{"class":43},[33,794,48],{"class":47},[33,796,743],{"class":43},[33,798,799],{"class":321},")[",[33,801,802],{"class":680},"number",[33,804,805],{"class":321},"]",[33,807,113],{"class":47},[33,809,539],{"class":136},[33,811,812],{"class":140},"time",[33,814,137],{"class":136},[33,816,817],{"class":47},">)",[33,819,329],{"class":47},[33,821,822,825,827,829,831,834,836,839,842,844,846,848,850,853,855,858,861,864],{"class":35,"line":332},[33,823,824],{"class":43},"    errors",[33,826,48],{"class":47},[33,828,743],{"class":43},[33,830,48],{"class":47},[33,832,833],{"class":39},"push",[33,835,133],{"class":321},[33,837,838],{"class":47},"{",[33,840,841],{"class":122}," ...",[33,843,778],{"class":43},[33,845,113],{"class":47},[33,847,708],{"class":321},[33,849,677],{"class":47},[33,851,852],{"class":43}," Date",[33,854,48],{"class":47},[33,856,857],{"class":39},"now",[33,859,860],{"class":321},"() ",[33,862,863],{"class":47},"}",[33,865,146],{"class":321},[33,867,868],{"class":35,"line":357},[33,869,870],{"class":47},"  }\n",[33,872,873],{"class":35,"line":379},[33,874,281],{"emptyLinePlaceholder":280},[33,876,877,880,882,884,886,888,890,892],{"class":35,"line":403},[33,878,879],{"class":126},"  return",[33,881,106],{"class":47},[33,883,657],{"class":43},[33,885,113],{"class":47},[33,887,725],{"class":43},[33,889,113],{"class":47},[33,891,772],{"class":43},[33,893,894],{"class":47}," }\n",[33,896,897,899],{"class":35,"line":415},[33,898,863],{"class":47},[33,900,146],{"class":43},[15,902,903],{},"搭配一個 fetch wrapper，所有錯誤都會流進 store：",[24,905,907],{"className":93,"code":906,"language":95,"meta":29,"style":29},"async function safeFetch\u003CT>(url: string, opts?: RequestInit): Promise\u003CT> {\n  const store = useDebugErrors()\n  try {\n    const res = await fetch(url, opts)\n    if (!res.ok) {\n      const body = await res.json().catch(() => null)\n      store.push({ url, status: res.status, body })\n      throw new Error(`HTTP ${res.status}`)\n    }\n    return res.json() as Promise\u003CT>\n  } catch (err) {\n    store.push({ url, body: String(err) })\n    throw err\n  }\n}\n",[19,908,909,961,974,981,1006,1027,1063,1099,1132,1136,1160,1176,1210,1218,1222],{"__ignoreMap":29},[33,910,911,914,917,920,922,925,928,931,933,935,937,940,942,945,947,949,952,954,956,959],{"class":35,"line":36},[33,912,913],{"class":102},"async",[33,915,916],{"class":102}," function",[33,918,919],{"class":39}," safeFetch",[33,921,236],{"class":47},[33,923,924],{"class":666},"T",[33,926,927],{"class":47},">(",[33,929,930],{"class":777},"url",[33,932,677],{"class":122},[33,934,681],{"class":680},[33,936,113],{"class":47},[33,938,939],{"class":777}," opts",[33,941,690],{"class":122},[33,943,944],{"class":666}," RequestInit",[33,946,583],{"class":47},[33,948,677],{"class":122},[33,950,951],{"class":666}," Promise",[33,953,236],{"class":47},[33,955,924],{"class":666},[33,957,958],{"class":47},">",[33,960,329],{"class":47},[33,962,963,965,968,970,972],{"class":35,"line":149},[33,964,654],{"class":102},[33,966,967],{"class":109}," store",[33,969,123],{"class":122},[33,971,626],{"class":39},[33,973,275],{"class":321},[33,975,976,979],{"class":35,"line":156},[33,977,978],{"class":126},"  try",[33,980,329],{"class":47},[33,982,983,986,989,991,993,996,998,1000,1002,1004],{"class":35,"line":284},[33,984,985],{"class":102},"    const",[33,987,988],{"class":109}," res",[33,990,123],{"class":122},[33,992,127],{"class":126},[33,994,995],{"class":39}," fetch",[33,997,133],{"class":321},[33,999,930],{"class":43},[33,1001,113],{"class":47},[33,1003,939],{"class":43},[33,1005,146],{"class":321},[33,1007,1008,1010,1012,1015,1018,1020,1023,1025],{"class":35,"line":290},[33,1009,335],{"class":126},[33,1011,296],{"class":321},[33,1013,1014],{"class":122},"!",[33,1016,1017],{"class":43},"res",[33,1019,48],{"class":47},[33,1021,1022],{"class":43},"ok",[33,1024,352],{"class":321},[33,1026,312],{"class":47},[33,1028,1029,1032,1034,1036,1038,1040,1042,1044,1046,1048,1051,1053,1055,1057,1061],{"class":35,"line":315},[33,1030,1031],{"class":102},"      const",[33,1033,698],{"class":109},[33,1035,123],{"class":122},[33,1037,127],{"class":126},[33,1039,988],{"class":43},[33,1041,48],{"class":47},[33,1043,588],{"class":39},[33,1045,44],{"class":321},[33,1047,48],{"class":47},[33,1049,1050],{"class":39},"catch",[33,1052,133],{"class":321},[33,1054,44],{"class":47},[33,1056,326],{"class":102},[33,1058,1060],{"class":1059},"s39Yj"," null",[33,1062,146],{"class":321},[33,1064,1065,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1091,1093,1095,1097],{"class":35,"line":332},[33,1066,1067],{"class":43},"      store",[33,1069,48],{"class":47},[33,1071,833],{"class":39},[33,1073,133],{"class":321},[33,1075,838],{"class":47},[33,1077,674],{"class":43},[33,1079,113],{"class":47},[33,1081,687],{"class":321},[33,1083,677],{"class":47},[33,1085,988],{"class":43},[33,1087,48],{"class":47},[33,1089,1090],{"class":43},"status",[33,1092,113],{"class":47},[33,1094,698],{"class":43},[33,1096,119],{"class":47},[33,1098,146],{"class":321},[33,1100,1101,1104,1107,1110,1112,1115,1118,1121,1123,1125,1127,1130],{"class":35,"line":357},[33,1102,1103],{"class":126},"      throw",[33,1105,1106],{"class":122}," new",[33,1108,1109],{"class":39}," Error",[33,1111,133],{"class":321},[33,1113,1114],{"class":136},"`",[33,1116,1117],{"class":140},"HTTP ",[33,1119,1120],{"class":136},"${",[33,1122,1017],{"class":43},[33,1124,48],{"class":136},[33,1126,1090],{"class":43},[33,1128,1129],{"class":136},"}`",[33,1131,146],{"class":321},[33,1133,1134],{"class":35,"line":379},[33,1135,418],{"class":47},[33,1137,1138,1141,1143,1145,1147,1149,1152,1154,1156,1158],{"class":35,"line":403},[33,1139,1140],{"class":126},"    return",[33,1142,988],{"class":43},[33,1144,48],{"class":47},[33,1146,588],{"class":39},[33,1148,860],{"class":321},[33,1150,1151],{"class":126},"as",[33,1153,951],{"class":666},[33,1155,236],{"class":47},[33,1157,924],{"class":666},[33,1159,260],{"class":47},[33,1161,1162,1164,1167,1169,1172,1174],{"class":35,"line":415},[33,1163,424],{"class":47},[33,1165,1166],{"class":126}," catch",[33,1168,296],{"class":321},[33,1170,1171],{"class":43},"err",[33,1173,352],{"class":321},[33,1175,312],{"class":47},[33,1177,1178,1181,1183,1185,1187,1189,1191,1193,1195,1197,1200,1202,1204,1206,1208],{"class":35,"line":421},[33,1179,1180],{"class":43},"    store",[33,1182,48],{"class":47},[33,1184,833],{"class":39},[33,1186,133],{"class":321},[33,1188,838],{"class":47},[33,1190,674],{"class":43},[33,1192,113],{"class":47},[33,1194,698],{"class":321},[33,1196,677],{"class":47},[33,1198,1199],{"class":39}," String",[33,1201,133],{"class":321},[33,1203,1171],{"class":43},[33,1205,352],{"class":321},[33,1207,863],{"class":47},[33,1209,146],{"class":321},[33,1211,1212,1215],{"class":35,"line":429},[33,1213,1214],{"class":126},"    throw",[33,1216,1217],{"class":43}," err\n",[33,1219,1220],{"class":35,"line":435},[33,1221,870],{"class":47},[33,1223,1225],{"class":35,"line":1224},15,[33,1226,432],{"class":47},[455,1228,1230,1231,1234],{"id":1229},"方向三最快速的-window__err__-暴露","方向三：最快速的 ",[19,1232,1233],{},"window.__ERR__"," 暴露",[15,1236,1237],{},"臨時救火用：",[24,1239,1241],{"className":93,"code":1240,"language":95,"meta":29,"style":29},"const app = createApp(App)\n\napp.config.errorHandler = (err, _instance, info) => {\n  ;(window as any).__ERR__ = { err, info, time: Date.now() }\n  console.error('[Vue Error]', err, info)\n}\n\nwindow.addEventListener('unhandledrejection', (e) => {\n  ;(window as any).__ERR__ = e.reason\n})\n",[19,1242,1243,1258,1262,1299,1349,1377,1381,1385,1416,1444],{"__ignoreMap":29},[33,1244,1245,1247,1250,1252,1255],{"class":35,"line":36},[33,1246,103],{"class":102},[33,1248,1249],{"class":109}," app",[33,1251,123],{"class":122},[33,1253,1254],{"class":39}," createApp",[33,1256,1257],{"class":43},"(App)\n",[33,1259,1260],{"class":35,"line":149},[33,1261,281],{"emptyLinePlaceholder":280},[33,1263,1264,1267,1269,1272,1274,1277,1279,1281,1283,1285,1288,1290,1293,1295,1297],{"class":35,"line":156},[33,1265,1266],{"class":43},"app",[33,1268,48],{"class":47},[33,1270,1271],{"class":43},"config",[33,1273,48],{"class":47},[33,1275,1276],{"class":39},"errorHandler",[33,1278,123],{"class":122},[33,1280,296],{"class":47},[33,1282,1171],{"class":777},[33,1284,113],{"class":47},[33,1286,1287],{"class":777}," _instance",[33,1289,113],{"class":47},[33,1291,1292],{"class":777}," info",[33,1294,583],{"class":47},[33,1296,326],{"class":102},[33,1298,329],{"class":47},[33,1300,1301,1304,1306,1309,1312,1315,1317,1319,1322,1324,1326,1329,1331,1333,1335,1337,1339,1341,1343,1345,1347],{"class":35,"line":284},[33,1302,1303],{"class":47},"  ;",[33,1305,133],{"class":321},[33,1307,1308],{"class":43},"window",[33,1310,1311],{"class":126}," as",[33,1313,1314],{"class":680}," any",[33,1316,583],{"class":321},[33,1318,48],{"class":47},[33,1320,1321],{"class":43},"__ERR__",[33,1323,123],{"class":122},[33,1325,106],{"class":47},[33,1327,1328],{"class":43}," err",[33,1330,113],{"class":47},[33,1332,1292],{"class":43},[33,1334,113],{"class":47},[33,1336,708],{"class":321},[33,1338,677],{"class":47},[33,1340,852],{"class":43},[33,1342,48],{"class":47},[33,1344,857],{"class":39},[33,1346,860],{"class":321},[33,1348,432],{"class":47},[33,1350,1351,1354,1356,1358,1360,1362,1365,1367,1369,1371,1373,1375],{"class":35,"line":290},[33,1352,1353],{"class":43},"  console",[33,1355,48],{"class":47},[33,1357,349],{"class":39},[33,1359,133],{"class":321},[33,1361,137],{"class":136},[33,1363,1364],{"class":140},"[Vue Error]",[33,1366,137],{"class":136},[33,1368,113],{"class":47},[33,1370,1328],{"class":43},[33,1372,113],{"class":47},[33,1374,1292],{"class":43},[33,1376,146],{"class":321},[33,1378,1379],{"class":35,"line":315},[33,1380,432],{"class":47},[33,1382,1383],{"class":35,"line":332},[33,1384,281],{"emptyLinePlaceholder":280},[33,1386,1387,1389,1391,1394,1396,1398,1401,1403,1405,1407,1410,1412,1414],{"class":35,"line":357},[33,1388,1308],{"class":43},[33,1390,48],{"class":47},[33,1392,1393],{"class":39},"addEventListener",[33,1395,133],{"class":43},[33,1397,137],{"class":136},[33,1399,1400],{"class":140},"unhandledrejection",[33,1402,137],{"class":136},[33,1404,113],{"class":47},[33,1406,296],{"class":47},[33,1408,1409],{"class":777},"e",[33,1411,583],{"class":47},[33,1413,326],{"class":102},[33,1415,329],{"class":47},[33,1417,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1439,1441],{"class":35,"line":379},[33,1419,1303],{"class":47},[33,1421,133],{"class":321},[33,1423,1308],{"class":43},[33,1425,1311],{"class":126},[33,1427,1314],{"class":680},[33,1429,583],{"class":321},[33,1431,48],{"class":47},[33,1433,1321],{"class":43},[33,1435,123],{"class":122},[33,1437,1438],{"class":43}," e",[33,1440,48],{"class":47},[33,1442,1443],{"class":43},"reason\n",[33,1445,1446,1448],{"class":35,"line":403},[33,1447,863],{"class":47},[33,1449,146],{"class":43},[15,1451,1452,1453,1455],{},"之後 DevTools 敲 ",[19,1454,1321],{}," 就能拿到最近一次錯誤。",[11,1457,1458],{"id":1458},"關鍵啟示",[15,1460,1461,1463],{},[19,1462,169],{}," 之所以好用，不是因為它是什麼黑魔法，而是 Nuxt 幫你做了兩件事：",[1465,1466,1467,1475],"ol",{},[185,1468,1469,85,1472,1474],{},[87,1470,1471],{},"統一錯誤格式",[19,1473,165],{}," 產出結構化物件",[185,1476,1477,1480,1481,67,1483,1486],{},[87,1478,1479],{},"統一錯誤通道","：所有 ",[19,1482,81],{},[19,1484,1485],{},"showError"," 的錯誤都匯流到同一個地方",[15,1488,1489,1490,1492,1493,1496,1497,1500,1501,1504,1505,90],{},"純 Vue 要得到同樣體驗，別想著去模擬 ",[19,1491,51],{}," 這個容器——重點在複製那兩件事：",[87,1494,1495],{},"API 層統一錯誤 schema + data fetching library 把 error 存成 ref","。做到這兩點，",[19,1498,1499],{},"useQuery().error"," 或你自己的 ",[19,1502,1503],{},"useDebugErrors().last"," 就是你的 ",[19,1506,169],{},[11,1508,1509],{"id":1509},"小結",[182,1511,1512,1523,1529],{},[185,1513,1514,1516,1517,1519,1520,1522],{},[19,1515,21],{}," 下 ",[19,1518,214],{}," 仍然有效，因為 ",[19,1521,51],{}," 不是 SSR 專屬",[185,1524,1525,1526,1528],{},"console 沒噴錯是 ",[19,1527,81],{}," 把 error 當資料吞起來，不是 bug",[185,1530,1531],{},"純 Vue 復刻這體驗：選對 data fetching library + 統一 API 錯誤格式，而不是去模擬 payload 本身",[1533,1534,1535],"style",{},"html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":29,"searchDepth":156,"depth":156,"links":1537},[1538,1539,1540,1541,1542,1548,1549],{"id":13,"depth":149,"text":13},{"id":74,"depth":149,"text":75},{"id":173,"depth":149,"text":174},{"id":221,"depth":149,"text":221},{"id":445,"depth":149,"text":446,"children":1543},[1544,1545,1546],{"id":457,"depth":156,"text":458},{"id":598,"depth":156,"text":599},{"id":1229,"depth":156,"text":1547},"方向三：最快速的 window.__ERR__ 暴露",{"id":1458,"depth":149,"text":1458},{"id":1509,"depth":149,"text":1509},"Nuxt","2026-04-18","在 ssr: false 的 Nuxt 專案裡，API 回傳 500 時 console 一片安靜，卻能透過 useNuxtApp().payload.error 看到完整錯誤內容。本文記錄這個現象的成因，以及純 Vue 專案如何復刻同樣的除錯體驗。",false,"md",null,{},"\u002Fblog\u002Fnuxt\u002Fuse-nuxt-app-payload-error",{"title":5,"description":1552},"blog\u002Fnuxt\u002Fuse-nuxt-app-payload-error",[1550,1561,1562,1563],"Vue","Debug","Error Handling","5nqAXWFUv7E_nHTa4tkps5093xDH53BKei5-Jk00w_c",1780512499430]