TechFeedTechFeed
Frontend

Objects are not valid as a React child, 객체, Promise | 카카오 유저를 화면에 넣었는데 빨간 줄이면?

Objects are not valid as a React child는 일반 객체, Date, Promise를 JSX 자식으로 넣었을 때 납니다. 카카오 유저는 닉네임 필드를, 목록은 map을, 프로미스는 await나 use로 바꿉니다. 리액트 31번, 넥스트, JSON.stringify, 프론트엔드, 1인 개발자 기준. 2026년 9월 리액트·넥스트 공식 문서.

by

핵심만: 콘솔에 객체가 잘 찍혀도 리액트 화면 자식으로는 못 넣습니다. 일반 객체, 날짜, 프로미스를 그대로 넣으면 개발에선 키 목록이, 배포에선 31번으로만 남습니다.


카카오 유저는 닉네임 필드를, 목록은 한 줄씩 엘리먼트로, 프로미스는 기다린 값만 남깁니다. 훅 지시자나 브라우저 전용 값 칸과 자리를 나눕니다.


이런 분에게
  • 카카오 로그인은 됐는데 프로필 줄에서만 빨간 줄이 뜨는 사람
  • 배포만 31번 미니파이 에러가 나는 사람
  • 서버에서 받은 값을 화면 자식으로 바로 넣은 사람

※ 기준일 2026-09-04. 리액트 31번 에러, 목록 렌더, 넥스트 데이터 가져오기, 카카오 사용자 정보.


콘솔에 객체가 찍혀도 화면 자식은 아니다

리액트는 일반 객체를 화면에 어떻게 그릴지 정하지 않습니다. 개발 모드 메시지는 키 목록을 보여 주고, 배포 빌드는 같은 자리를 31번 미니파이 에러로만 남깁니다. 공식 디코더가 원문을 돌려 줍니다. 첫 검색어는 Objects are not valid as a React child입니다.


제가 카카오 로그인 응답을 화면에 넣다 본 줄은 거의 항상 닉네임이 아니라 객체 통째였습니다. 로그는 펼쳐 보여 주니 된 것 같고, 태그 안에 유저 변수를 넣으면 그리기 규칙이 없어서 바로 죽습니다. 토스 결제 알림 본문, 다음 주소 검색 결과, 슈퍼베이스 행 하나도 같은 자리입니다.


하이드레이션 미스매치나 브라우저 전용 값, 훅 지시자와는 칸이 다릅니다. 여기는 서버와 브라우저가 갈라진 게 아니라, 자식으로 올 수 없는 값을 넣은 칸만 봅니다. 1인 개발자가 한국 로그인 응답을 붙일 때 가장 자주 걸리는 줄이기도 합니다.


먼저 기억할 것 | 콘솔이 초록이어도 화면 자식은 따로 검사합니다. 로그에 객체가 보이면 그 값을 태그 안에 넣지 말고, 문자열이나 숫자 필드만 꺼냅니다.


어떤 값이 리액트 자식이 되나

문자열, 숫자, 엘리먼트, 빈 값, 그리고 그 배열만 됩니다. 엘리먼트 만들기 문서가 자식으로 허용하는 건 리액트 엘리먼트, 문자열, 숫자, 포털, 빈 노드(없음, 참, 거짓), 그리고 그 노드들의 배열입니다. 일반 객체, 날짜, 프로미스, 오류 객체, 맵, 셋은 여기에 없습니다.


넣은 값화면먼저 볼 줄
문자열, 숫자그대로 글자닉네임, 번호
참, 거짓, 없음아무것도 안 그림조건으로 가리기
엘리먼트 배열목록으로 그림한 줄씩 바꾼 결과
일반 객체31번 에러유저, 오류 통째
날짜, 오류 객체31번 에러지금 시각, 오류 통째
프로미스프로미스를 찾았다는 메시지기다림 없는 요청, 비동기 페이지

배열 자체는 자식이 될 수 있습니다. 다만 배열 칸마다 객체가 들어 있으면 그 칸이 다시 31번을 냅니다. 회원 목록을 통째로 넣은 줄이 여기입니다. 공식 목록 문서는 객체 배열을 한 줄씩 엘리먼트로 바꾸라고 합니다. 한국 1인 쇼핑몰 관리자 목록도 같은 규칙입니다.


리액트 화면 자식으로 객체를 넣지 않고 필드만 꺼내 그리는 개발 모니터
콘솔에 객체가 보여도 JSX에는 문자열이나 숫자 필드만 넣는다

카카오 유저를 통째로 넣은 자리

닉네임 필드를 꺼내 넣고, 목록은 한 줄씩 바꿉니다. 카카오 사용자 정보 응답은 번호와 닉네임이 겹칸 객체로 옵니다. 로그인 화면이 된 뒤 프로필 한 줄만 죽는 패턴이 거의 여기입니다. 카카오 사용자 정보 응답을 유저 통째로 넣지 마세요.


다음 주소 검색도 같습니다. 도로명, 지번이 객체 안에 들어 있으니 화면에는 도로명 문자열만 꺼냅니다. 토스 결제 알림 본문을 관리자 화면에 디버그로 뿌릴 때도 원문 객체를 자식으로 두면 같은 빨간 줄입니다. 한국 서비스 응답은 거의 다 객체라서, 화면 칸을 정하는 일이 따로 있습니다.


카카오 유저 객체는 필드를 골라 넣기
// 깨지는 줄. user는 일반 객체다 function Profile({ user }) { return <p>{user}</p>; } // 고친 줄. 닉네임 문자열만 자식이 된다 function Profile({ user }) { const nick = user?.kakao_account?.profile?.nickname ?? '이름 없음'; return <p>{nick}</p>; } // 목록은 객체 배열을 엘리먼트 배열로 바꾼다 function MemberList({ members }) { return ( <ul> {members.map((m) => ( <li key={m.id}>{m.nickname}</li> ))} </ul> ); }

화살표 함수 중괄호 | 한 줄씩 바꿀 때 중괄호만 열고 반환을 빼면 그 칸은 값이 없습니다. 화면이 비는 것과 31번은 다른 줄이니, 블록 몸이면 반환을 쓰거나 괄호로 태그를 감싸세요. 공식 목록 문서가 이 함정을 따로 적습니다.


Promise와 Date는 왜 깨지나

둘 다 객체라서, 기다리거나 문자열로 바꿔야 합니다. 메시지에 프로미스를 찾았다고 나오면 화면이 프로미스를 자식으로 받은 상태입니다. 넥스트 앱 폴더 서버 컴포넌트는 기다림으로 풀고, 클라이언트는 use로 읽습니다. 페이지 폴더에서 비동기 페이지를 그대로 두면 같은 줄이 납니다.


지금 시각을 그대로 넣어도 객체입니다. 표준 시각 문자열이거나 서울 시간대 문자열로 만든 뒤에 넣으세요. 시간대 칸은 날짜 글자 불일치에서 따로 다룹니다. 오류는 객체 통째가 아니라 메시지 문자열입니다.


프로미스는 await 또는 use로 풀기
// 앱 라우터 서버 컴포넌트. fetch 결과를 기다린 뒤 필드만 그린다 export default async function Page() { const res = await fetch('https://example.com/me', { cache: 'no-store' }); const user = await res.json(); return <p>{user.nickname}</p>; } // 클라이언트는 async 함수 컴포넌트를 쓰지 말고, 받은 프로미스를 use로 읽는다 'use client'; import { use } from 'react'; function Nickname({ userPromise }) { const user = use(userPromise); return <p>{user.nickname}</p>; }
서버에서 받은 JSON을 기다린 뒤 필드만 화면에 넣는 흐름을 떠올리게 하는 서버랙 통로
프로미스는 화면 자식이 아니다. 기다린 값의 문자열 필드만 넣는다

고치는 순서 세 칸

메시지에서 찾은 값을 읽고, 타입을 확인하고, 필드만 남깁니다. 개발 모드면 키 목록이 이미 힌트입니다. 배포만 31번이면 로컬에서 같은 화면을 개발 모드로 한 번 재현하세요. 미니파이 숫자는 리액트 31번 페이지가 원문을 돌려 줍니다.


메시지원인다음 한 줄
키가 나열된 객체일반 객체를 자식으로 넣음닉네임, 제목 같은 문자열 필드
프로미스를 찾음기다림이 없음서버는 기다림, 화면은 use
날짜 객체시각을 그대로 넣음서울 시간대 문자열
오류 객체오류를 그대로 넣음메시지 문자열
배열만 의심객체 배열을 한 줄씩 안 바꿈한 줄씩 + 고유 키

타입 확인은 값의 종류와 생성자 이름이면 충분합니다. 객체로 나오면 그 값을 자식에서 빼고 필드를 고릅니다. 넥스트 데이터 가져오기는 공식 가져오기 문서가 서버에서 기다리는 칸을 기준으로 둡니다. 카카오 콜백 뒤 프로필 한 줄이 이 순서면 대부분 끝납니다.


  • [ ] 개발 모드에서 found 옆 타입과 키 목록을 읽었다
  • [ ] typeof와 toString으로 객체, Date, Promise, Error를 갈랐다
  • [ ] 화면에는 문자열 또는 숫자 필드만 남겼다
  • [ ] 목록이면 map으로 엘리먼트를 만들고 key를 달았다
  • [ ] 배포 31번이면 로컬 개발 모드로 같은 화면을 재현했다

JSON.stringify는 디버그 칸이다

배포 화면에 제이슨 원문을 그대로 두지 마세요. 문자열로 바꾸기는 객체를 글자로 바꿔 31번을 잠깐 피하게 해 줍니다. 로컬에서 키가 뭔지 볼 때만 쓰고, 회원 화면에는 닉네임, 이메일처럼 고른 칸만 남깁니다. 날짜는 표준 시각 글자가 되고, 없는 칸은 빠지니 디버그 결과와 실제 필드가 다를 수 있습니다.


시크릿이 섞인 카카오 토큰, 토스 결제 알림 원문을 화면에 뿌리는 습관은 로그 칸으로 옮기세요. 화면 자식 에러를 없애는 일과 개인정보를 노출하는 일은 별개입니다. 한국 1인 서비스는 이 두 칸을 처음부터 나누는 편이 덜 다칩니다.


디버그용 문자열, 화면용 필드
// 로컬에서 키를 확인할 때만 <pre>{JSON.stringify(user, null, 2)}</pre> // 회원 화면에 남을 줄 <p>{user.kakao_account?.profile?.nickname ?? '이름 없음'}</p>
디버그 JSON과 실제 프로필 필드를 구분해 작업하는 모니터 책상
stringify는 키를 보는 디버그용이다. 배포 화면에는 고른 필드만 남긴다

참고 자료


자주 묻는 질문

콘솔엔 객체가 나오는데 화면만 깨져요.

로그는 객체를 펼쳐 보여 주고, 화면은 문자열과 숫자, 엘리먼트만 그립니다. 로그가 성공한 값을 유저 통째로 넣지 말고 닉네임 같은 필드만 꺼내세요.


배포만 31번 미니파이 에러가 나요.

같은 원인입니다. 배포 빌드는 메시지를 숫자로 줄입니다. 로컬 개발 모드로 재현하면 키 목록이 나옵니다. 리액트 31번 페이지가 원문을 복원합니다.


배열을 넣었는데도 객체 에러가 나요.

배열 칸마다 객체가 들어 있으면 그 칸이 31번을 냅니다. 회원 목록 통째가 아니라 닉네임 문자열을 가진 엘리먼트를 한 줄씩 만드세요. 키는 형제 사이에서 고유해야 합니다.


문자열로 바꾸면 끝나나요.

로컬에서 키를 보는 디버그용입니다. 회원 화면에 제이슨 원문을 남기지 말고, 닉네임처럼 고른 필드만 그리세요. 날짜와 없는 칸은 디버그 결과가 실제 화면 값과 다를 수 있습니다.


하이드레이션 미스매치랑 같은 건가요.

아닙니다. 미스매치는 서버 화면과 브라우저 첫 그림이 다른 칸이고, 31번은 자식 타입이 객체인 칸입니다. 브라우저 전용 값, 훅 지시자와도 자리를 나눕니다.


앱 폴더에서 프로미스를 클라이언트에 넘기면요.

서버에서 만든 프로미스를 속성으로 넘기고, 클라이언트는 use로 읽습니다. 태그에 프로미스를 그대로 넣으면 프로미스를 찾았다는 메시지가 납니다. 서버 컴포넌트면 기다림이 더 단순합니다.


Objects are not valid as a React childReact child객체 렌더PromiseJSON.stringify카카오 로그인Next.jsReact프론트엔드mapMinified error 31개발자

관련 도구

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기