글 목록을 한참 내려서 읽다가 다른 글로 넘어갔는데, 화면이 중간부터 보여서 당황했던 적이 있어요. 이것도 이 사이트(Frontend Note) 자체의 src/components/ScrollToTop.jsx 얘기예요. 이 사이트에서 목록 스크롤 내린 채로 다른 글로 이동해보시면 실제 동작을 바로 확인하실 수 있어요.

SPA에서 스크롤이 계속 이어지는 이유

브라우저 기본 페이지 이동은 새 문서를 불러오니까 스크롤이 자연스럽게 맨 위에서 시작해요. 근데 React Router 같은 클라이언트 라우팅은 같은 문서 안에서 컴포넌트만 바꿔치기하니까, 브라우저 입장에선 "새 페이지로 갔다"는 신호가 없어서 스크롤 위치가 그대로 남아있어요. 그래서 목록을 한참 내려서 읽던 사용자가 글 링크를 누르면, 새 글이 중간부터 보이는 문제가 생기더라고요.

다섯 줄로 해결했어요

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const ScrollToTop = () => {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
};

export default ScrollToTop;

useLocation으로 현재 경로(pathname)를 구독하고, 이 값이 바뀔 때마다(라우트가 전환될 때마다) window.scrollTo(0, 0)을 불러요. 이 컴포넌트는 화면에 아무것도 안 그리고(return null) App.jsx의 라우트 트리 바깥, Header 위쪽에 한 번만 렌더링해두면 모든 라우트 전환에 공통으로 적용돼요.

이 구현이 안 다루는 경우

이 컴포넌트는 pathname만 감시해서, 같은 경로 안에서 쿼리 스트링이나 해시만 바뀌는 이동(같은 글 안 앵커 링크 같은)엔 반응 안 해요. 나중에 페이지 안 특정 섹션으로 이동하는 앵커 링크가 이 사이트에 생긴다면, pathname뿐 아니라 해시 값도 같이 확인해서 스크롤 동작을 분기해야 할 거예요. 지금은 그런 앵커 이동이 없어서 이 단순한 형태로도 충분했어요.