當我的React應用崩潰時,我學到了最重要的一課
# 我怎麼讓我的 React 應用撐過了好幾百萬人潮
### 每個寫 React 的人都該知道的十個小祕訣(希望你不會像我一樣踩坑)😅
[這篇文章我自己稍微編修了一下,如果你有空可以去看看完整版!]
那時候,記憶有點模糊,大概是某一天晚上吧,我的 React 網站突然就掛了。說不上是哪裡出問題,只記得之前都還挺順。然後不知道為什麼,好像某次流量暴增——比平常多出好幾倍那種——整個應用開始卡住、畫面沒反應,最後乾脆直接當掉。有些用戶抱怨,也有人搞不清楚狀況。我其實也滿挫折的,努力弄了這麼久,一夕之間全亂掉。
不過……
### 每個寫 React 的人都該知道的十個小祕訣(希望你不會像我一樣踩坑)😅
[這篇文章我自己稍微編修了一下,如果你有空可以去看看完整版!]
那時候,記憶有點模糊,大概是某一天晚上吧,我的 React 網站突然就掛了。說不上是哪裡出問題,只記得之前都還挺順。然後不知道為什麼,好像某次流量暴增——比平常多出好幾倍那種——整個應用開始卡住、畫面沒反應,最後乾脆直接當掉。有些用戶抱怨,也有人搞不清楚狀況。我其實也滿挫折的,努力弄了這麼久,一夕之間全亂掉。
不過……
原文出處: https://www.kantti.net/tw/column/2297/explore-react-frontend-architecture
懶加載讓我的應用起死回生
那大概是我印象中學到過最有感的一課了。當時,深吸一口氣,袖子往上捲——其實也沒什麼儀式感,只是腦袋裡開始盤算接下來要怎麼補強這個應用程式。說真的,那時候有人隨口提了一句:「如果訪客人數暴增到幾百萬會怎樣?」我心裡還有點忐忑,不太確定能不能撐住。
一開始嘛,所有東西都傻傻一起丟上去——沒分先後,有的圖片、元件全都在網頁一打開就拼命跑。有點像把整箱雜物倒在桌上,一團亂。這種做法,其實蠻多人初期都會踩坑,畢竟看起來直接又省事。但現實不是想像,使用者等半天畫面才動一下,誰受得了?
後來聽朋友聊到「懶加載」這玩意兒——其實名字也有點趣味,就是慢慢來,該出現的時候再出現。不知道是不是錯覺,自從改成這種方式,用戶等待的時間好像短了不少。有些圖示或功能按鈕,就拖到他們滑到某個區塊時才真正跑資料。效率提升多少呢?坦白講沒算過精確數字,大約可以說減少了好幾成的負擔。
不過話說回來,「懶加載」並非萬靈丹,在特定情境下確實能帶來明顯改善,但偶爾也碰到瀏覽器兼容的小麻煩,需要另外處理。如果放眼整體經驗,也許只有部分用戶會立刻感受到明顯變化。不過至少,那陣子我對自己的選擇比較沒有那麼心虛,大致就是這樣吧。
一開始嘛,所有東西都傻傻一起丟上去——沒分先後,有的圖片、元件全都在網頁一打開就拼命跑。有點像把整箱雜物倒在桌上,一團亂。這種做法,其實蠻多人初期都會踩坑,畢竟看起來直接又省事。但現實不是想像,使用者等半天畫面才動一下,誰受得了?
後來聽朋友聊到「懶加載」這玩意兒——其實名字也有點趣味,就是慢慢來,該出現的時候再出現。不知道是不是錯覺,自從改成這種方式,用戶等待的時間好像短了不少。有些圖示或功能按鈕,就拖到他們滑到某個區塊時才真正跑資料。效率提升多少呢?坦白講沒算過精確數字,大約可以說減少了好幾成的負擔。
不過話說回來,「懶加載」並非萬靈丹,在特定情境下確實能帶來明顯改善,但偶爾也碰到瀏覽器兼容的小麻煩,需要另外處理。如果放眼整體經驗,也許只有部分用戶會立刻感受到明顯變化。不過至少,那陣子我對自己的選擇比較沒有那麼心虛,大致就是這樣吧。

優化圖片和資源後,用戶不再抱怨速度慢
結果?頁面載入速度有變得快一些,流暢度也明顯提升。React這邊處理起來好像沒什麼難度,甚至有點輕鬆。
這樣一來,一開始那些沉重的東西就不用全部丟給瀏覽器了。
說到圖片資源,那陣子確實遇過困擾,圖檔畫質都不差,可是尺寸大到用行動網路的人常常卡住,要等很久才看得到完整內容。其實解法也不算複雜,只是當時花了點時間才意識到——
const LazyComponent = React.lazy(() => import('./BigComponent'));
這樣一來,一開始那些沉重的東西就不用全部丟給瀏覽器了。
說到圖片資源,那陣子確實遇過困擾,圖檔畫質都不差,可是尺寸大到用行動網路的人常常卡住,要等很久才看得到完整內容。其實解法也不算複雜,只是當時花了點時間才意識到——
使用Memoization阻止不必要的重新渲染
圖片的處理大概是用了TinyPNG壓縮,然後又把格式換成WebP,好像聽說這種圖檔比起JPEG或PNG要小一些,但畫質也沒什麼明顯落差。有人還會用lazy loading讓那些圖片不會一次全載,這樣整體感覺比較順,尤其在手機上可能特別有感。實作時有的人大概會放個假的小圖做佔位,用個什麼data-src存真的圖路徑,就類似
那樣。
說到React,有些情境下重複渲染問題挺明顯的,好像只要稍微一多點元件就容易出現沒必要的重算。有的人會試著用React.memo或useMemo來減少類似狀況,不過效果怎樣得看應用場景,有時候變化不太大時才比較有效。反正這些方法通常在專案稍微複雜一點之後,大家陸續都會碰過吧。不一定每次都能讓速度提升很多,但使用妥當還是可能帶來某種程度的改善。
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />
那樣。
說到React,有些情境下重複渲染問題挺明顯的,好像只要稍微一多點元件就容易出現沒必要的重算。有的人會試著用React.memo或useMemo來減少類似狀況,不過效果怎樣得看應用場景,有時候變化不太大時才比較有效。反正這些方法通常在專案稍微複雜一點之後,大家陸續都會碰過吧。不一定每次都能讓速度提升很多,但使用妥當還是可能帶來某種程度的改善。

代碼分割讓JavaScript包變得更輕量
記得那時候,好像用了 React.memo 來包一個叫 ExpensiveComponent 的東西,裡頭只接收 data 當作參數。這麼一弄,處理速度感覺快了不少——有些畫面變流暢,大概節省了過去將近一半的效能開銷吧。不過說真的,也不是所有情況都適合這樣做,還是要看場景。
說到 JavaScript 檔案的大小,那陣子才發現自己的 bundle 怎麼越堆越大,一打開網頁就卡住。明明只是換個分頁,用戶就要等好久。回頭想想,好像是因為一次把全部程式塞進去了,結果誰都跑不掉。後來聽人提起「代碼切割」這招,就是讓瀏覽器只抓目前需要那部分,不會傻傻的一次全載完。
React 那邊有給幾行語法,看似簡單:用 React.lazy 去 import 某個 Page 組件。然後再搭配 Suspense,把 LoadingSpinner 丟進 fallback。有點像是在等資料時先秀個轉圈動畫,不至於全白螢幕嚇跑訪客。感覺下來,有些操作確實不用再忍受漫長讀取,瀏覽體驗也順多了。不過嘛,有沒有辦法解決所有慢的問題倒也不敢保證,只是用在特定頁面上效果還行。
其實整套方法,不算太複雜,但也沒什麼萬靈丹。如果哪天 bundle 又突然膨脹,可能又得再找別招。但至少當時應該減輕了一些下載壓力,用戶端負擔也小了點。
把靜態文件放到CDN上拯救了我的服務器
有些時候,網站的靜態檔案全都堆在自己的主機上。事情一開始也沒什麼大問題,直到用戶突然變多——可能是某個時段或是因為剛好被人分享了出去,主機就這樣一下子反應不過來。有人後來選擇把圖片啊、前端腳本和版型這類東西搬到內容傳遞網路(CDN)去。據說這麼做之後,載入速度有明顯提升,用戶在不同地方打開頁面,好像也順暢不少。其實CDN選項還蠻多的,不少人會先想到Cloudflare,不過AWS CloudFront或Fastly等其他服務,也偶爾會被提起。有些經驗分享裡提到,主機壓力減輕了不少,但細節到底差多少,有時還得看網站流量規模跟檔案大小。有些朋友認為,不管怎樣,把靜態內容交給專門的平台處理,也許對於體驗來說,是一種比較穩妥的做法吧。不過哪一家服務適合自己,大概還是要根據情況慢慢試試才知道。

減少API調用次數的秘訣:防抖和緩存
API那部分啊,早期的時候每點一下、切換個頁面什麼的,後端就被呼叫了一次又一次。這種情形,好像在不少專案裡都碰過,不只某一兩次。有人後來想到用緩衝和快取來減少不必要的資料請求。debounce 這種寫法,大致上是讓系統等個幾百毫秒再決定要不要動作,有點像人家思考完才開口問問題那樣,不會一下子就急著丟東西出去。大約有七成多的無意義流量就這樣被擋掉了,感覺整體順暢了些。
然後前端嘛,其實不少用 React 的專案都還是單純跑在瀏覽器端。不過慢慢也聽到一些團隊開始嘗試伺服器端渲染,用 Next.js 來做。據說這麼搞之後,在某些頁面加載時速度有提升。有朋友說他們遇到過類似狀況:SSR 加上前端快取,兩者搭配起來,使用者進站時體驗變得好像更流暢,但具體差多少,其實各種環境下結果也不盡相同啦。
總歸一句,好像很多優化手段都是要多試幾輪,看哪些適合自己現有架構,再去細調比較妥當。有些改善看起來效果明顯,有些則是微幅改變而已。
然後前端嘛,其實不少用 React 的專案都還是單純跑在瀏覽器端。不過慢慢也聽到一些團隊開始嘗試伺服器端渲染,用 Next.js 來做。據說這麼搞之後,在某些頁面加載時速度有提升。有朋友說他們遇到過類似狀況:SSR 加上前端快取,兩者搭配起來,使用者進站時體驗變得好像更流暢,但具體差多少,其實各種環境下結果也不盡相同啦。
總歸一句,好像很多優化手段都是要多試幾輪,看哪些適合自己現有架構,再去細調比較妥當。有些改善看起來效果明顯,有些則是微幅改變而已。
為什麼我選擇了服務器端渲染(SSR)
有時候網頁開得比平常快一些,這可能和檔案體積變小有關。像是那些 JavaScript、CSS 甚至 HTML,幾乎每個檔案都被處理過,壓縮成比較迷你的版本。據說這樣做,容量可以少掉不少,雖然沒人去算到底剩下幾成,但流覽器載入感覺起來確實省時些。
SEO的部分,好像也因為這樣略微提升。有些朋友提到搜尋表現改善了,不過實際的影響範圍,大概只有部分頁面比較明顯吧。
至於即時互動,有段時間會發現單靠 API 輪詢速度挺慢的,那種一再請求伺服器的方法,感覺效能有限。後來乾脆換用 WebSocket。像 Socket.io 這類套件,其實蠻容易就把連線拉起來:
訊息就這樣自動飄進來了。整體看起來,比原本那種反覆查詢伺服器的方式要即時許多。不敢說每個場景都適合,但在需要同步更新的情境下,用戶端體驗確實流暢了不少。有的人會覺得應用程式變得很接近「秒開」那種感受,不過是否真有如此誇張,也許還要看其他因素配合才行。
SEO的部分,好像也因為這樣略微提升。有些朋友提到搜尋表現改善了,不過實際的影響範圍,大概只有部分頁面比較明顯吧。
至於即時互動,有段時間會發現單靠 API 輪詢速度挺慢的,那種一再請求伺服器的方法,感覺效能有限。後來乾脆換用 WebSocket。像 Socket.io 這類套件,其實蠻容易就把連線拉起來:
const socket = io("https://myserver.com");
socket.on("message", (data) => {
console.log("New message:", data);
});
訊息就這樣自動飄進來了。整體看起來,比原本那種反覆查詢伺服器的方式要即時許多。不敢說每個場景都適合,但在需要同步更新的情境下,用戶端體驗確實流暢了不少。有的人會覺得應用程式變得很接近「秒開」那種感受,不過是否真有如此誇張,也許還要看其他因素配合才行。

WebSockets如何讓實時更新變得流暢
那時好像才剛把 Gzip 跟 Brotli 這些壓縮方式打開,其實只是加了兩三行指令,印象中就像是在伺服器上寫了 `
` 那種設定,把一些常見的檔案格式都交給壓縮去處理。說也奇怪,本來還擔心沒什麼感覺,結果一開啟,用手機或者比較慢的網路測試時,整個速度真的有明顯提升,不少朋友反饋也差不多。
之前遇到過一次應用程式大當機,那次讓我滿頭包。後來在要正式上線前,也算是提心吊膽地跑了一輪壓力測試吧。JMeter 這類工具就派上用場了,我那時候模擬過非常多的使用者同時連線,大概已經快逼近幾百萬人一齊進站那種規模,比平常流量高出好多倍。整體看下來,系統好像都還撐得住,只是偶爾某些細節會卡一下。
回想起來,當初如果沒有遇到那場小災難,也許我根本不會特別去注意效能或可擴展性。現在呢?只能說,那些挫折多少讓我的應用變得更穩健了一點點,看起來目前就算碰到數以百萬計的訪客,也還沒出現什麼大問題。不敢說完全無懈可擊,但至少現在比較能安心得下心。
上線前的負載測試讓我睡得更安穩
有些人在寫 React 專案的時候,好像常常等到出事才開始想優化。其實,有些開發者會建議,還沒遇到什麼大問題時就可以稍微注意一下效能,偶爾測試看看,畢竟等出現奇怪的卡頓或慢速再來補救,通常都比較麻煩。不知道是不是大家都有碰過這類困擾?好像不少團隊都曾經遇過一兩次那種莫名其妙跑很慢的狀況。
也許你身邊同事還沒注意到這件事,但如果覺得有幫助,不妨跟大家分享一下心得。反正,有時一個小提醒就可能讓團隊整體進步不少。有些人會在留言區討論自己的經驗,也有人選擇把相關資訊丟給組內夥伴參考,其實哪種方式都無妨啦。
對了,有時候看到某些 React 頁面莫名地變慢,好像也不是每次都能馬上定位原因。如果你剛好想到什麼案例或小技巧,歡迎在下面留言聊聊~
也許你身邊同事還沒注意到這件事,但如果覺得有幫助,不妨跟大家分享一下心得。反正,有時一個小提醒就可能讓團隊整體進步不少。有些人會在留言區討論自己的經驗,也有人選擇把相關資訊丟給組內夥伴參考,其實哪種方式都無妨啦。
對了,有時候看到某些 React 頁面莫名地變慢,好像也不是每次都能馬上定位原因。如果你剛好想到什麼案例或小技巧,歡迎在下面留言聊聊~