[NextJS] window is not defined

김현욱·2022년 9월 30일
0

개발일지

목록 보기
3/3

1. Introduction

아마 NextJs 를 처음 접하고 프로젝트를 만들다보면 'window is not defined' 문구와 함께 window 객체를 쓰지 못하는 경우를 마주치게 될 것입니다.
기존에 Vue 나 React 만 사용하셨던 분들은 "window 가 왜 undefined 라는 거지?? 그럴 수가 있나??" 라는 생각이 드실 겁니다. (왜냐하면 제가 그랬거든요....😂 )
특히 카카오 맵, 아임포트 같이 scirpt 로 다운로드 받은 후 초기화 해줘야 하는 외부 API 를 사용할 때 문제가 됩니다.

이 문제 비단 개발과정에서만 발생하는 것이 아닙니다. 저 같은 경우에 열심히 작업하고 React 프로젝트에서 했던 방식처럼 S3로 배포 했었는 데 분명히 npm run dev 로 실행했을 땐 잘 되던 프로젝트들이 S3 로 배포만 하면 데이터가 하나도 안 뜨던 문제가 발생했었습니다.

"window is not defined" 를 해결하면 생각보다 여러 문제가 같이 풀리니 NextJs 를 사용하실 팀 분들은 같이 고려하시면 좋을 것 같습니다.

제가 해당 Error 를 해결하면서 배운 정보와 방법을 공유하려고 합니다.
혹시 틀린 부분이 있거나 더 좋은 해결 방법이 있다면 댓글로 알려주시면 감사하겠습니다😊

2. The reason why happen Error

결론부터 말씀드리면 HTML Rendering 시점과 방식의 차이 때문입니다.

Background

먼저 Nextjs 의 배경부터 살펴보겠습니다. 본 자료는 공식문서를 참고하였습니다.

우선 NextJs 라는 프레임워크 탄생 배경을 이해하려면 React 를 먼저 알아야 합니다.

What is the React??

React는 사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다. “컴포넌트”라고 불리는 작고 고립된 코드의 파편을 이용하여 복잡한 UI를 구성하도록 돕습니다.

React 는 Javacsript(Typescript) 에서 가장 유명한 SPA 라이브러입니다.
예전에는 SSR 방식, 즉, 서버에서 HTML을 랜더링 한 후 Client 에 전송하는 방식이었지만 인터넷 연결이 안 좋을 때 이슈, 서버에 부담 등의 단점이 있었습니다.

이후 V8엔진과 더불어 브라우저 성능이 좋아지면서 CSR 방식이 유행을 하고 현재 많은 웹페이지들이 CSR 방식을 채택했습니다.
제가 생각하기엔 웹이 단순히 정보를 나열하기만 했던 과거와 달리 사용자와 상호작용

CSR 방식을 간단하게 설명하자면 다음과 같습니다.

html,css,javascript 랜더링하지 않고 브라우저에게 모두 전달을 합니다.
그 파일들을 받은 브라우저가 해당 파일들을 가지고 html 을 랜더링으로 하고 사용자에게 웹페이지를 보여줍니다.

이런 방식 때문에 CSR 방식의 문제점으로 자주 언급되는 3가지가 다음과 같습니다.

  1. 초기 랜더링 시간이 SSR 방식에 비해 오래 걸린다.
  2. SEO(검색엔진최적화)에 적합한 방식은 아니다.
  3. 사용자가 아무것도 없는 html을 받기 때문에 브라우저가 Javascript 를 실행시키지 못하면 빈화면을 본다.

보다 자세한 내용은 여기를 참고해보시면 좋을 것 같습니다

NextJs 가 가지고 있는 가장 특징적인 부분이 이와 관련되어 있습니다.
NextJs 는 모든 페이지를 Pre-rendering 합니다.

By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and SEO.
NextJs 는 모든 페이지를 pre-render 시킵니다. 다시 말하자면 client-side 에서 Javascript 가 모든 것을 하는 대신에 HTML 페이지들을 사전에 만들어놓습니다.
Pre-render 를 통해 더 좋은 퍼포먼스와 SEO 최적화를 만들어 낼 수 있습니다.
-출처 : NextJs Document

참고로 NextJs 는 Pre-render 방법으로 크게 Static-generation(build 시 생성) 과 Server-generation(request 요청 시 생성) 2가지를 제공하고 있습니다. 각각의 method 들의 특징과 사용 방법은 기회가 된다면 다루어 보겠습니다.

The Reason why occur error

바로 이 Pre-rendering 에서 "window is not definded" 에러가 발생하게 됩니다.
해당 오류가 React 에서는 발생하지 않고 NextJs 에서 발생하는 이유를 다시 한 번 정리해보겠습니다.

React 는 Rendering 과정을 브라우저에게 모두 위임합니다.(client-side rendering)
브라우저는 window 객체를 항상 가지고 있기 때문에 해당 오류가 발생하지 않습니다.
NextJs 의 경우 모든 페이지를 Pre-rendering 을 합니다.(server-side rendering)
즉, 웹 페이지가 만들어지는 시점이 server(NodeJs) 이고 nodejs 는 winddow 객체를 가지고 있지 않습니다. 그렇기 때문에 특정 웹페이즈를 요청했을 때 해당 오류가 발생하게 됩니다.

When??

어디서 발생하는 지는 알기가 어렵습니다. 어떤 시점에 어떠한 방식으로 HTML 을 요청할 지 모르기 때문입니다. 그렇기 때문에 브라우저가 실행시켜야 하는 객체들은 꼭 따로 처리를 해주셔야 예상하지 못한 에러를 피하실 수 있습니다.

3. How can i solve this?

문제의 발생지점을 생각해보면 해결책은 쉽게 찾을 수 있습니다.
해결하는 방식은 타 블로그나 Stackoverflow 에 잘 정리가 되어 있기 때문에 간단하게만 언급하겠습니다.

  • Dynamic import
    NextJs 도 해당 부분을 인지해서 따로 모듈을 만들어놨습니다.
    꼭 window 객체를 처리하기 위한 모듈은 아니고 컴포넌트나 라이브러리들의 import 방식을 정할 수 있는 모듈입니다.
    ssr:false 로 import 해 오면 SSR 방식을 사용하지 않는, 즉 Client-side Rendering 방식으로 불러올 수 있게 되어 에러가 발생하지 않습니다.
  • useEffect 사용하기
    React 에서는 Components 를 Class 방식 대신 Function 방식으로 만들기를 권고하고 있습니다.
    useEffect 는 React 에서 사용하는 생명주기 Method 입니다.
    해당 Method 를 통해 컴포넌들이 모두 불러와졌을 때, 즉, Pre-rendering 한 페이지가 브라우저에 온전히 도착했을 때 window 객체를 불러오도록 설정할 수 있습니다.
    useEffect 를 사용하면 쉽게 에러를 해결할 수 있습니다.

참고 : React의 class 생명주기 메서드에 친숙하다면, useEffect Hook을 componentDidMount와 componentDidUpdate, componentWillUnmount가 합쳐진 것으로 생각해도 좋습니다. - 출처 : React Document

블로그를 찾아보시면 다음과 같은 방식으로도 처리할 수 있습니다.

if(typeof window === undefined) ... 

위의 방법을 사용하면 에러가 발생하지 않고 프로그램이 동작할 것입니다.
하지만 제가 생각하기에 위의 방식은 window 가 undefined 가 됐을 때 로직을 만들어줘야하고 특별한 이유가 없이 따로 처리해줘야하면 useEffect 와 역할이 너무 중복되고 react 를 잘 사용하지 못하고 있다는 생각이 듭니다.
(안드로이드로 따지면 onCreate 에서 초기에 필요한 설정하는 함수를 실행시키면 되는 데 굳이 다른 생명주기에서 처리해주는 느낌이랄까요..?)

자세한 방법이나 코드들은 다른 분들이 잘 정리해주셨으니 참고하시면 좋을 거 같습니다.

4. More...

글 서두에서 말했던 다른 부분도 해결된다 라는 예시를 간단하게 소개하겠습니다.
지금 생각해보면 바보 같은 행동이었던 거 같습니다.

  1. getStaticProps 에 동적으로 받와야하는 api 관련 로직을 넣었습니다.
  2. getStaticProps, getServerSideProps 등의 함수를 사용하고 s3를 통해 배포를 했었습니다.

1번은 오류는 build 시 rendering 을 해버리기 때문에 동적으로 받아와야하는 정보들을 넣으시면 안됩니다.
2번은 Pre-render 를 시키려면 WAS 같은 서버가 필요한데 단순히 파일을 저장 제공하는 S3 에 넣어서 배포하면 안됩니다.

그러면 귀찮게 이런 거 쓰지말고 React 쓰면 되는 거 아니야? 라고 생각하실 수도 있습니다.
하지만 NextJs 는 Pre-rendering 뿐만 아니라 Css Module, Image optimize, Structure 등 다양한 기능을 제공합니다.
새로운 React 프로젝트를 시작하실 예정이라면 NextJs 도 후보 스택에 넣으시면 좋을 것 같습니다~

감사합니다😊

profile
아 왜 안돼?

0개의 댓글