React Router에서 스크롤 복원

발행: (2026년 1월 17일 오후 06:30 GMT+9)
7 min read
원문: Dev.to

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를 사용하고 이 동작이 없으면, 사용자는 눈치챌 것입니다—비록 직접 말하지 않더라도.

Back to Blog

관련 글

더 보기 »

React 앱의 기본

소개 오늘은 React 앱을 생성할 때 보이는 파일과 폴더의 이유와 사용 방법을 살펴보겠습니다. !React app structurehttps:/...