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