[{"data":1,"prerenderedAt":530},["ShallowReactive",2],{"insight-\u002Finsights\u002Fprocess-before-features":3,"insight-related-\u002Finsights\u002Fprocess-before-features":128},{"id":4,"title":5,"body":6,"category":116,"date":117,"description":118,"draft":119,"extension":120,"meta":121,"navigation":122,"path":123,"readingTime":124,"seo":125,"stem":126,"__hash__":127},"insights\u002Finsights\u002Fprocess-before-features.md","先問流程，再問功能",{"type":7,"value":8,"toc":107},"minimark",[9,13,16,20,26,29,34,37,42,45,48,51,64,67,70,73,76,79,82,85,89,92,101,104],[10,11,12],"p",{},"第一次和企業聊系統的時候，最常拿到的是一份功能清單。上面寫著「要有登入」「要有報表」「要有通知」。清單本身沒有問題，問題是它通常是從別人的系統抄來的，而不是從自己的流程長出來的。",[10,14,15],{},"所以我們現在習慣先把功能清單放在旁邊，改問另一組問題。",[17,18,19],"h2",{"id":19},"先問的三件事",[10,21,22],{},[23,24,25],"strong",{},"這件事現在是誰在做？",[10,27,28],{},"不是「哪個部門」，是哪一個人。找到那個人，通常也就找到了所有的例外狀況。",[10,30,31],{},[23,32,33],{},"他現在用什麼做？",[10,35,36],{},"答案很常是 Excel、LINE、紙本，或是三個一起。這不是落後，而是資訊。它代表現有工具至少在某個地方是好用的，不然不會活這麼久。",[10,38,39],{},[23,40,41],{},"做錯的時候會怎樣？",[10,43,44],{},"有些環節做錯只是重做一次，有些做錯會直接影響出貨或收錢。這個問題的答案，會決定哪裡需要防呆、哪裡可以放寬。",[17,46,47],{"id":47},"功能清單的問題",[10,49,50],{},"功能清單描述的是系統，流程描述的是人。兩者之間往往差了一段沒有人記錄下來的東西：",[52,53,54,58,61],"ul",{},[55,56,57],"li",{},"那個「先傳照片到群組給主管看一下」的步驟",[55,59,60],{},"那個「星期五下午單子會特別多」的節奏",[55,62,63],{},"那個「新人前兩週都會做錯同一件事」的現象",[10,65,66],{},"這些東西不會出現在需求文件裡，但它們決定了系統上線之後會不會被繞過。",[17,68,69],{"id":69},"一個實際的例子",[10,71,72],{},"有一種很常見的需求是「我要一個報表系統」。",[10,74,75],{},"如果直接照做，結果通常是一個有很多圖表的頁面，然後三個月後沒有人打開。",[10,77,78],{},"但如果先問「你做這份報表是為了回答什麼問題」，答案可能是「我只是想知道這週有沒有哪一天特別忙」。",[10,80,81],{},"那麼需要的可能不是報表系統，而是一個每週寄一次、只有五行字的信。",[10,83,84],{},"這兩件事的開發成本差了不只十倍，而後者比較有機會真的被看。",[17,86,88],{"id":87},"流程問清楚之後功能反而好決定","流程問清楚之後，功能反而好決定",[10,90,91],{},"把流程攤開之後，功能清單通常會發生兩件事：",[93,94,95,98],"ol",{},[55,96,97],{},"有一半的功能會被刪掉，因為它們解決的是不存在的問題。",[55,99,100],{},"會冒出兩三個原本沒寫的功能，而且它們才是真正的重點。",[10,102,103],{},"這個過程不需要很久。多數情況下，把一天的實際工作走過一次，大概兩個小時就夠了。",[10,105,106],{},"比起花兩個月做一個沒人用的系統，這兩個小時很便宜。",{"title":108,"searchDepth":109,"depth":109,"links":110},"",3,[111,113,114,115],{"id":19,"depth":112,"text":19},2,{"id":47,"depth":112,"text":47},{"id":69,"depth":112,"text":69},{"id":87,"depth":112,"text":88},"企業數位化","2026-08-12","很多系統做完之後沒人用，不是因為功能不夠，而是因為它從一開始就照著功能清單設計，而不是照著人實際的工作方式設計。",false,"md",{},true,"\u002Finsights\u002Fprocess-before-features",6,{"title":5,"description":118},"insights\u002Fprocess-before-features","7-zsY3oWaYKvoW50feBNsUNNqp91NdKpd-dtowIB1rU",[129,280],{"id":130,"title":131,"body":132,"category":271,"date":272,"description":273,"draft":119,"extension":120,"meta":274,"navigation":122,"path":275,"readingTime":276,"seo":277,"stem":278,"__hash__":279},"insights\u002Finsights\u002Fpermissions-are-not-a-final-step.md","權限不是上線前才補的東西",{"type":7,"value":133,"toc":264},[134,137,140,143,146,152,158,164,167,170,173,176,230,233,239,242,245,248,251,254,257,260],[10,135,136],{},"在系統開發裡，權限常常被排在很後面。先把畫面做出來、先讓流程跑得動，權限「之後再加」。",[10,138,139],{},"聽起來很合理，但實際上這個順序有一個問題：權限不是一層可以外掛的東西，它會決定資料怎麼查、怎麼存、怎麼分頁。等到功能都做完才回頭處理，通常會發現要改的不是幾行判斷，而是整個資料存取的寫法。",[17,141,142],{"id":142},"三個不同層次的權限",[10,144,145],{},"把權限分開來想，會比較容易決定每一層要做什麼。",[10,147,148,151],{},[23,149,150],{},"介面層","：這個按鈕要不要顯示。這是最表面的一層，也是唯一一層可以被使用者繞過的。",[10,153,154,157],{},[23,155,156],{},"應用層","：這個請求可不可以執行。伺服器收到請求時要自己判斷，不能相信前端已經檢查過。",[10,159,160,163],{},[23,161,162],{},"資料層","：這筆資料可不可以被讀到。資料庫本身也應該有規則，這樣即使應用層有漏洞，也不會直接把整張表倒出去。",[10,165,166],{},"只做第一層，等於沒有做。三層都做，才有辦法在某一層出錯的時候還有東西擋著。",[17,168,169],{"id":169},"從最小權限開始",[10,171,172],{},"比較安全的預設是：一開始什麼都不給，再依角色一項一項加回來。",[10,174,175],{},"反過來做（先全部開放，再逐一關掉）幾乎一定會漏掉東西，而且漏掉的地方通常不會有人回報，因為使用者不會抱怨「我看到了不該看的資料」。",[177,178,182],"pre",{"className":179,"code":180,"language":181,"meta":108,"style":108},"language-sql shiki shiki-themes github-light github-light","-- 不是「這個角色不能看什麼」，\n-- 而是「這個角色只能看什麼」\nselect * from orders\nwhere store_id = current_store_id();\n","sql",[183,184,185,194,199,215],"code",{"__ignoreMap":108},[186,187,190],"span",{"class":188,"line":189},"line",1,[186,191,193],{"class":192},"sJ8bj","-- 不是「這個角色不能看什麼」，\n",[186,195,196],{"class":188,"line":112},[186,197,198],{"class":192},"-- 而是「這個角色只能看什麼」\n",[186,200,201,205,208,211],{"class":188,"line":109},[186,202,204],{"class":203},"sCydW","select",[186,206,207],{"class":203}," *",[186,209,210],{"class":203}," from",[186,212,214],{"class":213},"sKWpL"," orders\n",[186,216,218,221,224,227],{"class":188,"line":217},4,[186,219,220],{"class":203},"where",[186,222,223],{"class":213}," store_id ",[186,225,226],{"class":203},"=",[186,228,229],{"class":213}," current_store_id();\n",[17,231,232],{"id":232},"前端隱藏不是安全機制",[10,234,235,238],{},[183,236,237],{},"v-if=\"isAdmin\""," 只是讓畫面乾淨，不是保護。任何人打開開發者工具都可以看到前端拿到了哪些資料。",[10,240,241],{},"判斷方式很簡單：如果把這個請求直接用 curl 打一次，會發生什麼事？如果答案是「會拿到資料」，那這個地方就沒有真的被保護。",[17,243,244],{"id":244},"環境也是權限的一部分",[10,246,247],{},"正式環境的金鑰不應該出現在開發環境，開發用的測試帳號也不應該出現在正式資料庫。這兩件事聽起來很基本，但在時間趕的時候特別容易被跳過。",[10,249,250],{},"比較實際的做法是一開始就把環境變數分好，讓「拿錯金鑰」這件事在流程上就做不到，而不是靠記得。",[17,252,253],{"id":253},"什麼時候處理",[10,255,256],{},"我們的習慣是在畫資料表的時候就一起決定：這張表有哪些角色會碰到、各自能做什麼。",[10,258,259],{},"這個階段做這件事幾乎不花時間，因為還沒有任何程式碼需要改。等到上線前一週才想，成本就完全不一樣了。",[261,262,263],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sCydW, html code.shiki .sCydW{--shiki-default:#D73A49;--shiki-dark:#D73A49}html pre.shiki code .sKWpL, html code.shiki .sKWpL{--shiki-default:#24292E;--shiki-dark:#24292E}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":108,"searchDepth":109,"depth":109,"links":265},[266,267,268,269,270],{"id":142,"depth":112,"text":142},{"id":169,"depth":112,"text":169},{"id":232,"depth":112,"text":232},{"id":244,"depth":112,"text":244},{"id":253,"depth":112,"text":253},"資安","2026-08-05","「先把功能做出來，權限之後再說」是很常見的順序，但它幾乎一定會讓你在最後一週重寫一次資料存取邏輯。",{},"\u002Finsights\u002Fpermissions-are-not-a-final-step",7,{"title":131,"description":273},"insights\u002Fpermissions-are-not-a-final-step","zWtmc13aqFoDwMCfylZRS7XYckT8GRrG34i03fYoBkQ",{"id":281,"title":282,"body":283,"category":522,"date":523,"description":524,"draft":119,"extension":120,"meta":525,"navigation":122,"path":526,"readingTime":396,"seo":527,"stem":528,"__hash__":529},"insights\u002Finsights\u002Fhow-much-javascript-does-a-website-need.md","一個企業網站到底需要多少 JavaScript",{"type":7,"value":284,"toc":515},[285,288,291,294,297,300,303,307,310,321,324,327,330,473,480,483,486,489,500,503,506,509,512],[10,286,287],{},"做企業形象網站的時候，一個很值得先問的問題是：這個網站真正需要「互動」的地方有幾個？",[10,289,290],{},"答案通常是三個以內：導覽選單、捲動進場、還有一兩個展開收合。",[10,292,293],{},"其他部分基本上是文字、圖片與連結。",[17,295,296],{"id":296},"那為什麼還要用框架",[10,298,299],{},"因為維護。同一份導覽列要出現在八個頁面上，用元件寫一次比複製八次好；SEO 的 metadata 要跟著頁面走，用框架處理比手動維護八份 HTML 好。",[10,301,302],{},"框架的價值在於開發與維護方式，不在於執行時要送多少 JavaScript 到瀏覽器。這兩件事其實可以分開。",[17,304,306],{"id":305},"靜態產生-少量-hydration","靜態產生 + 少量 hydration",[10,308,309],{},"比較舒服的組合是：",[52,311,312,315,318],{},[55,313,314],{},"頁面在建置階段就產生好 HTML，訪客第一次連進來拿到的就是完整內容",[55,316,317],{},"需要互動的元件才在瀏覽器端接手",[55,319,320],{},"純展示的區塊不需要任何 JavaScript",[10,322,323],{},"這樣做的好處很直接：第一次載入快、SEO 沒有問題、即使 JavaScript 載入失敗，內容仍然看得到。",[17,325,326],{"id":326},"動畫的部分",[10,328,329],{},"大部分進場動畫都可以用 CSS 完成：",[177,331,335],{"className":332,"code":333,"language":334,"meta":108,"style":108},"language-css shiki shiki-themes github-light github-light","@media (prefers-reduced-motion: no-preference) {\n  [data-reveal] {\n    opacity: 0;\n    transform: translateY(18px);\n  }\n  [data-reveal].is-visible {\n    opacity: 1;\n    transform: none;\n    transition: opacity .85s, transform .85s;\n  }\n}\n","css",[183,336,337,345,357,372,394,400,415,426,438,462,467],{"__ignoreMap":108},[186,338,339,342],{"class":188,"line":189},[186,340,341],{"class":203},"@media",[186,343,344],{"class":213}," (prefers-reduced-motion: no-preference) {\n",[186,346,347,350,354],{"class":188,"line":112},[186,348,349],{"class":213},"  [",[186,351,353],{"class":352},"se37E","data-reveal",[186,355,356],{"class":213},"] {\n",[186,358,359,363,366,369],{"class":188,"line":109},[186,360,362],{"class":361},"sMN4m","    opacity",[186,364,365],{"class":213},": ",[186,367,368],{"class":361},"0",[186,370,371],{"class":213},";\n",[186,373,374,377,379,382,385,388,391],{"class":188,"line":217},[186,375,376],{"class":361},"    transform",[186,378,365],{"class":213},[186,380,381],{"class":361},"translateY",[186,383,384],{"class":213},"(",[186,386,387],{"class":361},"18",[186,389,390],{"class":203},"px",[186,392,393],{"class":213},");\n",[186,395,397],{"class":188,"line":396},5,[186,398,399],{"class":213},"  }\n",[186,401,402,404,406,409,412],{"class":188,"line":124},[186,403,349],{"class":213},[186,405,353],{"class":352},[186,407,408],{"class":213},"]",[186,410,411],{"class":352},".is-visible",[186,413,414],{"class":213}," {\n",[186,416,417,419,421,424],{"class":188,"line":276},[186,418,362],{"class":361},[186,420,365],{"class":213},[186,422,423],{"class":361},"1",[186,425,371],{"class":213},[186,427,429,431,433,436],{"class":188,"line":428},8,[186,430,376],{"class":361},[186,432,365],{"class":213},[186,434,435],{"class":361},"none",[186,437,371],{"class":213},[186,439,441,444,447,450,453,456,458,460],{"class":188,"line":440},9,[186,442,443],{"class":361},"    transition",[186,445,446],{"class":213},": opacity ",[186,448,449],{"class":361},".85",[186,451,452],{"class":203},"s",[186,454,455],{"class":213},", transform ",[186,457,449],{"class":361},[186,459,452],{"class":203},[186,461,371],{"class":213},[186,463,465],{"class":188,"line":464},10,[186,466,399],{"class":213},[186,468,470],{"class":188,"line":469},11,[186,471,472],{"class":213},"}\n",[10,474,475,476,479],{},"JavaScript 只需要負責一件事：判斷元素進到畫面了沒有。一個 ",[183,477,478],{},"IntersectionObserver"," 就夠了，不需要動畫函式庫。",[10,481,482],{},"真正需要引入動畫套件的情況是時間軸控制、複雜的序列或是與捲動位置綁定的連續動畫。如果只是淡入，那大概是殺雞用牛刀。",[17,484,485],{"id":485},"一個判斷標準",[10,487,488],{},"在加入任何函式庫之前，可以先問：",[93,490,491,494,497],{},[55,492,493],{},"這個功能沒有它做不到嗎？",[55,495,496],{},"它會增加多少載入量？",[55,498,499],{},"三年後還會有人維護它嗎？",[10,501,502],{},"三題裡面有兩題答不出來，通常就不該加。",[17,504,505],{"id":505},"這不是效能潔癖",[10,507,508],{},"網站慢一點，多數訪客不會抱怨，他們只會離開，而你不會知道。",[10,510,511],{},"而且載入量這件事有個特性：它只會往上長，不會自己變少。每個「先加進來再說」的套件，最後都會變成沒有人敢刪的東西。所以在一開始少加一點，會比之後再來瘦身容易很多。",[261,513,514],{},"html pre.shiki code .sCydW, html code.shiki .sCydW{--shiki-default:#D73A49;--shiki-dark:#D73A49}html pre.shiki code .sKWpL, html code.shiki .sKWpL{--shiki-default:#24292E;--shiki-dark:#24292E}html pre.shiki code .se37E, html code.shiki .se37E{--shiki-default:#6F42C1;--shiki-dark:#6F42C1}html pre.shiki code .sMN4m, html code.shiki .sMN4m{--shiki-default:#005CC5;--shiki-dark:#005CC5}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":108,"searchDepth":109,"depth":109,"links":516},[517,518,519,520,521],{"id":296,"depth":112,"text":296},{"id":305,"depth":112,"text":306},{"id":326,"depth":112,"text":326},{"id":485,"depth":112,"text":485},{"id":505,"depth":112,"text":505},"技術選型","2026-07-22","大部分形象網站真正需要互動的地方不超過三個，但它們常常載入了一整套前端框架與動畫函式庫。",{},"\u002Finsights\u002Fhow-much-javascript-does-a-website-need",{"title":282,"description":524},"insights\u002Fhow-much-javascript-does-a-website-need","R08ApjvjFxM1RjX6ESvKnDm_yfO4t9R9OVIfygDAcS0",1787664334192]