프로젝트 개발 중 IntersectionObserver를 사용해 무한 스크롤 기능을 구현하려고 했는데, 다음과 같은 오류가 발생했습니다.
// 오류가 발생한 코드
const observer = new window.IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
// 데이터 로딩 로직
}
},
{ threshold: 0.2 }
);
오류 메시지 ReferenceError: 'window' is not defined
SSR (Server-Side Rendering) 환경의 문제
Next.js는 SSR을 지원하는 프레임워크입니다. 이는 서버에서도 컴포넌트를 렌더링한다는 의미입니다.
window 객체가 존재하지 않음 (브라우저 전용 API)window 객체가 존재함// 서버에서 실행될 때
console.log(window); // undefined ❌
// 브라우저에서 실행될 때
console.log(window); // Window 객체 ✅
window 객체는 브라우저에서만 존재하는 전역 객체입니다window 객체가 없습니다window에 접근하면 오류가 발생합니다사용 시기: 단순한 브라우저 API 접근
if (typeof window !== "undefined") {
const observer = new window.IntersectionObserver(callback);
}
사용 시기: 컴포넌트 마운트 시 한 번만 실행
useEffect(() => {
// 여기서는 window 객체가 확실히 존재함
const observer = new window.IntersectionObserver(callback);
return () => observer.disconnect();
}, []);
사용 시기: IntersectionObserver, ResizeObserver 등 복잡한 API
// 사용 시기: IntersectionObserver, ResizeObserver 등 복잡한 API
const observerRef = useRef();
useEffect(() => {
if (typeof window !== "undefined") {
observerRef.current = new IntersectionObserver(callback);
}
}, []);
import dynamic from 'next/dynamic'
const ClientOnlyComponent = dynamic(() => import('./Component'), {
ssr: false
})
// Isomorphic Hook 패턴
const useIsomorphicLayoutEffect = typeof window !== 'undefined'
? useLayoutEffect
: useEffect;
// 클라이언트 상태 체크 hook
const useClientOnly = () => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
};
import React, { useState, useEffect, useRef, useLayoutEffect } from "react";
// 가장 많이 사용되는 isomorphic hook 패턴
const useIsomorphicLayoutEffect = typeof window !== 'undefined'
? useLayoutEffect
: useEffect;
// 클라이언트 상태 체크 hook
const useClientOnly = () => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
};
const MoimListPage = () => {
const [visibleCount, setVisibleCount] = useState(8);
const [loading, setLoading] = useState(false);
const isClient = useClientOnly();
const loader = useRef();
const observerRef = useRef();
// Intersection Observer로 무한 스크롤 구현
useIsomorphicLayoutEffect(() => {
if (!isClient) return;
const currentLoader = loader.current;
if (currentLoader && typeof window !== 'undefined' && 'IntersectionObserver' in window) {
observerRef.current = new window.IntersectionObserver(
(entries) => {
if (
entries[0].isIntersecting &&
!loading &&
visibleCount < filteredMoims.length
) {
setLoading(true);
setTimeout(() => {
setVisibleCount((prev) =>
Math.min(prev + 8, filteredMoims.length)
);
setLoading(false);
}, 800);
}
},
{ threshold: 0.1, rootMargin: "100px" }
);
observerRef.current.observe(currentLoader);
}
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}, [filteredMoims.length, loading, visibleCount, isClient]);
return (
// JSX 렌더링
);
};
🎉 결론
현재 프로젝트에서는 가장 안전하고 견고한 방법인 "useRef + 조건문"을 사용했습니다!