[{"data":1,"prerenderedAt":1464},["ShallowReactive",2],{"search-dialog-posts":3,"post-\u002Fposts\u002Fnuxt3-route-middleware":316,"post-surround-\u002Fposts\u002Fnuxt3-route-middleware":1457},[4,12,21,29,37,47,57,67,77,85,96,104,112,121,129,137,147,155,164,173,181,188,195,207,215,224,236,241,247,254,260,266,272,279,286,291,298,304,310],{"path":5,"title":6,"description":7,"category":8,"tags":9},"\u002Fposts\u002Fgit-commands-cheatsheet","Git 常用指令與終端機筆記","2018 年整理的 Git 學習筆記，包含基本終端機指令、Git 操作、分支、reset、SourceTree、Vim 與一些常用設定。","notes",[10,11],"Git","版本控制",{"path":13,"title":14,"description":15,"category":16,"tags":17},"\u002Fposts\u002Fjs-prototype-inheritance","JavaScript 物件導向：建構函式、prototype 與繼承三步驟","整理 ES5 時代用建構函式加 prototype 手刻物件導向的完整流程——封裝屬性與方法、共用函式為什麼要掛在 prototype 上、以及繼承的三個步驟（call 借用母類別建構子、Object.create 接上原型鏈、修正 constructor 指向），並附上物件縮寫與計算屬性這兩個順便學到的 ES6 寫法。","frontend",[18,19,20],"JavaScript","物件導向","prototype",{"path":22,"title":23,"description":24,"category":16,"tags":25},"\u002Fposts\u002Fflexbox-notes","Flexbox 對齊與彈性尺寸筆記","記錄 Flexbox 的主軸與交錯軸對齊、flex-direction、flex-wrap，以及 flex-grow、flex-shrink、flex-basis 等常用屬性。",[26,27,28],"CSS","Flexbox","版面",{"path":30,"title":31,"description":32,"category":16,"tags":33},"\u002Fposts\u002Fscreen-orientation-api","screen.orientation：現在判斷與鎖定螢幕方向的做法","整理現在 JavaScript 判斷螢幕方向的方式，包括 screen.orientation、change 事件與 lock()，並說明 RWD 使用 matchMedia 或 CSS orientation 會更適合。",[18,34,35,36],"RWD","螢幕方向","PWA",{"path":38,"title":39,"description":40,"category":16,"tags":41},"\u002Fposts\u002Fpinia-vuex-state-management","Pinia \u002F Vuex 狀態管理及差異","紀錄 Vuex、Pinia 的狀態管理概念、差異與用法。",[42,43,44,45,46],"Pinia","Vuex","Vue3","狀態管理","Nuxt",{"path":48,"title":49,"description":50,"category":8,"tags":51},"\u002Fposts\u002Fpyenv-python-version-management","Python 多版本管理 pyenv","紀錄 macOS 與 Windows 使用 pyenv 管理 Python 版本的方式。",[52,53,54,55,56],"Python","pyenv","macOS","Windows","版本管理",{"path":58,"title":59,"description":60,"category":8,"tags":61},"\u002Fposts\u002Fnpm-publish-vue2-component","npm 發布流程","紀錄 Vue 2 元件的 npm 發布、dist-tag 與打包流程。",[62,63,64,65,66],"npm","Vue2","Vue CLI","套件發布","MIT",{"path":68,"title":69,"description":70,"category":8,"tags":71},"\u002Fposts\u002Fmobile-debugging-today","現在怎麼 debug 手機網頁：從裝一堆工具到幾乎不用裝","對照 2022 年那套 scoop、iTunes、remotedebug-ios-webkit-adapter 的繁瑣設定，整理現在偵錯手機網頁的做法：Android 無線偵錯、iOS 的實際選項、on-device console、Playwright 裝置模擬，以及本機 HTTPS 的處理。",[72,73,74,75,76],"debug","iOS","Android","Chrome DevTools","Playwright",{"path":78,"title":79,"description":80,"category":16,"tags":81},"\u002Fposts\u002Fplaywright-vs-cypress","E2E 測試現在選什麼：Playwright 與 Cypress 的實際差異","對照幾年前用 Cypress 的做法，說明 Playwright 為什麼變成多數新專案的預設選擇：真正的多瀏覽器支援、免費的平行執行、trace viewer 除錯，以及兩者在架構上的根本差異與各自仍然適合的場景。",[76,82,83,84],"Cypress","E2E","測試",{"path":86,"title":87,"description":88,"category":89,"tags":90},"\u002Fposts\u002Fjs-encoding-today","現在處理編碼：TextEncoder、Uint8Array 的 Base64 方法與 URLSearchParams","整理 JavaScript 現代編碼方式，使用 TextEncoder、TextDecoder 與 Uint8Array 處理 UTF-8 與 Base64，並說明 Base64URL、URLSearchParams 與 encodeURIComponent 的差異。","research",[91,92,93,94,95,18],"編碼","TextEncoder","Base64","URLSearchParams","Uint8Array",{"path":97,"title":98,"description":99,"category":16,"tags":100},"\u002Fposts\u002Fdart-sass-migration","Dart Sass 遷移筆記：node-sass 之後怎麼裝 Sass","node-sass 與 LibSass 已停止維護，現在改用 Dart Sass。整理 webpack 與 Vite 的安裝方式，以及為什麼不再像 node-sass 一樣容易遇到 Node.js 版本與原生編譯問題。",[101,102,103],"Sass","Webpack","Vite",{"path":105,"title":106,"description":107,"category":16,"tags":108},"\u002Fposts\u002Fdayjs-vue3-computed","day.js 搭配 Vue 3 的日期格式化","整理 Vue 3 日期格式化的常見做法，使用 day.js 取代舊專案常見的 moment.js，並以 method 或 computed 取代 Vue 2 filter，也補充原生 Intl.DateTimeFormat 的使用方式。",[44,109,110,111],"day.js","日期","Intl",{"path":113,"title":114,"description":115,"category":16,"tags":116},"\u002Fposts\u002Fes-modules-today","ES Module 現在的樣子：Node ESM、exports 與 import maps","整理現代 JavaScript ES Module 的常見用法，包括 Node.js 原生 ESM、package.json exports、瀏覽器 import maps、JSON import attributes，以及 Vite 動態載入的注意事項。",[117,118,119,120,103,18],"ES Module","Node.js","import maps","exports",{"path":122,"title":123,"description":124,"category":89,"tags":125},"\u002Fposts\u002Fstructured-clone","structuredClone()：終於有內建的深層拷貝","整理 JavaScript structuredClone() 的深層拷貝用法，說明 Date、Map、Set、循環參考的處理方式，以及 Function、class 與 Vue reactive Proxy 的注意事項。",[18,126,127,128],"structuredClone","深拷貝","Vue",{"path":130,"title":131,"description":132,"category":16,"tags":133},"\u002Fposts\u002Fdynamic-import","JavaScript 動態載入：import() 與 Vue 非同步元件","整理靜態 import、動態 import() 與 Vue defineAsyncComponent 的使用方式，並簡單補充 Vite import.meta.glob 的用途。",[18,134,135,103,136],"ES Modules","Vue 3","Code Splitting",{"path":138,"title":139,"description":140,"category":16,"tags":141},"\u002Fposts\u002Fscreen-wake-lock-api","Screen Wake Lock API：不用套件也能讓頁面保持亮著","整理 Screen Wake Lock API 的基本用法，說明 HTTPS、頁面可見性、切換分頁後重新取得 wake lock，以及 Vue／Nuxt 計時器使用時的注意事項。",[142,128,143,144,145,146],"Wake Lock","Nuxt4","行動裝置","計時器","Web API",{"path":148,"title":149,"description":150,"category":16,"tags":151},"\u002Fposts\u002Fhtml-meta-boilerplate-today","現在的 HTML meta 標籤怎麼寫","整理現在一個新頁面該有的 meta 標籤骨架——viewport 補上 viewport-fit=cover 因應瀏海與圓角螢幕、color-scheme 讓表單控制項跟著深色模式換色、theme-color 決定行動瀏覽器工具列顏色，以及基本的 SEO 與 Open Graph 標籤。",[152,153,154],"HTML","meta","SEO",{"path":156,"title":157,"description":158,"category":8,"tags":159},"\u002Fposts\u002Fdocker-node-postgres","把 Node + PostgreSQL 的 backend 裝進 Docker：Dockerfile、.dockerignore 與 compose","從「本機裝了就跑」到打包成容器的通用做法——Dockerfile 每一行的理由與 layer cache 為什麼決定了指令順序、.dockerignore 漏掉 node_modules 對原生模組是災難級的問題、容器之間為什麼不能用 localhost 互相找到對方，以及怎麼驗證資料真的寫進了 volume。",[160,161,118,162,163],"Docker","Docker Compose","PostgreSQL","Express",{"path":165,"title":166,"description":167,"category":16,"tags":168},"\u002Fposts\u002Fnuxt-server-proxy","Nuxt 3 \u002F 4 用 Server API 做代理與轉發","整理 Nuxt 3 與 Nuxt 4 使用 server\u002Fapi 轉發第三方 API 的方式，可以避免瀏覽器 CORS 問題、隱藏 API Key，也能在伺服器端加入快取。",[169,143,170,171,172],"Nuxt3","CORS","Nitro","API",{"path":174,"title":175,"description":176,"category":16,"tags":177},"\u002Fposts\u002Fnuxt4-project-setup","Nuxt 4 建立專案：和 Nuxt 3 有哪些不同","整理 Nuxt 4 建立專案的基本流程與目錄結構，並對照 Nuxt 3 的差異，包括 app\u002F 目錄、路徑別名、TypeScript，以及目前常用的 ESLint 與 Tailwind CSS 4 設定。",[143,44,178,179,180],"TypeScript","ESLint","Tailwind CSS 4",{"path":182,"title":183,"description":184,"category":16,"tags":185},"\u002Fposts\u002Fnuxt3-route-middleware","Nuxt 3 \u002F 4 的 Route Middleware：進頁面前先做判斷","整理 Nuxt 3 與 Nuxt 4 的 Route Middleware，用 defineNuxtRouteMiddleware 在進入頁面前檢查登入狀態，搭配 navigateTo 重新導向，並說明一般 middleware、global middleware 與 definePageMeta 的用法。",[169,143,186,187],"Middleware","驗證",{"path":189,"title":190,"description":191,"category":16,"tags":192},"\u002Fposts\u002Fnuxt-route-rules-redirect","Nuxt 3 \u002F 4 的 routeRules：重新導向與頁面設定","整理 Nuxt 3 與 Nuxt 4 的 routeRules 用法，可以依照不同網址設定重新導向、預渲染、SSR 與快取，並簡單對照 Nuxt 2 的舊做法。",[169,143,193,194],"routeRules","重新導向",{"path":196,"title":197,"description":198,"category":16,"tags":199},"\u002Fposts\u002Fios-iframe-google-login-blank-page","iOS iframe 串接 Google 登入出現空白頁：Popup 與頁面回收問題排查紀錄","記錄 iframe 內串接 Google 第三方登入時，部分 iOS 裝置因背景頁面遭到回收並重新載入，導致 Popup 登入流程中斷，以及最後透過 Redirect 分流與登入狀態接回機制處理的過程。",[73,200,201,202,203,204,205,206],"Safari","iframe","Google Login","GIS","OAuth","Popup","Redirect",{"path":208,"title":209,"description":210,"category":16,"tags":211},"\u002Fposts\u002Fios-login-tab-return-behavior","iOS 第三方登入後回不到原頁？分頁開啟關係與回頁行為實測","記錄 iOS Safari 與 Chrome 在多分頁第三方登入流程中，登入頁關閉後不一定回到原頁的實測，以及分頁開啟關係、window.close 與 window.focus 的限制。",[73,200,212,202,204,201,213,214],"Chrome","window.close","window.focus",{"path":216,"title":217,"description":218,"category":16,"tags":219},"\u002Fposts\u002Fgoogle-app-about-blank-placeholder","Google App 內建瀏覽器：about:blank 佔位頁被關閉的排查紀錄","記錄 iOS Google App 內建瀏覽器在非同步開啟外部頁面時，about:blank 佔位分頁被自動關閉，導致第三方登入或外部導轉失敗的排查與解法。",[73,220,221,222,204,223],"Google App","window.open","about:blank","WebView",{"path":225,"title":226,"description":227,"category":228,"tags":229},"\u002Fposts\u002Fclaude-code-codex-cli-init-basics","AI 相關紀錄 v1｜Claude Code \u002F Codex CLI 初始化與基本概念","從 Claude Code 與 Codex CLI 開始，整理 CLI、Coding Agent、Context、\u002Finit 與 CLAUDE.md／AGENTS.md 這幾個一開始最容易搞混的概念，以及在既有專案中的安裝與啟動方式。","ai",[230,231,232,233,234,235],"AI","Claude Code","Codex CLI","Coding Agent","CLAUDE.md","AGENTS.md",{"path":237,"title":238,"description":239,"category":228,"tags":240},"\u002Fposts\u002Fclaude-agents-md-project-guidelines","AI 相關紀錄 v2｜CLAUDE.md \u002F AGENTS.md 怎麼寫？專案規範與實際範例","整理 CLAUDE.md 與 AGENTS.md 的用途、適合記錄的專案規範，以及可直接參考的設定範例。",[230,231,232,234,235],{"path":242,"title":243,"description":244,"category":228,"tags":245},"\u002Fposts\u002Fclaude-codex-config-locations","AI 相關紀錄 v3｜Claude Code `.claude\u002F` \u002F Codex CLI 設定位置：全域與專案設定基本概要介紹","整理 Claude Code 與 Codex CLI 的使用者層級、專案層級設定位置，以及 rules、skills、agents 等目錄的用途。",[230,231,232,246],"設定檔",{"path":248,"title":249,"description":250,"category":228,"tags":251},"\u002Fposts\u002Fclaude-settings-codex-config","AI 相關紀錄 v4｜Claude Code settings \u002F Codex config 基本設定","整理 Claude Code settings.json 與 Codex config.toml 的設定層級、權限控制和常見基本設定。",[230,231,232,252,253],"settings.json","config.toml",{"path":255,"title":256,"description":257,"category":228,"tags":258},"\u002Fposts\u002Fclaude-codex-rules-structure","AI 相關紀錄 v5｜Rules：專案規則多了之後怎麼拆？","整理 Claude Code Rules 與 Codex AGENTS.md 的拆分方式，說明何時該拆、怎麼依目錄管理專案規則。",[230,231,232,259,235],"Rules",{"path":261,"title":262,"description":263,"category":228,"tags":264},"\u002Fposts\u002Fclaude-codex-slash-commands","AI 相關紀錄 v6｜Commands \u002F Slash Commands：CLI 裡的 `\u002F` 指令是什麼？","介紹 Claude Code 與 Codex CLI 的 Slash Commands，整理常用內建指令、自訂指令與 Skills 的差異。",[230,231,232,265],"Slash Commands",{"path":267,"title":268,"description":269,"category":228,"tags":270},"\u002Fposts\u002Fclaude-codex-skills","AI 相關紀錄 v7｜Skills：把重複工作流程整理起來","介紹 Claude Code 與 Codex Skills，整理適合保存成 Skill 的重複流程、基本結構與使用方式。",[230,231,232,271],"Skills",{"path":273,"title":274,"description":275,"category":228,"tags":276},"\u002Fposts\u002Fclaude-codex-agents-subagents","AI 相關紀錄 v8｜Agent \u002F Subagent：什麼時候真的需要分工？","整理 Agent、Subagent 與 Custom Agent 的用途，說明何時適合分工，以及它們和 Rules、Skills 的差異。",[230,231,232,277,278],"Agent","Subagent",{"path":280,"title":281,"description":282,"category":228,"tags":283},"\u002Fposts\u002Fmcp-basics","AI 相關紀錄 v9｜MCP：讓 AI 使用其他工具與資料","介紹 MCP 如何讓 AI 使用外部工具與資料，並以 Pencil MCP 說明實際用途、權限風險及與 Skill 的差異。",[230,284,231,232,285],"MCP","Pencil",{"path":287,"title":288,"description":289,"category":228,"tags":290},"\u002Fposts\u002Fai-coding-agent-concepts-summary","AI 相關紀錄 v10｜基本概念小結論","用一篇快速整理 CLAUDE.md、AGENTS.md、設定檔、Rules、Commands、Skills、Agent 與 MCP 的角色和差異。",[230,231,232,284,271,277],{"path":292,"title":293,"description":294,"category":228,"tags":295},"\u002Fposts\u002Fclaude-code-codex-model-selection","Claude Code 與 Codex：不同模型怎麼選？","比較 Claude Code 與 Codex 不同模型的速度、能力與成本取捨，依工作難度整理實用的選擇方向。",[230,231,296,297],"Codex","模型選擇",{"path":299,"title":300,"description":301,"category":228,"tags":302},"\u002Fposts\u002Fclaude-model-selection","Claude 模型簡單怎麼選？","整理 Claude 各模型在速度、能力與使用情境上的差異，提供從輕量工作到複雜任務的選擇方向。",[230,231,303,297],"Claude",{"path":305,"title":306,"description":307,"category":228,"tags":308},"\u002Fposts\u002Fcodex-model-selection","Codex 模型簡單怎麼選？","整理 Codex 各模型在速度、能力與使用情境上的差異，提供從簡單修改到大型端到端任務的選擇方向。",[230,296,309,297],"OpenAI",{"path":311,"title":312,"description":313,"category":228,"tags":314},"\u002Fposts\u002Fclaude-code-codex-cli-command-cheatsheet","Claude Code \u002F Codex CLI 常用指令速查表","整理 Claude Code 與 Codex CLI 的常用斜線指令、快捷操作與進階功能，方便需要時快速查找。",[230,231,232,265,315],"速查表",{"id":317,"title":183,"body":318,"category":16,"description":184,"draft":1447,"extension":1448,"image":1449,"meta":1450,"navigation":445,"path":182,"publishedAt":1451,"relatedExperiments":1452,"seo":1453,"stem":1454,"tags":1455,"updatedAt":1451,"__hash__":1456},"posts\u002Fposts\u002F20260625.nuxt3-route-middleware.md",{"type":319,"value":320,"toc":1432},"minimark",[321,325,328,337,340,343,354,361,366,369,375,378,384,387,495,498,504,510,514,517,520,582,585,590,592,597,600,607,664,667,759,762,771,774,777,786,789,805,812,815,829,832,838,841,847,850,856,861,868,870,876,879,885,888,891,894,932,939,942,1038,1041,1047,1051,1054,1059,1061,1067,1070,1076,1080,1086,1089,1095,1098,1101,1106,1112,1116,1243,1246,1252,1255,1261,1264,1269,1272,1275,1282,1285,1351,1354,1375,1378,1383,1386,1391,1394,1428],[322,323,324],"p",{},"Nuxt 2 時期就有 Middleware，常拿來在進頁面前做登入驗證。",[322,326,327],{},"Middleware 可以先簡單理解成：",[329,330,331],"blockquote",{},[322,332,333],{},[334,335,336],"strong",{},"使用者準備進入某個頁面之前，先執行一段檢查。",[322,338,339],{},"最常見的用途就是登入驗證。",[322,341,342],{},"例如：",[344,345,351],"pre",{"className":346,"code":348,"language":349,"meta":350},[347],"language-text","使用者進入 \u002Fmember\n↓\n先檢查有沒有登入\n↓\n有登入 → 繼續進入\n沒登入 → 導到 \u002Flogin\n","text","",[352,353,348],"code",{"__ignoreMap":350},[322,355,356,357,360],{},"Nuxt 3 開始使用 ",[352,358,359],{},"defineNuxtRouteMiddleware()","，Nuxt 4 也延續相同方式。",[362,363,365],"h2",{"id":364},"建立登入驗證-middleware","建立登入驗證 Middleware",[322,367,368],{},"Nuxt 4 建立：",[344,370,373],{"className":371,"code":372,"language":349,"meta":350},[347],"app\u002Fmiddleware\u002Fauth.ts\n",[352,374,372],{"__ignoreMap":350},[322,376,377],{},"Nuxt 3 則是：",[344,379,382],{"className":380,"code":381,"language":349,"meta":350},[347],"middleware\u002Fauth.ts\n",[352,383,381],{"__ignoreMap":350},[322,385,386],{},"內容：",[344,388,392],{"className":389,"code":390,"language":391,"meta":350,"style":350},"language-ts shiki shiki-themes github-light github-dark-default","export default defineNuxtRouteMiddleware(() => {\n  const authState = useAuthState()\n\n  if (!authState.value) {\n    return navigateTo('\u002Flogin')\n  }\n})\n","ts",[352,393,394,421,440,447,462,481,487],{"__ignoreMap":350},[395,396,399,403,406,410,414,417],"span",{"class":397,"line":398},"line",1,[395,400,402],{"class":401},"sDgYj","export",[395,404,405],{"class":401}," default",[395,407,409],{"class":408},"sVjZL"," defineNuxtRouteMiddleware",[395,411,413],{"class":412},"sfpNf","(() ",[395,415,416],{"class":401},"=>",[395,418,420],{"class":419},"szmH1"," {\n",[395,422,424,427,431,434,437],{"class":397,"line":423},2,[395,425,426],{"class":401},"  const",[395,428,430],{"class":429},"sPgVT"," authState",[395,432,433],{"class":401}," =",[395,435,436],{"class":408}," useAuthState",[395,438,439],{"class":419},"()\n",[395,441,443],{"class":397,"line":442},3,[395,444,446],{"emptyLinePlaceholder":445},true,"\n",[395,448,450,453,456,459],{"class":397,"line":449},4,[395,451,452],{"class":401},"  if",[395,454,455],{"class":419}," (",[395,457,458],{"class":401},"!",[395,460,461],{"class":419},"authState.value) {\n",[395,463,465,468,471,474,478],{"class":397,"line":464},5,[395,466,467],{"class":401},"    return",[395,469,470],{"class":408}," navigateTo",[395,472,473],{"class":419},"(",[395,475,477],{"class":476},"sNbzA","'\u002Flogin'",[395,479,480],{"class":419},")\n",[395,482,484],{"class":397,"line":483},6,[395,485,486],{"class":419},"  }\n",[395,488,490,493],{"class":397,"line":489},7,[395,491,492],{"class":419},"}",[395,494,480],{"class":412},[322,496,497],{},"可以白話理解成：",[344,499,502],{"className":500,"code":501,"language":349,"meta":350},[347],"如果沒有登入\n→ 不要繼續進原本的頁面\n→ 改去 \u002Flogin\n",[352,503,501],{"__ignoreMap":350},[322,505,506,509],{},[352,507,508],{},"navigateTo()"," 就是 Nuxt 3 \u002F 4 用來做頁面導向的方式。",[362,511,513],{"id":512},"在頁面指定-middleware","在頁面指定 Middleware",[322,515,516],{},"不是每一頁都需要登入驗證。",[322,518,519],{},"例如只有會員頁需要，可以在頁面裡使用：",[344,521,525],{"className":522,"code":523,"language":524,"meta":350,"style":350},"language-vue shiki shiki-themes github-light github-dark-default","\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  middleware: 'auth'\n})\n\u003C\u002Fscript>\n","vue",[352,526,527,552,560,568,573],{"__ignoreMap":350},[395,528,529,532,536,540,543,546,549],{"class":397,"line":398},[395,530,531],{"class":419},"\u003C",[395,533,535],{"class":534},"sXQ1o","script",[395,537,539],{"class":538},"syv4q"," setup",[395,541,542],{"class":538}," lang",[395,544,545],{"class":419},"=",[395,547,548],{"class":476},"\"ts\"",[395,550,551],{"class":419},">\n",[395,553,554,557],{"class":397,"line":423},[395,555,556],{"class":408},"definePageMeta",[395,558,559],{"class":419},"({\n",[395,561,562,565],{"class":397,"line":442},[395,563,564],{"class":419},"  middleware: ",[395,566,567],{"class":476},"'auth'\n",[395,569,570],{"class":397,"line":449},[395,571,572],{"class":419},"})\n",[395,574,575,578,580],{"class":397,"line":464},[395,576,577],{"class":419},"\u003C\u002F",[395,579,535],{"class":534},[395,581,551],{"class":419},[322,583,584],{},"這樣進入這個頁面之前，就會先執行：",[344,586,588],{"className":587,"code":372,"language":349,"meta":350},[347],[352,589,372],{"__ignoreMap":350},[322,591,377],{},[344,593,595],{"className":594,"code":381,"language":349,"meta":350},[347],[352,596,381],{"__ignoreMap":350},[362,598,599],{"id":599},"搭配頁面的自訂設定",[322,601,602,603,606],{},"也可以自己在 ",[352,604,605],{},"definePageMeta()"," 加上設定：",[344,608,610],{"className":522,"code":609,"language":524,"meta":350,"style":350},"\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  middleware: 'auth',\n  requiresAuth: true\n})\n\u003C\u002Fscript>\n",[352,611,612,628,634,644,652,656],{"__ignoreMap":350},[395,613,614,616,618,620,622,624,626],{"class":397,"line":398},[395,615,531],{"class":419},[395,617,535],{"class":534},[395,619,539],{"class":538},[395,621,542],{"class":538},[395,623,545],{"class":419},[395,625,548],{"class":476},[395,627,551],{"class":419},[395,629,630,632],{"class":397,"line":423},[395,631,556],{"class":408},[395,633,559],{"class":419},[395,635,636,638,641],{"class":397,"line":442},[395,637,564],{"class":419},[395,639,640],{"class":476},"'auth'",[395,642,643],{"class":419},",\n",[395,645,646,649],{"class":397,"line":449},[395,647,648],{"class":419},"  requiresAuth: ",[395,650,651],{"class":429},"true\n",[395,653,654],{"class":397,"line":464},[395,655,572],{"class":419},[395,657,658,660,662],{"class":397,"line":483},[395,659,577],{"class":419},[395,661,535],{"class":534},[395,663,551],{"class":419},[322,665,666],{},"Middleware 裡就能讀：",[344,668,670],{"className":389,"code":669,"language":391,"meta":350,"style":350},"export default defineNuxtRouteMiddleware((to) => {\n  if (to.meta.requiresAuth) {\n    const authState = useAuthState()\n\n    if (!authState.value) {\n      return navigateTo('\u002Flogin')\n    }\n  }\n})\n",[352,671,672,694,701,714,718,729,742,747,752],{"__ignoreMap":350},[395,673,674,676,678,680,683,687,690,692],{"class":397,"line":398},[395,675,402],{"class":401},[395,677,405],{"class":401},[395,679,409],{"class":408},[395,681,682],{"class":412},"((",[395,684,686],{"class":685},"sjlk-","to",[395,688,689],{"class":412},") ",[395,691,416],{"class":401},[395,693,420],{"class":419},[395,695,696,698],{"class":397,"line":423},[395,697,452],{"class":401},[395,699,700],{"class":419}," (to.meta.requiresAuth) {\n",[395,702,703,706,708,710,712],{"class":397,"line":442},[395,704,705],{"class":401},"    const",[395,707,430],{"class":429},[395,709,433],{"class":401},[395,711,436],{"class":408},[395,713,439],{"class":419},[395,715,716],{"class":397,"line":449},[395,717,446],{"emptyLinePlaceholder":445},[395,719,720,723,725,727],{"class":397,"line":464},[395,721,722],{"class":401},"    if",[395,724,455],{"class":419},[395,726,458],{"class":401},[395,728,461],{"class":419},[395,730,731,734,736,738,740],{"class":397,"line":483},[395,732,733],{"class":401},"      return",[395,735,470],{"class":408},[395,737,473],{"class":419},[395,739,477],{"class":476},[395,741,480],{"class":419},[395,743,744],{"class":397,"line":489},[395,745,746],{"class":419},"    }\n",[395,748,750],{"class":397,"line":749},8,[395,751,486],{"class":419},[395,753,755,757],{"class":397,"line":754},9,[395,756,492],{"class":419},[395,758,480],{"class":412},[322,760,761],{},"這裡的：",[344,763,765],{"className":389,"code":764,"language":391,"meta":350,"style":350},"to\n",[352,766,767],{"__ignoreMap":350},[395,768,769],{"class":397,"line":398},[395,770,764],{"class":419},[322,772,773],{},"代表「準備前往的頁面」。",[322,775,776],{},"所以：",[344,778,780],{"className":389,"code":779,"language":391,"meta":350,"style":350},"to.meta.requiresAuth\n",[352,781,782],{"__ignoreMap":350},[395,783,784],{"class":397,"line":398},[395,785,779],{"class":419},[322,787,788],{},"就是查看那個頁面有沒有設定：",[344,790,792],{"className":389,"code":791,"language":391,"meta":350,"style":350},"requiresAuth: true\n",[352,793,794],{"__ignoreMap":350},[395,795,796,800,803],{"class":397,"line":398},[395,797,799],{"class":798},"sYRkA","requiresAuth",[395,801,802],{"class":419},": ",[395,804,651],{"class":429},[362,806,808,809],{"id":807},"全站都要執行global","全站都要執行：",[352,810,811],{},".global",[322,813,814],{},"如果不想每個頁面都寫：",[344,816,818],{"className":389,"code":817,"language":391,"meta":350,"style":350},"middleware: 'auth'\n",[352,819,820],{"__ignoreMap":350},[395,821,822,825,827],{"class":397,"line":398},[395,823,824],{"class":798},"middleware",[395,826,802],{"class":419},[395,828,567],{"class":476},[322,830,831],{},"可以把檔名改成：",[344,833,836],{"className":834,"code":835,"language":349,"meta":350},[347],"auth.global.ts\n",[352,837,835],{"__ignoreMap":350},[322,839,840],{},"Nuxt 4：",[344,842,845],{"className":843,"code":844,"language":349,"meta":350},[347],"app\u002Fmiddleware\u002Fauth.global.ts\n",[352,846,844],{"__ignoreMap":350},[322,848,849],{},"Nuxt 3：",[344,851,854],{"className":852,"code":853,"language":349,"meta":350},[347],"middleware\u002Fauth.global.ts\n",[352,855,853],{"__ignoreMap":350},[322,857,858,860],{},[352,859,811],{}," 代表：",[329,862,863],{},[322,864,865],{},[334,866,867],{},"每次切換頁面都執行這支 middleware。",[322,869,342],{},[344,871,874],{"className":872,"code":873,"language":349,"meta":350},[347],"middleware\u002F\n├── auth.ts\n└── analytics.global.ts\n",[352,875,873],{"__ignoreMap":350},[322,877,878],{},"可以理解成：",[344,880,883],{"className":881,"code":882,"language":349,"meta":350},[347],"auth.ts\n→ 頁面有指定才執行\n\nanalytics.global.ts\n→ 每個頁面都執行\n",[352,884,882],{"__ignoreMap":350},[322,886,887],{},"如果整個後台都需要登入驗證，也可以考慮使用 global middleware。",[362,889,890],{"id":890},"注意不要一直重新導向",[322,892,893],{},"假設寫成：",[344,895,897],{"className":389,"code":896,"language":391,"meta":350,"style":350},"export default defineNuxtRouteMiddleware(() => {\n  return navigateTo('\u002Flogin')\n})\n",[352,898,899,913,926],{"__ignoreMap":350},[395,900,901,903,905,907,909,911],{"class":397,"line":398},[395,902,402],{"class":401},[395,904,405],{"class":401},[395,906,409],{"class":408},[395,908,413],{"class":412},[395,910,416],{"class":401},[395,912,420],{"class":419},[395,914,915,918,920,922,924],{"class":397,"line":423},[395,916,917],{"class":401},"  return",[395,919,470],{"class":408},[395,921,473],{"class":419},[395,923,477],{"class":476},[395,925,480],{"class":419},[395,927,928,930],{"class":397,"line":442},[395,929,492],{"class":419},[395,931,480],{"class":412},[322,933,934,935,938],{},"如果連 ",[352,936,937],{},"\u002Flogin"," 本身也會執行這支 middleware，就可能一直重新導向。",[322,940,941],{},"因此通常要先判斷：",[344,943,945],{"className":389,"code":944,"language":391,"meta":350,"style":350},"export default defineNuxtRouteMiddleware((to) => {\n  const authState = useAuthState()\n\n  if (\n    !authState.value &&\n    to.path !== '\u002Flogin'\n  ) {\n    return navigateTo('\u002Flogin')\n  }\n})\n",[352,946,947,965,977,981,988,999,1010,1015,1027,1031],{"__ignoreMap":350},[395,948,949,951,953,955,957,959,961,963],{"class":397,"line":398},[395,950,402],{"class":401},[395,952,405],{"class":401},[395,954,409],{"class":408},[395,956,682],{"class":412},[395,958,686],{"class":685},[395,960,689],{"class":412},[395,962,416],{"class":401},[395,964,420],{"class":419},[395,966,967,969,971,973,975],{"class":397,"line":423},[395,968,426],{"class":401},[395,970,430],{"class":429},[395,972,433],{"class":401},[395,974,436],{"class":408},[395,976,439],{"class":419},[395,978,979],{"class":397,"line":442},[395,980,446],{"emptyLinePlaceholder":445},[395,982,983,985],{"class":397,"line":449},[395,984,452],{"class":401},[395,986,987],{"class":419}," (\n",[395,989,990,993,996],{"class":397,"line":464},[395,991,992],{"class":401},"    !",[395,994,995],{"class":419},"authState.value ",[395,997,998],{"class":401},"&&\n",[395,1000,1001,1004,1007],{"class":397,"line":483},[395,1002,1003],{"class":419},"    to.path ",[395,1005,1006],{"class":401},"!==",[395,1008,1009],{"class":476}," '\u002Flogin'\n",[395,1011,1012],{"class":397,"line":489},[395,1013,1014],{"class":419},"  ) {\n",[395,1016,1017,1019,1021,1023,1025],{"class":397,"line":749},[395,1018,467],{"class":401},[395,1020,470],{"class":408},[395,1022,473],{"class":419},[395,1024,477],{"class":476},[395,1026,480],{"class":419},[395,1028,1029],{"class":397,"line":754},[395,1030,486],{"class":419},[395,1032,1034,1036],{"class":397,"line":1033},10,[395,1035,492],{"class":419},[395,1037,480],{"class":412},[322,1039,1040],{},"意思就是：",[344,1042,1045],{"className":1043,"code":1044,"language":349,"meta":350},[347],"沒有登入\n而且現在不是登入頁\n→ 才導向 \u002Flogin\n",[352,1046,1044],{"__ignoreMap":350},[362,1048,1050],{"id":1049},"route-middleware-和-server-middleware-不一樣","Route Middleware 和 Server Middleware 不一樣",[322,1052,1053],{},"Nuxt 裡其實有兩種名字很像的 middleware。",[1055,1056,1058],"h3",{"id":1057},"route-middleware","Route Middleware",[322,1060,840],{},[344,1062,1065],{"className":1063,"code":1064,"language":349,"meta":350},[347],"app\u002Fmiddleware\u002F\n",[352,1066,1064],{"__ignoreMap":350},[322,1068,1069],{},"用途範例：",[344,1071,1074],{"className":1072,"code":1073,"language":349,"meta":350},[347],"進入頁面前檢查\n登入驗證\n權限判斷\n頁面重新導向\n",[352,1075,1073],{"__ignoreMap":350},[1055,1077,1079],{"id":1078},"server-middleware","Server Middleware",[344,1081,1084],{"className":1082,"code":1083,"language":349,"meta":350},[347],"server\u002Fmiddleware\u002F\n",[352,1085,1083],{"__ignoreMap":350},[322,1087,1088],{},"用途比較像：",[344,1090,1093],{"className":1091,"code":1092,"language":349,"meta":350},[347],"處理 HTTP request\n記錄 log\n設定 request context\nServer 端驗證\n",[352,1094,1092],{"__ignoreMap":350},[322,1096,1097],{},"兩個不是同一套東西。",[322,1099,1100],{},"如果是：",[329,1102,1103],{},[322,1104,1105],{},"使用者能不能進這個 Vue 頁面？",[322,1107,1108,1109,1111],{},"通常是在講 ",[334,1110,1058],{},"。",[362,1113,1115],{"id":1114},"nuxt-234-對照","Nuxt 2、3、4 對照",[1117,1118,1119,1137],"table",{},[1120,1121,1122],"thead",{},[1123,1124,1125,1128,1131,1134],"tr",{},[1126,1127],"th",{},[1126,1129,1130],{},"Nuxt 2",[1126,1132,1133],{},"Nuxt 3",[1126,1135,1136],{},"Nuxt 4",[1138,1139,1140,1160,1176,1193,1209,1226],"tbody",{},[1123,1141,1142,1146,1151,1155],{},[1143,1144,1145],"td",{},"Route Middleware 位置",[1143,1147,1148],{},[352,1149,1150],{},"middleware\u002F",[1143,1152,1153],{},[352,1154,1150],{},[1143,1156,1157],{},[352,1158,1159],{},"app\u002Fmiddleware\u002F",[1123,1161,1162,1165,1168,1172],{},[1143,1163,1164],{},"Middleware 寫法",[1143,1166,1167],{},"context function",[1143,1169,1170],{},[352,1171,359],{},[1143,1173,1174],{},[352,1175,359],{},[1123,1177,1178,1180,1185,1189],{},[1143,1179,194],{},[1143,1181,1182],{},[352,1183,1184],{},"redirect()",[1143,1186,1187],{},[352,1188,508],{},[1143,1190,1191],{},[352,1192,508],{},[1123,1194,1195,1198,1201,1205],{},[1143,1196,1197],{},"頁面設定",[1143,1199,1200],{},"Options API",[1143,1202,1203],{},[352,1204,605],{},[1143,1206,1207],{},[352,1208,605],{},[1123,1210,1211,1214,1217,1222],{},[1143,1212,1213],{},"全域 Middleware",[1143,1215,1216],{},"另外設定",[1143,1218,1219],{},[352,1220,1221],{},".global.ts",[1143,1223,1224],{},[352,1225,1221],{},[1123,1227,1228,1231,1234,1239],{},[1143,1229,1230],{},"讀取頁面 meta",[1143,1232,1233],{},"舊 route meta",[1143,1235,1236],{},[352,1237,1238],{},"to.meta",[1143,1240,1241],{},[352,1242,1238],{},[322,1244,1245],{},"所以從 Nuxt 3 升到 Nuxt 4：",[344,1247,1250],{"className":1248,"code":1249,"language":349,"meta":350},[347],"Middleware 的概念和寫法幾乎沒有變。\n",[352,1251,1249],{"__ignoreMap":350},[322,1253,1254],{},"主要只是檔案從：",[344,1256,1259],{"className":1257,"code":1258,"language":349,"meta":350},[347],"middleware\u002F\n",[352,1260,1258],{"__ignoreMap":350},[322,1262,1263],{},"搬到：",[344,1265,1267],{"className":1266,"code":1064,"language":349,"meta":350},[347],[352,1268,1064],{"__ignoreMap":350},[362,1270,1271],{"id":1271},"小結",[322,1273,1274],{},"可以把 Route Middleware 記成：",[329,1276,1277],{},[322,1278,1279],{},[334,1280,1281],{},"進頁面之前，先幫我檢查一下。",[322,1283,1284],{},"例如登入驗證：",[344,1286,1287],{"className":389,"code":390,"language":391,"meta":350,"style":350},[352,1288,1289,1303,1315,1319,1329,1341,1345],{"__ignoreMap":350},[395,1290,1291,1293,1295,1297,1299,1301],{"class":397,"line":398},[395,1292,402],{"class":401},[395,1294,405],{"class":401},[395,1296,409],{"class":408},[395,1298,413],{"class":412},[395,1300,416],{"class":401},[395,1302,420],{"class":419},[395,1304,1305,1307,1309,1311,1313],{"class":397,"line":423},[395,1306,426],{"class":401},[395,1308,430],{"class":429},[395,1310,433],{"class":401},[395,1312,436],{"class":408},[395,1314,439],{"class":419},[395,1316,1317],{"class":397,"line":442},[395,1318,446],{"emptyLinePlaceholder":445},[395,1320,1321,1323,1325,1327],{"class":397,"line":449},[395,1322,452],{"class":401},[395,1324,455],{"class":419},[395,1326,458],{"class":401},[395,1328,461],{"class":419},[395,1330,1331,1333,1335,1337,1339],{"class":397,"line":464},[395,1332,467],{"class":401},[395,1334,470],{"class":408},[395,1336,473],{"class":419},[395,1338,477],{"class":476},[395,1340,480],{"class":419},[395,1342,1343],{"class":397,"line":483},[395,1344,486],{"class":419},[395,1346,1347,1349],{"class":397,"line":489},[395,1348,492],{"class":419},[395,1350,480],{"class":412},[322,1352,1353],{},"頁面需要使用時：",[344,1355,1357],{"className":389,"code":1356,"language":391,"meta":350,"style":350},"definePageMeta({\n  middleware: 'auth'\n})\n",[352,1358,1359,1365,1371],{"__ignoreMap":350},[395,1360,1361,1363],{"class":397,"line":398},[395,1362,556],{"class":408},[395,1364,559],{"class":419},[395,1366,1367,1369],{"class":397,"line":423},[395,1368,564],{"class":419},[395,1370,567],{"class":476},[395,1372,1373],{"class":397,"line":442},[395,1374,572],{"class":419},[322,1376,1377],{},"如果每一頁都要執行，就把檔名改成：",[344,1379,1381],{"className":1380,"code":835,"language":349,"meta":350},[347],[352,1382,835],{"__ignoreMap":350},[322,1384,1385],{},"Nuxt 3 與 Nuxt 4 的寫法基本相同，最大的差別只是 Nuxt 4 的 Route Middleware 放在：",[344,1387,1389],{"className":1388,"code":1064,"language":349,"meta":350},[347],[352,1390,1064],{"__ignoreMap":350},[362,1392,1393],{"id":1393},"參考資料",[1395,1396,1397,1407,1414,1421],"ul",{},[1398,1399,1400],"li",{},[1401,1402,1406],"a",{"href":1403,"rel":1404},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fdirectory-structure\u002Fapp\u002Fmiddleware",[1405],"nofollow","Nuxt 4：Route Middleware",[1398,1408,1409],{},[1401,1410,1413],{"href":1411,"rel":1412},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fapi\u002Futils\u002Fdefine-nuxt-route-middleware",[1405],"Nuxt 4：defineNuxtRouteMiddleware",[1398,1415,1416],{},[1401,1417,1420],{"href":1418,"rel":1419},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fapi\u002Futils\u002Fdefine-page-meta",[1405],"Nuxt 4：definePageMeta",[1398,1422,1423],{},[1401,1424,1427],{"href":1425,"rel":1426},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F3.x\u002Fdirectory-structure\u002Fmiddleware",[1405],"Nuxt 3：Route Middleware",[1429,1430,1431],"style",{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .sVjZL, html code.shiki .sVjZL{--shiki-default:#6F42C1;--shiki-dark:#D2A8FF}html pre.shiki code .sfpNf, html code.shiki .sfpNf{--shiki-default:#24292E;--shiki-dark:#FFA657}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .sXQ1o, html code.shiki .sXQ1o{--shiki-default:#22863A;--shiki-dark:#7EE787}html pre.shiki code .syv4q, html code.shiki .syv4q{--shiki-default:#6F42C1;--shiki-dark:#79C0FF}html pre.shiki code .sjlk-, html code.shiki .sjlk-{--shiki-default:#E36209;--shiki-dark:#FFA657}html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}",{"title":350,"searchDepth":442,"depth":442,"links":1433},[1434,1435,1436,1437,1439,1440,1444,1445,1446],{"id":364,"depth":423,"text":365},{"id":512,"depth":423,"text":513},{"id":599,"depth":423,"text":599},{"id":807,"depth":423,"text":1438},"全站都要執行：.global",{"id":890,"depth":423,"text":890},{"id":1049,"depth":423,"text":1050,"children":1441},[1442,1443],{"id":1057,"depth":442,"text":1058},{"id":1078,"depth":442,"text":1079},{"id":1114,"depth":423,"text":1115},{"id":1271,"depth":423,"text":1271},{"id":1393,"depth":423,"text":1393},false,"md",null,{},"2026-06-25",[],{"title":183,"description":184},"posts\u002F20260625.nuxt3-route-middleware",[169,143,186,187],"SLED1J8hJYmhjJBPdrVY_2-l1LrRsn2ntCQ9g50Euss",[1458,1461],{"title":190,"path":189,"stem":1459,"description":191,"category":16,"publishedAt":1460,"children":-1},"posts\u002F20260629.nuxt-route-rules-redirect","2026-06-29",{"title":175,"path":174,"stem":1462,"description":176,"category":16,"publishedAt":1463,"children":-1},"posts\u002F20260620.nuxt4-project-setup","2026-06-20",1791786901004]