- Published on
Next.js IntersectionObserver 導致 ALB 健康檢查失敗問題
- Authors

- Name
- Alex Yu
上週在修改一個 Next.js 專案的 custom hook 時,原本只是想調整一個使用 IntersectionObserver 的實作,結果部署到測試環境後發現 AWS 的 Application Load Balancer (ALB) 健康檢查持續失敗,服務被標記為 unhealthy。還好是在測試環境就發現了,沒有影響到正式環境。追查後才發現是因為在 server 端執行時 IntersectionObserver 不存在,導致 Node.js 拋出錯誤。這篇文章記錄這次除錯的過程和解決方案。
問題現象
部署新版本後,ALB 監控介面顯示目標群組中有 1 個 unhealthy 的實例,健康檢查持續失敗。


檢查 Next.js server 的 log 後發現大量的錯誤訊息:
{"message":"ReferenceError: IntersectionObserver is not defined"}
這個錯誤導致 server 回傳錯誤響應給 Nginx,進而讓 ALB 的健康檢查認為服務異常。
問題根源
問題出在原本的 useIntersectionObserver hook 中,在模組載入時就直接初始化了 IntersectionObserver:
// 有問題的程式碼
'use client'
import { RefObject, useEffect, useRef, useState } from 'react'
const useIntersectionObserver = (
elementRef: RefObject<Element>,
{ threshold = 0, root = null, rootMargin = '0px' }: IntersectionObserverInit,
): boolean => {
const [isIntersecting, setIntersecting] = useState<boolean>(false)
const observerParams = { threshold, root, rootMargin }
// ❌ 問題:在 useRef 初始化時直接 new IntersectionObserver
const observer = useRef<IntersectionObserver>(
new IntersectionObserver(
([entry]) => setIntersecting(entry.isIntersecting),
observerParams,
),
)
useEffect(() => {
const hasIOSupport = !!window.IntersectionObserver
const currentObserver = observer.current
const currentTarget = elementRef?.current
if (!hasIOSupport || !currentTarget) return
if (currentTarget) {
currentObserver.observe(currentTarget)
}
return () => {
if (currentTarget) {
currentObserver.unobserve(currentTarget)
}
}
}, [root, rootMargin, elementRef, threshold])
return isIntersecting
}
為什麼會出錯?
這個問題最容易被誤解的地方是:明明已經在檔案頂部標記了 'use client',為什麼還會在 server 端執行?
關鍵在於理解 'use client' 並不代表程式碼完全不會在 server 端執行。它只是告訴 Next.js 這個元件需要在客戶端渲染,但模組的初始化程式碼仍然會在 server 端執行。
在我們的程式碼中:
const observer = useRef<IntersectionObserver>(
new IntersectionObserver(...) // ← 這行會在模組載入時就執行!
)
這個 new IntersectionObserver(...) 是作為 useRef 的初始值,它會在模組被 import 時就執行,而這個時機點在 Next.js 的 Server-Side Rendering (SSR) 過程中還在 server 端的 Node.js 環境。
相對的,useEffect 內的程式碼才是真正「只在客戶端執行」的地方,因為 useEffect 是在元件掛載到 DOM 之後才會被呼叫。
執行流程如下:
有問題的流程:
- Server 端:模組被載入 →
useRef(new IntersectionObserver(...))執行 → ❌ 錯誤 - 因為 Node.js 環境沒有
IntersectionObserver,拋出ReferenceError - Server 回傳錯誤響應 → Nginx 接收錯誤 → ALB 健康檢查失敗
修正後的流程:
- Server 端:模組被載入 →
useRef(null)執行 → ✅ 安全 - 客戶端:元件掛載 →
useEffect執行 →new IntersectionObserver(...)執行 → ✅ 成功
解決方案
解決這個問題的關鍵是延遲 IntersectionObserver 的初始化,確保它只在 useEffect 內被建立。以下是修復後的程式碼:
'use client'
import { RefObject, useEffect, useRef, useState } from 'react'
const useIntersectionObserver = (
elementRef: RefObject<Element>,
{ threshold = 0, root = null, rootMargin = '0px' }: IntersectionObserverInit,
): boolean => {
const [isIntersecting, setIntersecting] = useState<boolean>(false)
// ✅ 修正:初始化為 null,不直接建立 IntersectionObserver
const observer = useRef<IntersectionObserver | null>(null)
useEffect(() => {
// ✅ 修正:加上 typeof window 檢查
const hasIOSupport =
typeof window !== 'undefined' && !!window.IntersectionObserver
const currentTarget = elementRef?.current
if (!hasIOSupport || !currentTarget) return
// ✅ 修正:在 useEffect 內才建立 IntersectionObserver
if (!observer.current) {
observer.current = new IntersectionObserver(
([entry]) => setIntersecting(entry.isIntersecting),
{ threshold, root, rootMargin },
)
}
const currentObserver = observer.current
currentObserver.observe(currentTarget)
return () => {
currentObserver.unobserve(currentTarget)
}
}, [root, rootMargin, elementRef, threshold])
return isIntersecting
}
修改重點
這次修正主要有三個關鍵改動:
useRef初始值改為null: 不在模組載入時就建立IntersectionObserver- 加上
typeof window !== 'undefined'檢查: 確保在瀏覽器環境中 - 在
useEffect內延遲初始化: 只有在客戶端執行時才建立 observer
這樣就能確保 IntersectionObserver 只會在瀏覽器環境的 useEffect 中被建立,避免在 server 端執行時出錯。
後記
這次的問題讓我重新認識了 Next.js 中 'use client' 的真正意義。原本以為標記了 'use client' 就可以安心使用瀏覽器 API,沒想到模組初始化的時機點仍然在 server 端。
其實類似的問題不只會發生在 IntersectionObserver,其他瀏覽器專屬的 API(如 window、document、localStorage 等)都可能遇到同樣的狀況。關鍵在於理解程式碼的執行時機:
useRef的初始值會在模組載入時執行(server + client 都會)useEffect內的程式碼只在元件掛載後執行(僅 client)
另外,這次問題雖然只是一個小小的 hook 修改,卻影響到整個服務的可用性。還好我們有完整的測試環境,在正式上線前就發現了這個問題,不然影響範圍就大了。這也再次驗證了測試環境和分階段部署的重要性。
最後,健康檢查機制在這次事件中扮演了關鍵角色,讓我們能快速發現異常並回溯問題。如果沒有 ALB 的健康檢查告警,可能要等到使用者回報才會發現服務出問題。監控和告警真的是不可或缺的一環。
希望這篇文章能幫助到遇到類似問題的開發者!
