2026. 6. 30.

Next.js window is not defined, 오류 줄에서 실행 위치 찾는 순서

Next.js에서 window is not defined 또는 localStorage is not defined가 뜰 때, 오류 줄부터 client 경계와 실행 시점을 좁혀 최소 수정하는 순서입니다.

5 min read
Next.js window is not defined, 오류 줄에서 실행 위치 찾는 순서 대표 이미지

AI가 Next.js 화면을 만들어 줬는데 실행하자마자 ReferenceError: window is not defined가 뜨면, 초보자는 보통 페이지 전체에 "use client"를 붙이거나 코드를 다시 만들어 달라고 합니다. localStorage is not defined, document is not defined도 비슷하게 보입니다. 하지만 이 오류의 첫 질문은 화면 구조가 아니라 오류가 난 줄이 서버 렌더 중 실행됐는지입니다.

Next.js App Router에서는 서버에서 먼저 렌더링되는 코드와 브라우저에서 실행되는 코드가 나뉩니다. window, document, localStorage는 브라우저 쪽 API입니다. 서버가 HTML을 만들 때 이 값을 바로 읽으면 정의되지 않았다는 오류가 납니다. 오늘 목표는 Next.js를 전부 설명받는 것이 아니라 브라우저에서만 실행해야 하는 줄과 시점을 분리하는 것입니다.

Next.js window 오류에서 서버와 브라우저 실행 위치를 구분하는 대표 이미지

Next.js의 use client 문서는 이 지시어가 Client Component의 진입점이자 서버·클라이언트 경계를 선언한다고 설명합니다. React의 useEffect 문서는 Effect가 클라이언트에서만 실행된다고 설명합니다. MDN의 Window.localStorage 문서localStoragewindow에 붙은 origin별 브라우저 저장소라고 설명합니다. 그래서 답은 하나로 고정되지 않습니다. use client가 필요한 코드인지, 렌더링 뒤에 읽어야 하는 값인지, 브라우저 전용 라이브러리를 따로 불러야 하는지 나눠야 합니다.

첫 오류 줄과 브라우저 API 이름부터 적습니다

먼저 터미널이나 브라우저 콘솔에서 첫 에러 줄을 찾습니다. 아래처럼 메시지에 API 이름이 들어갑니다.

ReferenceError: window is not defined
ReferenceError: localStorage is not defined
ReferenceError: document is not defined

이 줄만 보고 바로 전체 코드를 바꾸면 범위가 커집니다. 먼저 세 칸을 채웁니다.

에러 메시지: ReferenceError: window is not defined
문제 파일: app/page.tsx
문제 줄: const width = window.innerWidth

여기서 봐야 할 것은 함수 이름이나 컴포넌트 이름보다 브라우저에서만 존재하는 값입니다. window.innerWidth, localStorage.getItem, document.querySelector가 렌더링 중 바로 실행되면 서버에서 터질 수 있습니다.

AI에게는 이렇게 좁혀 묻습니다.

Next.js에서 ReferenceError: window is not defined가 납니다.
문제 줄은 app/page.tsx의 window.innerWidth입니다.
전체 파일을 클라이언트로 바꾸기 전에, 이 줄이 서버 렌더 중 실행되는지 먼저 판단해 주세요.

이 요청의 핵심은 AI가 고칠 범위를 오류 줄 하나로 시작하게 만드는 것입니다.

use client는 작은 컴포넌트 경계에만 둡니다

클릭, 입력, 브라우저 저장소, 화면 크기처럼 브라우저에서만 필요한 값이 들어간 컴포넌트라면 클라이언트 컴포넌트 경계가 필요할 수 있습니다. Next.js 문서 기준으로 "use client"는 파일 맨 위에 두며, 그 파일에서 내보내는 컴포넌트가 클라이언트 진입점이 됩니다.

"use client";

export default function WidthBadge() {
  return <span>{window.innerWidth}</span>;
}

하지만 위 코드는 아직 충분하지 않습니다. 파일이 클라이언트 컴포넌트가 되어도 첫 렌더 중 window.innerWidth를 바로 읽으면 렌더링 타이밍 문제가 남을 수 있습니다. 또 페이지 전체에 "use client"를 붙이면 서버에서 처리해도 되는 영역까지 클라이언트 쪽으로 밀릴 수 있습니다.

초보자가 먼저 할 일은 작은 컴포넌트로 경계를 자르는 것입니다.

// app/page.tsx
import WidthBadge from "./WidthBadge";

export default function Page() {
  return (
    <main>
      <h1>대시보드</h1>
      <WidthBadge />
    </main>
  );
}
// app/WidthBadge.tsx
"use client";

export default function WidthBadge() {
  return <span>화면 크기 확인 중</span>;
}

이 단계에서는 아직 브라우저 값을 읽지 않았습니다. 먼저 경계를 좁게 만든 뒤, 다음 단계에서 브라우저 값을 안전한 시점에 읽습니다. 전체 페이지가 아니라 브라우저 API를 쓰는 작은 파일만 클라이언트로 둡니다.

Next.js window 오류를 오류 줄, client 경계, Effect, 브라우저 전용 라이브러리 순서로 확인하는 흐름

경계 파일이 헷갈리면 Next.js use client를 붙일 파일만 고르는 순서에서 컴포넌트 범위를 대조할 수 있습니다. 여기서는 window를 읽는 줄과 시점만 먼저 좁힙니다.

브라우저 값은 useEffect 안에서 읽습니다

브라우저 API 값이 화면이 뜬 뒤에 필요하다면 useEffect 안으로 옮깁니다. React 문서의 설명처럼 Effect는 클라이언트에서만 실행되므로, 서버가 HTML을 만드는 시점과 분리할 수 있습니다.

"use client";

import { useEffect, useState } from "react";

export default function WidthBadge() {
  const [width, setWidth] = useState<number | null>(null);

  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);

  return <span>{width === null ? "확인 중" : `${width}px`}</span>;
}

localStorage도 같은 방식으로 봅니다. MDN 기준으로 localStorage는 브라우저의 window에 연결된 저장소입니다. 서버에서 바로 읽는 값이 아닙니다.

"use client";

import { useEffect, useState } from "react";

export default function SavedName() {
  const [name, setName] = useState("");

  useEffect(() => {
    const saved = localStorage.getItem("name");
    if (saved) setName(saved);
  }, []);

  return <p>{name || "이름 없음"}</p>;
}

여기서 주의할 점은 예제를 그대로 확장해 민감정보를 저장하지 않는 것입니다. 토큰, 비밀번호, 결제 정보는 이런 연습 예제처럼 localStorage에 넣을 대상이 아닙니다. 이 글의 범위는 저장소 설계가 아니라 localStorage is not defined 오류의 실행 위치를 분리하는 것입니다.

AI에게는 이렇게 요청합니다.

localStorage를 렌더링 중 바로 읽고 있어서 오류가 납니다.
이 컴포넌트만 클라이언트 컴포넌트로 두고,
localStorage.getItem은 useEffect 안에서 읽도록 최소 수정해 주세요.
저장 키 이름과 화면 구조는 바꾸지 마세요.

이 문장은 AI가 새 상태 관리 구조를 만들지 못하게 막습니다. 지금 필요한 것은 구조 개편이 아니라 브라우저 값을 읽는 시점 이동입니다.

브라우저 전용 라이브러리는 dynamic import로 분리합니다

차트, 지도, 에디터 라이브러리처럼 내부에서 windowdocument를 바로 쓰는 패키지도 있습니다. 내 코드에는 window가 없어 보이는데 빌드나 실행 중 같은 오류가 난다면 라이브러리가 서버에서 로드되는 순간 브라우저 API를 읽고 있을 수 있습니다.

Next.js의 Lazy Loading 문서next/dynamic을 사용한 지연 로딩을 설명하고, Client Component에서만 ssr: false 옵션으로 사전 렌더링을 끌 수 있다고 안내합니다. 이 선택지는 모든 오류의 첫 해결책이 아닙니다. 라이브러리 자체가 브라우저에서만 동작할 때 쓰는 분리 방식입니다.

"use client";

import dynamic from "next/dynamic";

const BrowserChart = dynamic(() => import("./BrowserChart"), {
  ssr: false
});

export default function ChartPanel() {
  return <BrowserChart />;
}

이때도 먼저 확인해야 할 것이 있습니다.

상황먼저 할 일
내 코드에 window가 바로 보임작은 클라이언트 컴포넌트와 useEffect로 분리
라이브러리 import 순간 오류가 남Client Component 안에서 dynamicssr: false 검토
버튼 클릭 뒤에만 필요한 라이브러리클릭 핸들러나 동적 로드로 늦게 불러오기
서버에서도 필요한 데이터 처리브라우저 API 없는 순수 함수로 분리

ssr: false는 편한 우회 버튼이 아닙니다. 서버에서 그려야 할 콘텐츠까지 모두 미루면 첫 화면이나 SEO에 영향을 줄 수 있습니다. 브라우저에서만 의미가 있는 부분에만 적용합니다.

AI에게 넘길 5칸을 채우면 수정 범위가 줄어듭니다

여기까지 봤다면 AI에게 전체 프로젝트를 맡기지 말고 다섯 칸을 먼저 채웁니다.

Next.js window 오류를 AI에게 묻기 전 채울 다섯 칸 점검표
에러 메시지:
문제 파일:
문제 줄:
브라우저 API 이름:
원하는 수정 범위:

예시는 이렇습니다.

에러 메시지: ReferenceError: localStorage is not defined
문제 파일: app/components/SavedName.tsx
문제 줄: const saved = localStorage.getItem("name")
브라우저 API 이름: localStorage
원하는 수정 범위: 이 컴포넌트만 수정, 페이지 전체 구조 변경 금지

요청:
1. 이 코드가 서버 렌더 중 실행되는지 먼저 설명해 주세요.
2. 필요한 경우 작은 클라이언트 컴포넌트 경계만 추가해 주세요.
3. localStorage 읽기는 useEffect 안으로 옮겨 주세요.
4. dynamic import가 필요한 상황인지 아닌지 판단해 주세요.

이 프롬프트의 목적은 AI에게 더 많은 일을 시키는 것이 아닙니다. AI가 추측으로 바꿀 수 있는 범위를 줄이는 것입니다. 파일 전체를 다시 만들기 전에 에러 줄, 실행 위치, 브라우저 API 이름, 수정 금지 범위를 고정하면 결과가 짧아집니다.

오늘은 서버와 브라우저 경계만 나눕니다

window is not defined 오류는 Next.js가 망가졌다는 뜻이 아닙니다. 브라우저에만 있는 값을 서버가 먼저 읽었다는 신호일 때가 많습니다. 볼 순서는 네 가지입니다.

1. 첫 에러 줄에서 브라우저 API 이름을 찾는다.
2. 그 줄이 서버 렌더 중 실행되는지 본다.
3. 작은 컴포넌트에만 "use client" 경계를 둔다.
4. 값 읽기는 useEffect나 dynamic import로 필요한 시점까지 늦춘다.

코딩사관학교 초보자라면 오늘은 한 가지만 기억하면 됩니다. 이 오류의 첫 행동은 전체 재작성 요청이 아니라 실행 위치 확인입니다. 그 다음 AI에게 "이 줄을 브라우저에서만 실행되게 최소 수정해 달라"고 요청하면 됩니다.

저장값이 새로고침 뒤 사라지는 문제까지 이어진다면 React state와 저장소를 나누는 순서로 범위를 확장할 수 있습니다. 지금은 먼저 오류가 난 한 줄을 고친 뒤 개발 서버와 새로고침에서 같은 화면이 유지되는지 확인하세요. 버전별 동작 차이는 공식 문서와 최신 릴리스 노트를 확인하세요.

참고 출처

다음으로 읽을 기사

같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.

댓글 0

이 글을 읽은 독자들의 생각을 나눠보세요.

비밀번호(선택)

첫 번째 댓글을 남겨보세요

여러분의 생각이 다른 독자에게 도움이 됩니다.