React Router에서 스크롤 복원
Source: Dev.to
해당 텍스트를 번역하려면 원문 전체를 제공해 주시겠어요? 현재는 소스 링크만 포함되어 있어 번역할 내용이 없습니다. 텍스트를 복사해서 붙여 주시면 한국어로 번역해 드리겠습니다.
소개
React Router로 싱글 페이지 애플리케이션(SPA)을 구축할 때 거의 즉시 나타나는 일반적인 UX 문제가 있습니다: 네비게이션은 URL을 변경하지만 페이지가 최상단으로 스크롤되지 않습니다. 이는 특히 블로그, 문서, 대시보드와 같이 콘텐츠가 많은 페이지에서 사용자 기대를 깨뜨립니다.
전통적인 다중 페이지 웹사이트 vs. SPA
| 전통적인 다중 페이지 사이트 | SPA | |
|---|---|---|
| 네비게이션 | 전체 페이지 새로고침을 트리거 | 클라이언트‑사이드에서 발생 |
| DOM 재로드 | 예 | 아니오 |
| 브라우저 스크롤 초기화 | 자동으로 (0, 0)으로 이동 | 기본적으로 유지 |
그 결과, /blog → /about 로 이동하면 페이지가 절반 정도 스크롤된 상태로 남게 됩니다.
Simple and correct implementation
import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname } = useLocation();
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
useLocation()은 현재 위치 객체를 제공합니다.pathname은 라우트가 변경될 때만 바뀝니다 (쿼리 파라미터나 해시가 바뀔 경우는 포함하지 않으려면 직접 포함시켜야 합니다).useLayoutEffect는 브라우저가 화면을 그리기 이전에 실행되어, 눈에 보이는 점프/깜빡임을 방지합니다.
Tip: 스크롤, 포커스, DOM 측정과 같은 레이아웃 관련 부수 효과에는
useLayoutEffect를 사용하세요.
컴포넌트를 마운트할 위치
import { BrowserRouter } from "react-router-dom";
import { ScrollToTop } from "./ScrollToTop";
<BrowserRouter>
<ScrollToTop />
{/* ...your routes */}
</BrowserRouter>
- 라우터의 루트에 한 번만 마운트하세요.
- 개별 페이지 안에 넣거나 라우트를 각각 감싸 하지 마세요.
쿼리 매개변수 처리
쿼리 문자열이 변경될 때 스크롤을 초기화하고 싶다면:
import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname, search } = useLocation();
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, [pathname, search]);
return null;
}
앵커(해시) 내비게이션 보존
URL에 해시가 포함된 경우(예: /docs#getting-started), 위의 컴포넌트가 브라우저의 기본 앵커 스크롤링을 덮어씁니다. 해시 변경을 무시하도록 수정하세요:
import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname, hash } = useLocation();
useLayoutEffect(() => {
if (hash) return; // let the browser handle anchor scrolling
window.scrollTo(0, 0);
}, [pathname, hash]);
return null;
}
Note: 브라우저는 뒤로/앞으로 이동 시 스크롤 위치를 자동으로 기억합니다. 이 컴포넌트는 해당 동작을 방해할 수 있으므로, 주로 앞으로 이동할 때만 사용하거나 특정 라우트에서는 비활성화하는 것이 좋습니다.
iOS 및 대체 스크롤링 방법
일부 iOS 기기에서는 모멘텀 스크롤링 때문에 window.scrollTo가 실패할 수 있습니다. 더 안전한 대체 방법:
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
문제가 발생할 경우에만 대체 방법을 사용하세요.
사용자 정의 컨테이너 내부 스크롤
앱이 특정 요소 내부에서 스크롤될 경우, 해당 요소를 직접 지정하세요:
document.getElementById("scroll-root")?.scrollTo(0, 0);
개발 quirks
- 개발 모드에서는 React의 strict mode 때문에
useLayoutEffect가 두 번 실행됩니다. 이는 않습니다 프로덕션에 영향을 주며 무시해도 됩니다. - 이 효과는 네비게이션 시에만 실행되며, 단일 동기 작업을 수행하고 재렌더링을 전혀 발생시키지 않습니다.
ScrollToTop을 사용하지 않을 때
특정 UI 패턴에서는 스크롤 위치를 유지하는 것이 바람직합니다:
- 무한 스크롤 페이지
- 채팅 애플리케이션
- 자동 저장이 있는 폼
- 지도 기반 인터페이스
이러한 경우 자동 스크롤‑투‑탑을 비활성화하면 사용자 경험이 향상됩니다.
전체 예시 (해시 처리 포함)
import { useLayoutEffect } from "react";
import { useLocation } from "react-router-dom";
export function ScrollToTop() {
const { pathname, hash } = useLocation();
useLayoutEffect(() => {
if (hash) return;
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
}, [pathname, hash]);
return null;
}
결론
스크롤 복원은 선택적인 UI 다듬기가 아니라 핵심적인 네비게이션 기대치입니다. 이 작은 컴포넌트는
- 근본적인 SPA 결함을 해결하고
- 인지된 성능을 향상시키며
- 앱을 “네이티브”처럼 느끼게 합니다
React Router를 사용하고 이 동작이 없으면, 사용자는 눈치챌 것입니다—비록 직접 말하지 않더라도.