好了,我結束短暫的假期回來了,並帶來了一些有用的內容😄 如你所知,我會不定期地為你寫一些文章,比如《別再安裝庫了:10個瀏覽器API就能解決你的問題》 ——我真心喜歡寫這類文章,也知道你們會喜歡🙂
今天我想從一個稍微不同的角度來探討這個主題。我會向你展示一些有趣的事情,它們可能在不知不覺中讓你的應用程式執行速度變慢,儘管乍一看它們似乎完全無害。最棒的是什麼?其中一些問題可以出乎意料地快速解決。此外,當你向 Claude Code 或 Codex 詢問「為什麼我的應用程式運作緩慢」時,他們可能不會立即指出這些問題😅
通常,我們會在配備高速CPU、充足記憶體和高速網路的強大機器上開發應用程式。然而,真實使用者往往身處截然不同的現實環境。他們中的一些人確實擁有現代化的硬件,但總是會有人使用老舊的筆記型電腦、廉價的安卓手機、信號弱的WiFi,或者糟糕透頂的移動網絡😅
突然發現,對於 10% 到 30% 的用戶來說,你的應用程式執行速度慢得令人難以忍受。
毫秒之戰就此揭開序幕😀
本文中的每個例子都是我在實際專案中親身遇到的,或是我從其他開發者那裡聽說的,所以絕對不是假設情境。不妨檢查一下你的應用程式中是否也悄悄發生了類似的事情👀
同時,我正在慢慢準備JSNation大會的演講。如果你想支持我(或只是想看看我在花園裡笨拙地演講的樣子😄),可以點擊這裡給我點個讚。如果你想免費觀看我的完整演講,可以點擊這裡領取免費徽章。是不是很棒😄
好了,自我宣傳到此為止,讓我們進入正題吧!
這正是參加會議的價值所在。有時候你會在會上聽到一些問題,而這些問題你可能永遠不會自己去谷歌搜尋 😀
我的一位同事在報告中提到了這個問題。他的團隊正在努力找出為什麼有些用戶感覺他們的應用程式運作緩慢。不出所料,後端首先被指責。後端一如既往地糟糕😅
但最終,他們在網路標籤中發現了一些有趣的事情:幾乎每個 API 呼叫之前都會出現OPTIONS請求。其中一些請求耗時數百毫秒。
那麼,這裡究竟發生了什麼事?
這與 CORS 有關。瀏覽器有時會在實際呼叫 API 之前發送額外的OPTIONS請求。這稱為預檢請求,通常發生在「非簡單」請求中——例如使用PUT或DELETE等方法時,以及當加入自訂標頭時。
沒錯,即使是完全無害的GET請求,也可能因為三年前有人加入了X-Feature-Whatever而突然變成兩次網路呼叫😅
最搞笑的是,那個自訂頭部資訊根本就沒用過。它是多年前遺留下來的古老遺跡。沒人知道它存在的意義,也沒人質疑它。它就像一件不朽的企業遺物,在每一次重構中都倖存了下來😀
如果你好奇的話,我其實(和 Claude Code 一起😅)準備了一個小倉庫來展示這種行為:
https://github.com/sylwia-lask/preflight-options
讓我們看看截圖(請欣賞我高超的繪圖技巧!):
不含自訂標頭的 GET 請求:

帶有自訂標頭的 GET 請求:

說實話,這種事在大型專案中屢見不鮮。有人加入了一個自定義的頭部訊息,用於功能標誌、除錯、本地化、分析或“臨時”元資料……然後這個頭部訊息就一直存在了四年之久。
當然,有時自訂請求頭是完全合理的。但如果您僅將其用於前端邏輯,通常會有更好的替代方案,例如查詢參數、cookie、本機狀態或在啟動時取得一次的設定端點。
單獨來看,一次額外的請求或許無關緊要。但如果你的應用程式在啟動時發出數十次請求,尤其是在行動網路連線速度較慢的情況下,這就會變得非常明顯。
有時候問題不在於網路本身,而是啟動時載入的龐大 JavaScript 套件。這時,大家通常會說:
“可是怎麼做到的?我們已經在做程式碼拆分了!我們到處都用了懶加載!”
嗯……關於那件事😄
我曾經審核過一個Angular應用程式,乍看之下結構非常良好。它到處都是模組,採用了懶加載,架構合理,符合所有“最佳實踐”。
然而,該應用程式載入速度卻極其緩慢。
幸運的是,我們有webpack-bundle-analyzer 、 source-map-explorer 、 rollup-plugin-visualizer或@next/bundle-analyzer等工具,可以讓我們了解 bundle 內部實際發生了什麼。
我們發現了什麼?
是的,該應用程式被拆分成了多個模組…
……只不過每個模組只有2KB左右😅
因為幾乎所有重要的功能都集中在一個巨大的共享模組中,而這個模組又被到處導入,這意味著應用程式的大部分最終還是被打包進了主程式包裡😀
恭喜,您的應用程式現在已拆分為 400 個美觀且彼此分離的文件,所有文件都會在啟動時加載。
這也不是我遇到的唯一一個奇怪的案例。我已經遇到過這樣的情況:應用程式表面上「延遲載入」了模組,但實際上每次啟動時幾乎都要下載整個應用程式😄
例如,像這樣看起來就完全沒問題:
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.module').then(m => m.AdminModule)
}
看起來簡潔。看起來現代。看起來優化過。
直到你發現AdminModule導入了一個包含應用程式一半內容的龐大共享模組😅
所以,沒錯-即使你使用了import()或延遲載入模組,也不代表你的資源包就一定沒問題。務必檢查瀏覽器實際下載了哪些內容。
這是另一個極為常見的問題,尤其是在那些沒人真正控制使用者安裝哪些 npm 套件的專案中😅
在我目前的專案中,導入新的依賴項幾乎被視為一種神聖的儀式,需要得到「王國」中最睿智的「建築師」(基本上就是我和兩三個同事😀)的批准。但在許多專案中,人們安裝庫時卻完全不加思考。
然後突然間,你的申請表裡就出現了:
三個分析SDK,
兩個日期庫,
所有 Moment.js 語言環境
因為有人需要一個實用函數,所以就開發了整個 Lodash 套件。
Firebase 已全域導入,
三款圖標包,
還有一些“輕量級輔助軟體包”,悄無聲息地導入了半個網路的內容😀
我曾經見過一個應用程式同時載入三個不同的日期庫。最有趣的是,這個應用程式幾乎無法處理日期😅 看來每個開發者都有自己偏好的日期庫。
另一個經典的例子是像這樣導入 Lodash:
import _ from 'lodash';
而不是:
import debounce from 'lodash/debounce';
差異看似很小,但隨著時間的推移,這些差異會累積成很大的問題。尤其是在持續發展多年的企業級應用中更是如此。
可惜的是,搖樹並不能帶來魔法😅
這一點聽起來幾乎太顯而易見了,對吧?
大家都知道巨幅圖片不好。
……不過人們還是繼續寄巨型圖片😄
最近,在 WeAreDevelopers 的播客節目中,我們討論了哪些政府網站載入速度最快。令人驚訝的是,英國的網站遙遙領先。為什麼呢?我之後可能會專門寫一篇文章來探討這個問題,但總的來說,英國的網站非常簡潔。視覺幹擾極少,資訊文字豐富,佈局簡單,採用伺服器端渲染 (SSR) 技術,並且盡量減少不必要的素材。
第二快的是美國政府網站。
它遵循的原理幾乎完全相同……只不過啟動時會加載一張精美的大圖😅
突然間,那幅原本氣勢恢宏的畫作明顯變差了。
大型背景圖片的奇特之處在於,它們在配備高速網路的開發設備上看起來似乎無害,但在配置較低的裝置上卻會嚴重影響使用者體驗。
幸運的是,有很多方法可以改善這種情況:使用 AVIF 或 WebP,大力壓縮,避免在首屏放置過大的圖片,延遲加載非關鍵視覺內容,並且只預先加載真正關鍵的資源。
說實話?
有時候最快的影像就是…沒有影像😀
應用程式優化顯然是一個永無止境的話題,本文只是略作探討。但我認為最重要的一點是,性能問題往往是日積月累造成的。
一個不必要的標題。
一個過大的依賴項。
一個「臨時」共享模組。
一張無人質疑的背景圖。
單獨來看,這些問題都不算嚴重。但它們疊加在一起,導致應用程式執行緩慢——尤其是在老舊設備或速度較慢的行動網路環境下。
真正可怕的是什麼?
這些決定最初提出時,大多看起來完全合理😅
原文出處:https://dev.to/sylwia-lask/4-tiny-mistakes-that-secretly-destroy-app-performance-3cgo