최근 Next.js 패럴렐 라우트를 사용해서 모달을 만드는 작업을 하던 중 궁금한 점이 생기게 되었습니다. 패럴렐 라우트로 만든 모달을 닫아야할 때 router.refresh() 와 window.location.reload() 중 어떤 것을 사용하는 것이 적절한지에 대한 것이었습니다.
제가 경험한 바로는 window.location.reload() 를 사용하면 의도한 대로 모든 모달이 닫혔지만, router.refresh() 를 사용하면 URL은 변경되었음에도 불구하고 화면 상의 모달은 여전히 남아있었습니다.
그래서 과연 두개의 차이는 무엇이면 각각 어떤 상황에서 사용해야하는지에 대해 알아보도록 하겠습니다.
window.location.reload() 는 브라우저 네이티브 API로 전체 페이지를 완전히 새로고침하는 역할을 합니다. 이 메서드가 호출되면 다음과 같은 과정이 진행됩니다.
const handleHardReload = () => {
window.location.reload();
};
이러한 특성 때문에 window.location.reload() 는 완전한 초기화가 필요한 상황에서 확실한 해결책이 되지만, 사용자 경험 측면에서는 느리고 불편할 수 있습니다.
router.refresh() 는 Next.js의 App Router에서 제공하는 메서드로, 소프트 내비게이션(Soft Navigation) 방식으로 동작합니다.
- Hard Navigation: 브라우저가 완전히 새로운 페이지를 로드 (전체 새로고침)
- Soft Navigation: JavaScript로 필요한 컴포넌트만 교체 (부분 업데이트)
'use client';
import { useRouter } from 'next/navigation';
const RefreshButton = () => {
const router = useRouter();
const handleSoftRefresh = () => {
router.refresh();
};
return (
<button onClick={handleSoftRefresh}>
데이터 새로고침
</button>
);
};
router.refresh() 가 호출되면, Next.js는 현재 경로에 해당하는 서버 컴포넌트의 데이터만 다시 가져옵니다. 이때 중요한 점은 페이지의 구조나 레이아웃은 그대로 유지한다는 것입니다. 이는 사용자가 입력한 폼 데이터나 스크롤 위치 같은 상태들이 유지된다는 것을 의미합니다.
또한, 클라이언트 상태도 보존됩니다. useState나 Zustand 같은 상태 관리 라이브러리의 상태들이 그대로 유지되어, 사용자가 설정한 값들이 사라지지 않습니다.
가장 중요한 특징은 라우팅 구조와 슬롯이 변경되지 않는다는 점입니다. 특히 패럴렐 라우트의 슬롯들은 기존 상태를 그대로 유지하게 됩니다.
이러한 특성으로 인해 router.refresh()는 사용자 경험을 해치지 않으면서도 최신 데이터를 가져올 수 있는 효율적인 방법이지만, 완전한 초기화가 필요한 상황에서는 한계가 있습니다.
패럴렐 라우트를 사용하여 모달을 구현할 때는 다음과 같은 구조를 가집니다.
이해를 돕기 위한 예시 코드로 내용 이해를 위한 참고용으로 봐주세요.
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html>
<body>
<div>
{children}
{modal}
</div>
</body>
</html>
);
}
// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>메인 페이지</h1>
<Link href="/products/1">
상품 모달 열기
</Link>
</div>
);
}
// app/@modal/products/[id]/page.tsx
export default function ProductModal({ params }: { params: { id: string } }) {
const handleModalClose = () => {
window.history.pushState({}, '', '/');
router.refresh();
};
return (
<div>
<div>
<h2>상품 {params.id} 상세</h2>
<p>상품에 대한 자세한 정보입니다.</p>
<button onClick={handleModalClose}>
닫기
</button>
</div>
</div>
);
}
// app/@modal/default.tsx
export default function DefaultModal() {
return null; // 모달이 없을 때는 null 반환
}
위와 같은 구조에서 이런 구조에서 products/1 경로로 이동하면 @modal 슬롯에 의해 모달이 화면에 표시됩니다. 만약 이때, 사용자가 닫기 버튼을 클릭했을 때, URL을 /로 변경하고 router.refresh()를 호출해도 화면상의 모달은 여전히 남아있게 됩니다.
const handleModalClose = () => {
// 1. URL을 '/'로 변경
window.history.pushState({}, '', '/');
// 2. router.refresh() 호출
router.refresh();
};
router.refresh() 를 사용했을 때 패럴렐 라우트 모달이 닫히지 않는 원인은 Next.js의 패럴렐 라우트 슬롯 관리 방식 에 있습니다.
패럴렐 라우트에서 @modal 슬롯은 독립적인 렌더링 단위로 취급 됩니다. router.refresh() 가 호출되면 Next.js는 현재 활성화된 모든 슬롯을 기준으로 데이터를 다시 가져오지만, 슬롯 자체의 구조는 변경하지 않습니다.
이는 Next.js가 성능 최적화를 위해 설계한 동작으로, router.refresh() 는 필요한 데이터만 새로 가져오고 기존 컴포넌트 구조는 최대한 보존하려고 하기 때문입니다. 구체적으로 설명하면, URL이 /로 변경되어도 Next.js는 이미 마운트된 @modal 슬롯의 컴포넌트들이 여전히 유효하다고 판단합니다. router.refresh() 는 단순히 서버에서 새로운 데이터를 가져와서 기존 컴포넌트에 전달할 뿐, 컴포넌트 자체를 언마운트하지는 않습니다.
반면, window.location.reload() 는 브라우저 레벨에서 완전한 새로고침을 수행하므로, 모든 슬롯과 컴포넌트가 처음부터 다시 생성되어 모달이 확실히 닫히게 됩니다.

가장 권장되는 방법은 모달이 없는 경로로 명시적으로 이동하는 것입니다.
'use client';
import { useRouter } from 'next/navigation';
const CloseModalButton = () => {
const router = useRouter();
const closeModal = () => {
// 모달 슬롯이 매칭되지 않는 경로로 이동
router.push('/');
};
return (
<button
onClick={closeModal}
className="bg-red-500 text-white px-4 py-2 rounded"
>
모달 닫기
</button>
);
};
사용자가 모달을 열기 전 페이지로 돌아가는 경우에 유용합니다.
const BackButton = () => {
const router = useRouter();
const goBack = () => {
// 브라우저 히스토리에서 이전 페이지로 이동
router.back();
};
return (
<button onClick={goBack} className="bg-gray-500 text-white px-4 py-2 rounded">
이전으로
</button>
);
};
이 밖에도 복잡한 모달 관리가 필요한 경우 zustand를 활용한 상태 관리 방식 및 인터셉팅 라우트를 활용한 방법도 고려해 볼 수 있습니다.

이번 글을 작성하면서 패럴렐 라우트 모달 관리에 대한 이슈가 저뿐만 아니라 여러 개발자들이 공통적으로 겪고 있는 문제라는 것을 알게 되었습니다. 개발을 하다 보면 모달 관리는 언제나 고민거리였는데, 패럴렐 라우트가 이를 체계적이면서도 심플하게 해결해줄 수 있을 거라는 기대를 했지만 생각보다 쉽지 않더라고요 ... 😅
우선, 블로그를 정리하면서 느꼈던 것은 router.refresh()와 window.location.reload()의 차이점을 명확히 이해하는 것이었습니다.
router.refresh() 의 특징:
window.location.reload() 의 특징:
결론적으로, 패럴렐 라우트 모달을 올바르게 관리하려면 router.push() 나 router.back() 을 사용하여 적절한 경로로 이동하거나, 상태 관리 라이브러리를 활용한 조건부 렌더링 방식을 고려하는 것이 가장 적절한 해결책입니다.
패럴렐 라우트가 완벽한 해결책은 아니지만, 이러한 동작 원리와 차이점을 이해하고 상황에 맞는 적절한 방법을 선택한다면 Next.js에서 더욱 안정적이고 사용자 친화적인 모달 경험을 제공할 수 있을거라고 생각합니다~!
📚 참고
- Next.js의 고급 라우팅 패턴
- Next.js Parallel Routes 공식 문서
- GitHub Discussion - window.location.reload vs router.reload 차이점
- Creating Modals in Next.js 14: Using Parallel and Intercepting Routes