ReferenceError:window is not defined

Theo14·2024년 10월 23일
0

allthatarsenal

목록 보기
5/5
post-thumbnail

앞서 Funnel패턴으로 퀴즈 만들기(All That Arsenal)-1 에서 Funnel.tsx 를 개발하던 와중 다음과 같은 에러가 떴다.

ReferenceError:window is not defined 오류가 왜나왔는지 어떻게 해결하는지 알아보자.

ReferenceError:window is not defined가 나온 이유?

해당 오류 코드가 나오는 이유는 코드가 window객체를 정의 되지 않은 컨텍스트에서 엑세스하려고 시도할 때 발생한다. 이 오류는 주로 react.js어플리케이션에서 ssr을사용하는 경우 혹은 코드가 웹브라우저 환경 외부에서 실행되는 경우에 나타난다.

어???Funnel은 "use client"환경에서 개발해서SSR이 아닐텐데??

내가 알고 있는 지식으로는 "use client" 상에서 이용한다면 해당 페이지는 CSR(Client Side Rendering)이기 때문에 굳이 SSR을 false로 지정하지 않아도 된다고 생각했다. 하지만, stackoverflow에서 Next.js 13 "window is not defined"의 첫번째 답변을 통해 "use client"를 지정하더라도 페이지를 전적으로 클라이언트에서 렌더링하지 않는다라는 답변이 있었다. 서버에서 컴포넌트 코드를 실행하고 그래서 서버에서 사용할 수 없는 window와 같은 것들을 사용할 시에 이 점을 고려해야 한다라고 설명하고 있었다.

use client는 CSR인가요 SSR인가요?

"use client"를 사용하면 무조건 CSR이라고 생각하여 Next.js에서의 렌더링을 다시 알아봐야했다. Next.js는 프리렌더링 기능을 가진 리액트 프레임워크이다. 이건 페이지마다 Next.js가 HTML을 생성하여 SEO와 성능을 향상시키려 한다는 것을 의미한다.

Next.js에서 프리렌더링과 하이드레이션 개념을 다시 알아보자

Next.js는 모든 페이지를 미리 렌더링(pre-render)한다. 서버에서 HTML을 문자열로 가져온 후에, 클라이언트에서 서버에서 보내준 HTML을 hydrate() 혹은 render()하여 브라우저에 렌더링된다

Next.js는 서버에서 보여줄 HTML 컨텐츠를 가져오기 때문에 재차 render() 함수로 HTML을 생성하여 DOM을 그리는 일은 비효율적이다.

따라서 hydrate() 함수로 서버에서 받아온 HTML에 유저가 상호작용할 수 있는 이벤트 리스너만 연결한다. Next.js가 모든 일을 클라이언트 측에서 모든 작업을 수행하는 것이 아니라, 각 페이지의 HTML을 미리 생성하는 것이다.

생성된 HTML은 해당 페이지에 필요한 최소한의 자바스크립트 코드와 연결된다. 그 후 브라우저에 의해 페이지가 로드되면, 자바스크립트 코드가 실행되어 페이지와 유저가 상호작용할 수 있게 된다.

Next.js에서 미리 렌더링 하는 방식은 두 가지로 나뉘며, HTML이 생성되는 시점이 다르다. 하나는 빌드 타임에 HTML에 생성되어 매 요청마다 이를 재사용하게 해주는 SSG(Static-site Generation)이고, 다른 하나는 매 요청마다 HTML을 생성하는 SSR(Server-side Rendering)이다.

그래서 "use client는 CSR인가요 SSR인가요?"라는 질문의 답은 처음에는 서버에서 렌더링되고 이후 클라이언트에서 하이드레이션되어 실행된다. 그리고 상태 업데이트나 상호작용은 클라이언트에서 처리되기때문에 "둘다"라고 할수 있을 것 같다.

해결방법

다시 돌아와서 ReferenceError:window is not defined라는 오류를 해결해보자.
How to solve "window is not defined" errors in React and Next.js를 참고했다.

1. dynamic loading사용하기.

Funnel컴포넌트를 동적 임포트를 사용하여 ssr: false 옵션으로 불러오는 방법을 사용했다.이렇게 하면 컴포넌트가 서버 측에서 렌더링되지 않게 된다.
특히 window를 사용하는 외부 모듈을 임포트할 때 이 방법이 효과적이라고 한다.

이번 프로젝트에서 사용한 방법이다.

import dynamic from "next/dynamic";

// 컴포넌트를 dynamic import로 감싸고 ssr을 false로 설정
const FunnelComponent = dynamic(() => Promise.resolve(FunnelContent), {
	ssr: false,
});
//....

// 메인 export 컴포넌트
export default function Funnel() {
	return <FunnelComponent />;
}

2. type of

if (typeof window !== "undefined") {
  // 브라우저 코드
}

typeof는 window를 평가하지 않고 단순히 그 타입만 확인하기 때문에 Node.js 환경에서는 "undefined"로 처리된다.그러다가 Node.js 환경이 아닌(!=="undefined")window를 참조 할 수 있는 시점이되면 브라우저 코드를 실행하는 코드이다.

3. useEffect hook 사용하기

마지막 방식은 useEffect React 훅을 사용하는 것이다.useEffect는 렌더링 단계에서만 실행되기 때문에 서버에서는 실행되지 않는다.


// components/Scroll.js

import React, { useEffect } from "react";

export default function Scroll() {
  useEffect(function mount() {
    function onScroll() {
      console.log("scroll!");
    }

    window.addEventListener("scroll", onScroll);

    return function unMount() {
      window.removeEventListener("scroll", onScroll);
    };
  });

  return null;
}
// pages/index.js

import Scroll from "../components/Scroll";

export default function Home() {
  return (
    <div style={{ minHeight: "1000px" }}>
      <h1>Home</h1>
      <Scroll />
    </div>
  );
}

위의 예제 처럼 useEffect를 사용하는 방식은 컴포넌트가 마운트/언마운트될 때 리스너를 등록하고 해제하는 것이다. 하지만 마운트 시에만 리스너를 등록하고 이후 렌더링 이벤트는 무시하고 싶다면 다음과 같이 사용 할 수도 있다.


// components/Scroll.js

import React, { useEffect } from "react";

export default function Scroll() {
  useEffect(function onFirstMount() {
    function onScroll() {
      console.log("scroll!");
    }

    window.addEventListener("scroll", onScroll);
  }, []); // empty dependencies array means "run this once on first mount"

  return null;
}

마무리

이번 에러를 겪으며 가장 크게 배운 점은 "use client는 CSR인가요, SSR인가요?"라는 내 질문에 대해 망설임 없이 "CSR"이라고 답했던 순간이었다. Next.js에서 사용되는 용어였고, 그에 따라 pre-rendering과 hydration을 공부했었지만, 학습과 실제 개발에서의 사용을 분리해서 이해하고 있었다는 점이 의외로 실망스러웠다. CS 스터디에서 얻은 지식들이 많지만, 이를 실제 개발 환경에 연결하는 공부가 더 필요하다는 생각이 들었다.

Reference

https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97

https://pusha.tistory.com/entry/Nextjs-Reactjs-window-is-not-defined-%EC%9B%90%EC%9D%B8-%EB%B0%8F-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95

https://velog.io/@yunsungyang-omc/Next.js-%EC%97%90%EB%9F%AC%EB%A1%9C%EA%B7%B8-window-is-not-defined

https://stackoverflow.com/questions/75692116/next-js-13-window-is-not-defined

0개의 댓글