Next.js Window is not defined

이승훈·2025년 4월 1일
0

시행착오

목록 보기
25/28

아니 프론트엔드 서버가 터져가지고 봤더니
아니 클라우드와치에 계속 이런 에러가 누적되어있었네?

아니 근디 찾아보니까 컴포넌트 내부에서 useEffect로 안감싸고 아래코드처럼 router.back()을 호출하니까 이런게 발생한거였네?

"use client";

import { useRouter } from "next/navigation";

export default function CheckupDetailModal() {
  

  if (!specificItem) {
    router.back(); <- 이부분 컴포넌트 렌더링 중 호출되는 부분이라 추정됨 여기가 문제임
    return <></>;
  }


  return (
      <div>어쩌고저쩌고..</div>
}

일단 현상과 원인 적고 깊은 원리는 천천히 알아볼 예정
나중에 까먹을까봐 일단 적음

원인

정리된 원인부터 적자면 아래와 같다.

  • router.back()은 next/navigation의 useRouter에서 가져온 메서드로, 브라우저의 history API에 의존함.
  • 서버 렌더링중에는 window 객체가 없으므로, 컴포넌트의 최상위 스코프에서 router.back()을 호출하면 ReferenceError: window is not defined 에러가 발생

?? 아니 'use client'를 사용하는 컴포넌트인데 뭔 갑자기 서버에서 실행하다 window가 있네 없네 하는거여 ??
'use client' 사용하면 그냥 클라이언트 컴포넌트로서 클라이언트 사이드에서만 렌더링 하는거 아님?

아님 그게 아님.. 요즘 너무 공부를 안하고 있었던게 이렇게 탄로난다.
예전 Next.js 12버전 이전에 사용하던 개념이랑 헷갈렸던것 같다.

'use client' 쓸 때 일어나는 일

'use client'는 Next.js의 App Router에서 클라이언트 컴포넌트임을 선언하는 지시문이다.
클라이언트 컴포넌트는 클라이언트측에서 동작해야 하는 인터랙티브한 로직(useState, useEffect, useRouter 등..)을 포함할 수 있다.
그런데 'use client' 썻다고 서버사이드 렌더링이 완전히 배제되는것이 아니다!
AppRouter를 사용하는 Next.js는 기본적으로 서버에서 컴포넌트를 먼저 렌더링 한다.
그 후 클라이언트에서 하이드레이션 과정을 통해 컴포넌트를 인터렉티브하게 만든다..!

대략 순서대로 적으면 아래와 같다.

  1. 'use client' 를 사용한 컴포넌트라도 App Router에서는 서버에서 초기 HTML을 생성하기 위해 컴포넌트의 JSX 코드가 먼저 서버에서 실행된다.
    -> 이 과정에서 if문 안에있는 Router.back()이 실행된거임.즉, 서버에서 실행한거지.
  2. return문에 있는 JSX는 서버에서 렌더링되어 초기 HTML로 클라이언트에 전송된다.(useEffect나 이벤트 핸들러 같은 코드는 클라이언트에서만 실행되므로 서버 렌더링 중에는 실행되지 않는다.)
  3. 서버에서 생성된 HTML이 브라우저에 전달된 후, Next.js는 클라이언트에서 javascript를 실행 해 컴포넌트를 인터렉티브하게 만든다.(하이드레이션 과정)
    -> javascript를 실행하는 과정에서 'use client'로 선언된 컴포넌트는 클라이언트 측에서 다시 렌더링되며 useEffect, useState 같은 훅들이 동작할 수 있게 된다.

근데 하이드레이션(hydration)이 정확히 뭐임?

하이드레이션은 서버에서 생성된 HTML을 클라이언트(브라우저)에서 javascript로 활성화 하는 과정이다.
즉, 서버에서 미리 렌더링된 정적인 HTML에 React(Next.js)의 인터랙티브한 기능을 붙여서 사용자가 버튼 클릭이나 상태 변경 같은 동적인 동작을 할 수 있도록 만드는 과정이다.

하이드레이션 과정은 아래와 같다.

  1. 사용자가 페이지를 요청하면 Next.js 서버에서 HTML, Javascript 번틀을 브라우저로 전송한다.
    -> 이 HTML은 사용자에게 빠르게 표시된다. 그래서 페이지가 빠르게 로드되는 느낌을 줌(SEO, 초기 로딩 속도에 유리)
  2. (하이드레이션)브라우저가 HTML을 받은 후, Next.js(React)는 Javascript를 실행 해 서버에서 받은 HTML을 React 컴포넌트트리로 재구성한다.이 과정에서 'use client'로 선언된 컴포넌트의 클라이언트 측 로직(useEffect, useState, 이벤트 핸들러)이 실행된다.
  3. (하이드레이션)React는 서버에서 받은 HTML과 클라이언트에서 렌더링한 컴포넌트를 비교해 동일 구조임을 확인한 뒤 이벤트 리스터를 붙여 인터렉티브하게 만든다.
  4. 하이드레이션이 완료되면, 페이지가 완전히 인터렉티브해진다. 버튼을 클릭하거나 useState로 상태를 변경할 수 있게되는거다.

서버로부터 받은 Javascript 파일안에 React 코드와 이를 실행하기 위한 React 런타임, 그리고 Next.js의 클라이언트 측 로직이 포함되어있다.
그래서 컴포넌트 트리를 만들고 DOM 트리와 비교할 수 있는거임

  • React 코드 : 'use client'로 선언된 컴포넌트의 javascript 코드(예: JSX를 변환한 코드, useEffect, useState 같은 훅, 이벤트 핸들러 등)가 포함됨. 이 코드는 컴포넌트의 구조와 동작을 정의
  • React 런타임 : React가 브라우저에서 컴포넌트를 렌더링하고, 상태를 관리하며, 이벤트 리스너를 추가하는데 필요한 라이브러리 코드
  • Next.js 관련 코드 : Next.js의 클라이언트 측 라우팅(useRouter같은 훅), 하이드레이션 로직, 그리고 페이지 간 전환을 처리하는 코드들
  • 번들 : 이 모든 코드는 Next.js가 빌드 시 Webpack같은 도루고 번들링되어 최적화된 Javascript 파일로 브라우저에 전송됨.

해결

"use client";

import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function CheckupDetailModal({ specificItem }) {
  const router = useRouter();

  useEffect(() => {
    if (!specificItem) {
      router.back();
    }
  }, [specificItem, router]);

  if (!specificItem) {
    return null; // 또는 로딩 상태나 빈 프래그먼트를 반환
  }

  return <div>어쩌고저쩌고..</div>;
}

아주 간단하죵.?
그냥 router.bakc() 호출을 클라이언트 측에서만 실행되도록 강제하면 될일임.
이를 위해 useEffect를 사용해 클라이언트 렌더링이 완료된 후 해당 로직을 실행하도록 수정함.

애초에 기본도 모르고 작성한 코드가 문제였고 이제 이유 알았음.
재밋었다.

profile
Beyond the wall

0개의 댓글