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월 리액트·넥스트 공식 문서.
핵심만: 콘솔에 객체가 잘 찍혀도 리액트 화면 자식으로는 못 넣습니다. 일반 객체, 날짜, 프로미스를 그대로 넣으면 개발에선 키 목록이, 배포에선 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>;
}
프로미스는 화면 자식이 아니다. 기다린 값의 문자열 필드만 넣는다
고치는 순서 세 칸
메시지에서 찾은 값을 읽고, 타입을 확인하고, 필드만 남깁니다. 개발 모드면 키 목록이 이미 힌트입니다. 배포만 31번이면 로컬에서 같은 화면을 개발 모드로 한 번 재현하세요. 미니파이 숫자는 리액트 31번 페이지가 원문을 돌려 줍니다.
메시지
원인
다음 한 줄
키가 나열된 객체
일반 객체를 자식으로 넣음
닉네임, 제목 같은 문자열 필드
프로미스를 찾음
기다림이 없음
서버는 기다림, 화면은 use
날짜 객체
시각을 그대로 넣음
서울 시간대 문자열
오류 객체
오류를 그대로 넣음
메시지 문자열
배열만 의심
객체 배열을 한 줄씩 안 바꿈
한 줄씩 + 고유 키
타입 확인은 값의 종류와 생성자 이름이면 충분합니다. 객체로 나오면 그 값을 자식에서 빼고 필드를 고릅니다. 넥스트 데이터 가져오기는 공식 가져오기 문서가 서버에서 기다리는 칸을 기준으로 둡니다. 카카오 콜백 뒤 프로필 한 줄이 이 순서면 대부분 끝납니다.
배포 화면에 제이슨 원문을 그대로 두지 마세요. 문자열로 바꾸기는 객체를 글자로 바꿔 31번을 잠깐 피하게 해 줍니다. 로컬에서 키가 뭔지 볼 때만 쓰고, 회원 화면에는 닉네임, 이메일처럼 고른 칸만 남깁니다. 날짜는 표준 시각 글자가 되고, 없는 칸은 빠지니 디버그 결과와 실제 필드가 다를 수 있습니다.
시크릿이 섞인 카카오 토큰, 토스 결제 알림 원문을 화면에 뿌리는 습관은 로그 칸으로 옮기세요. 화면 자식 에러를 없애는 일과 개인정보를 노출하는 일은 별개입니다. 한국 1인 서비스는 이 두 칸을 처음부터 나누는 편이 덜 다칩니다.
디버그용 문자열, 화면용 필드
// 로컬에서 키를 확인할 때만
<pre>{JSON.stringify(user, null, 2)}</pre>
// 회원 화면에 남을 줄
<p>{user.kakao_account?.profile?.nickname ?? '이름 없음'}</p>