~ZeroToOne 프로젝트 트러블 슈팅~
이번 프로젝트의 1차 MVP에서 로그인 파트를 제외한 나머지 부분을 담당하면서 페이지 이동 흐름을 구현하는 과정을 진행했습니다. 이 과정에서 여러 라우팅 방식과 CSR/SSR에 따른 쿠키 처리의 차이 등을 겪으며 정말 많은 오류를 경험했습니다.
그 과정에서 겪은 시행착오와 각 페이지 이동 방식의 특성을 한 번 정리하며 모르는 부분을 짚고 넘어가기 위해 작성하였습니다.

1. 페이지 이동 방식
Next.js에서 페이지 이동을 구현할 때 사용할 수 있는 방법은 여러 가지가 존재합니다.
그중 가장 많이 사용하는 방식으로는 다음 세 가지 방식을 꼽을 수 있습니다.
Router.push()
Next.js가 제공하는 useRouter() 훅을 사용해 페이지 이동을 처리하는 방식입니다.
import { useRouter } from 'next/navigation';
const router = useRouter();
router.push('/home');
해당 코드는 클라이언트 사이드 라우팅(CSR) 방식입니다.
작동 방식은 스택(Stack)을 생각하시면 쉽게 이해할 수 있습니다.
이동할 때마다 브라우저의 히스토리 스택에 스택처럼 하나하나 쌓이기 때문에 뒤로 가기가 가능합니다.
현재 실행 중인 React 앱은 유지되며, 새로고침 없이 이동됩니다.
이동한 페이지에서 필요한 데이터는 useEffect나 useQuery를 통해 클라이언트에서 다시 불러올 수 있습니다.
CSR인 만큼 빠르고, 부드러운 것이 장점입니다.
window.location.href
브라우저의 기본 내장 객체인 window의 location 속성을 활용해 URL을 변경하는 방식입니다.
window.location.href = '/home';
풀 페이지 리로드(FULL RELOAD)가 발생합니다.
브라우저가 현재 페이지 전체를 새로고침 하는 것
- 서버로부터 HTML, JS, CSS, 이미지 등 모든 리소스를 처음부터 다시 요청합니다.
- 기존에 브라우저 메모리 상에 올라가 있던 React 상태, JS 변수, 클라이언트 상태, 전역 상태 등이 모두 초기화됩니다.
브라우저가 새로고침하듯 완전히 새로 렌더링되며 서버 요청도 새롭게 시작합니다.
만약 SSR 페이지라면 새롭게 서버에서 HTML을 받아옵니다.
새로 렌더링되기 때문에 상태 유지가 불가능하며, 전체 초기화가 필요할 때 사용합니다.
주로
- 로그인 직후 쿠키 갱신을 서버에 확실히 반영하고 싶을 때
- 클라이언트 상태를 완전히 초기화하고 싶을 때
이와 같은 경우에 사용합니다.
하지만, 아마 window와 next.js를 함께 써보신 분이 있다면 궁금한 점이 한 가지 있을 수 있다고 생각합니다.
Q. SSR 화면에서 사용 가능한가요?
바로 요것입니다. window는 브라우저 객체인데, SSR에서 사용 가능하다고?
SSR이 일어나는 시점은 말 그대로, 서버에서 HTML을 만들어 클라이언트에게 보내주는 시점입니다.
이 시점은 Node.js 런타임에서 실행되고, 당연하게도! 브라우저 객체인 window나 document 같은 것은 존재하지 않습니다!
즉 SSR 도중에 window.location.herf를 호출하면 ReferenceError: window is not defined 에러가 발생합니다.
그럼 위에서 말한
만약 SSR 페이지라면 새롭게 서버에서 HTML을 받아옵니다.
는 무엇이냐.
클라이언트에서 렌더링 된 SSR 화면 이후에는 사용 가능합니다!
SSR로 HTML을 만들어 클라이언트로 전달한 이후에는 그 HTML을 브라우저가 받아 렌더링 → hydraion → 클라이언트 코드 실행 순서로 진행됩니다.
이 hydration 이후에는 이제 브라우저 환경이기 때문에 window.location.href가 사용 가능합니다.
useEffect(() => {
window.location.href = '/home';
}, []);
이 코드처럼 useEffect, onClick 등 클라이언트 실행 시점에서 사용하면 문제없이 사용됩니다!
<Link /> 컴포넌트
Next.js에서 가장 권장하는 페이지 이동 방식입니다.
import Link from 'next/link';
<Link href="/home">Go Home</Link>
내부적으로 클라이언트 사이드 라우팅(CSR)을 수행합니다.
prefetch 기능을 지원해 미리 이동할 페이지의 데이터를 백그라운드에서 로딩합니다.
SEO에도 유리하고, 퍼포먼스 최적화에도 좋으며, 렌더링 중 발생하는 상태 변화도 부드럽게 처리 가능합니다.
2. SSR에서 쿠키 판별하기
이번 프로젝트에서는 로그인 여부를 판별하기 위해 서버사이드 렌더링(SSR) 단계에서 쿠키를 읽어 로그인 상태를 확인해야 했습니다. 클라이언트에서 상태를 판별하는 방식도 있지만, SSR에서 로그인 여부를 판별하면 다음과 같은 장점이 있기에 이와 같은 방법을 선택했었습니다.
- 서버에서 로그인 여부에 따라 초기 화면을 다르게 렌더링 가능
- SEO에서도 유리
SSR환경에서 로그인하기 트러블슈팅
const cookieStore = await cookies();
const value = cookieStore.get(name)?.value;
간단하게 이 두 가지만 첨부하겠습니다.
자세한 정보는 Next.js 공식 문서에 SSR에서 cookie를 불러오는 방법 파트에 자세하게 설명되어 있습니다.
처음에는 이런 방식으로 쿠키를 읽어 로그인 상태를 판별했는데, 문제는 로그인 직후 페이지 이동 시점에서 발생했습니다.
제가 생각한 작동 시나리오는 다음과 같았습니다.
- 클라이언트에서 로그인 API 호출 → 서버에서 Set-Cookie 내려줌.
- 로그인 성공 후 router.push('/')로 메인 페이지 이동.
- 메인 페이지의 SSR 코드가 실행
- server component에서 쿠키 읽기
근데 막상 화면에서는 분명 쿠키를 받아오는 게 확인됐는데, 마치 쿠키가 없는 것처럼, 로그인 화면에서 동작하지 않았습니다.
위 이슈의 핵심은 바로 router.push()였습니다.
router.push는 CSR 라우팅 방식이기에 새로고침 없이 페이지 컴포넌트만 교체하였습니다.
서버로 새 요청을 보내지 않았기에 SSR 렌더링도 새로 실행되지 않았고, 결과적으로 SSR에서는 여전히 로그인 이전 상태로 인식하는 문제가 있었습니다.
결국 이 문제를 해결하기 위한 여러 방법 중에서 제가 선택한 방식은
window.location.href 사용하기
였습니다.
브라우저 전체를 새로고침하여 서버로 새 요청을 보내는 방식을 선택하였습니다.
서버로 요청을 보내 쿠키가 반영이 되었고, SSR 화면이 정상 동작하는 방식을 확인하였습니다.
window.location.href를 사용하면 새로고침이 발생하게 되므로 UX가 부드럽지 못하다는 단점이 있을 수 있었지만,
제 프로젝트 같은 경우는 어차피 새로고침을 통해 메인 화면으로 이동하는 방식이 요구되었으므로 문제를 해결할 수 있었습니다.
3. 각 이동 방식은 언제 쓰는 게 적합한가?
이번 경험을 통해, Next.js에서 페이지 이동 방식과 SSR/CSR 쿠키 동기화 문제를 만날 때 어떤 기준으로 선택해야 하는지에 대해 저만의 선택 기준을 좀 정립할 수 있었습니다.
언제 router.push()를 쓰면 좋은가
- 일반적인 클라이언트 사이드 페이지 이동
- eg) 메뉴 이동, 탭 전환, 상세 페이지 이동 등
- SSR 쿠키와 크게 상관없는 경우
- 새로고침이 불필요하고, 부드러운 UX가 중요한 경우
언제 <Link>를 쓰면 좋은가
- 사용자 클릭으로 이동하는 경우
- Next.js에서 제공하는 prefetch 최적화를 활용할 때
- SSR에서 pre-rendering 이득을 최대화하고 싶을 때
언제 window.location.href를 쓰면 좋은가
- SSR 쿠키 상태를 반드시 최신 상태로 반영해야 하는 시점
- 주로 로그인 / 로그아웃 이후
- 서버에 새로 요청을 보내야 하는 경우
- 전체 새로고침이 허용되는 UX 상황
SSR과 CSR에서의 상태관리 고민
Next.js처럼 SSR과 CSR이 혼합되는 프레임워크를 사용할 때, 페이지 이동과 인증 상태 관리 문제는 항상 고민하게 만드는 문제인 것 같습니다.
처음에는 단순히 쿠키만 잘 세팅하면 되겠지 싶은 마음으로 개발을 진행했는데, 페이지 이동 방식, SSR의 요청 시점, 쿠키 반영 타이밍이 서로 엮이면서 의외로 복잡한 비동기 이슈들이 발생했습니다.
그 외에도 개발을 진행하면서 스스로에게 '나 제대로 하고 있는 거 맞나?' 하면서 제 코드를 믿지 못하는 경우도 정말 많았습니다...
특히 이번 트러블 슈팅을 해결하며 가장 크게 배운 점은 다음 두 가지였습니다.
- SSR에서 인증 상태를 신뢰하려면 반드시 서버로 새 요청을 보내야 한다.
- 클라이언트 사이드에서 부드러운 UX를 만들려다 오히려 상태 불일치 이슈를 초래할 수 있다.
결국 이런 경험이 쌓이면서 SSR과 CSR의 시점을 명확히 구분하고, 각 상황에 맞는 라우팅 방법을 선택하는 기준을 만들게 되는 것 같습니다.
'Framework > Next.js' 카테고리의 다른 글
| Next에도 prepatching이 필요할까? (0) | 2025.04.06 |
|---|---|
| SEO. 그게 어떻게 돌아가는 건데? (3) | 2025.01.22 |
| Next.js란 무엇인고 (0) | 2025.01.20 |
