Next.js Server Actions "Functions cannot be passed directly to Client Components" 에러 해결법

이미지
3초 요약: 이렇게 고치세요 원인: 서버 컴포넌트의 일반 함수는 직렬화가 안 돼서, Client Component에 props로 그대로 넘기면 React가 전달 자체를 막음 해결: 넘길 함수 내부 최상단에 'use server' 를 선언해 진짜 Server Action으로 만들거나, 서버 로직이 필요 없다면 아예 클라이언트 컴포넌트 안으로 로직을 옮기기 주의: 인자를 미리 고정해서 넘기고 싶으면 .bind(null, id) 로 감싸야지, 클로저로 감싼 일반 함수를 새로 만들면 다시 같은 에러가 남 async function deletePost(id: string) { 'use server'; await db.post.delete({ where: { id } }); } ↓ 왜 이런 에러가 나는지 원리 보기 서버 컴포넌트에서 만든 핸들러 함수를 버튼 같은 클라이언트 컴포넌트에 그대로 props로 넘기면 "Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with \"use server\"" 에러가 뜨는 경우가 있습니다. 왜 함수는 그냥 넘길 수 없는지와, Server Action으로 바꿔서 해결하는 방법을 정리합니다. 왜 서버 컴포넌트의 함수를 클라이언트 컴포넌트에 그대로 못 넘길까 서버 컴포넌트는 브라우저로 그대로 전송되지 않습니다. 서버에서 렌더링된 결과가 RSC(React Server Components) 페이로드라는 직렬화된 데이터 형태로 변환되어 클라이언트로 넘어가고, 클라이언트는 이 데이터를 읽어서 화면을 조립합니다. 이때 문자열, 숫자, 객체, 배열처럼 JSON으로 표현 가능한 값...

React Each child in a list should have a unique key prop 경고와 index key 버그

이미지
3초 요약: 이렇게 고치세요 원인: map() 으로 만든 목록의 최상위 요소에 key 를 주지 않아서 발생 — 화면을 다시 그릴 때 어떤 요소가 어떤 항목이었는지 React가 짝지을 근거가 없음 해결: 데이터가 원래 가진 고유 id를 key 로 사용 / 빈 태그로 감쌌다면 <Fragment key={...}> 로 바꿔서 지정 주의: 배열 index를 key 로 쓰면 경고는 사라지지만, 목록 순서가 바뀌거나 중간 항목이 삭제될 때 입력값·체크 상태가 엉뚱한 행에 남는 더 찾기 힘든 버그가 생김 {todos.map((todo) => ( <li key={todo.id}>{todo.text}</li> // index 말고 고유 id ))} ↓ 왜 이런 경고가 나는지 원리 보기 목록을 map() 으로 그리다 보면 콘솔에 'Warning: Each child in a list should have a unique "key" prop' 경고가 뜹니다. 화면은 멀쩡히 나오니 일단 넘어가기 쉬운데, 이 경고는 단순한 잔소리가 아니라 나중에 훨씬 찾기 어려운 버그로 되돌아오는 경우가 많습니다. React가 목록에 key를 요구하는 이유 React는 상태가 바뀔 때마다 화면 전체를 새로 그리는 대신, 이전 렌더링 결과와 새 결과를 비교해서 달라진 부분만 실제 DOM에 반영합니다. 이때 목록은 유독 까다로운 대상입니다. 항목이 추가되거나 삭제되거나 순서가 바뀌면, 새로 만들어진 요소 중 무엇이 이전의 어떤 요소와 같은 항목인지 판단할 근거가 필요하기 때문입니다. key 가 바로 그 근거입니다. React는 같은 key 를 가진 요소를 같은 항목으로 취급해서 DOM 노드와 그 안의 상태를 그대로 유지하고, key 가 사라지면 그 ...

React Objects are not valid as a React child 에러 원인과 해결법

이미지
3초 요약: 이렇게 고치세요 원인: JSX 중괄호 안에 문자열·숫자가 아닌 객체 를 그대로 넣어서 발생 — React는 객체를 화면에 어떻게 그려야 할지 알 수 없음 해결: 객체면 user.name 처럼 원시값 속성을 꺼내 쓰고 / 배열이면 .map() 으로 펼치고 / Date 면 toLocaleDateString() 으로 문자열 변환 주의: 에러 메시지의 found: object with keys {...} 부분에 실제 객체의 키가 찍히므로 범인을 특정하는 단서가 됨 — 단 Date 처럼 자체 속성이 없는 객체는 이 목록이 비어 보임 <p>{user}</p> {/* 에러 */} <p>{user.name}</p> {/* 정상 */} ↓ 왜 이런 에러가 나는지 원리 보기 잘 돌아가던 화면이 갑자기 하얗게 날아가면서 "Objects are not valid as a React child (found: object with keys {name, email})" 같은 에러가 뜬 적 있으실 겁니다. 원인은 대부분 간단합니다. JSX 중괄호 안에 문자열이나 숫자가 아닌 객체를 그대로 넣었기 때문인데, 정작 어느 줄이 범인인지 찾는 게 더 오래 걸리는 에러입니다. React가 객체를 화면에 그리지 못하는 이유 JSX의 중괄호는 그 안의 값을 화면에 출력하라는 뜻입니다. React는 여기에 들어온 값이 문자열이나 숫자면 텍스트 노드로 만들고, 배열이면 각 항목을 순회하고, React 엘리먼트면 그대로 렌더링합니다. 그런데 일반 객체가 들어오면 얘기가 달라집니다. { name: '김개발', email: 'dev@example.com' } 이라는 객체를 화면에 어떤 글자로 표시해야 할지는 React가 ...

Next.js Image hostname is not configured 에러 원인과 해결법

이미지
3초 요약: 이렇게 고치세요 원인: next/image 로 외부 호스트의 이미지를 불러올 때 그 호스트를 next.config.js 에 등록해두지 않아서 발생 해결: next.config.js 의 images.remotePatterns 에 해당 호스트를 추가 주의: 이미지 호스팅 호스트가 새로 생길 때마다 설정 파일을 고치고 재배포해야 함 — 사용자 업로드처럼 호스트가 자주 바뀌면 와일드카드 패턴을 고려할 것 images: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example-cms.com' }, ], } ↓ 왜 이런 에러가 나는지 원리 보기 외부 CMS나 스토리지에서 받아온 이미지 URL을 next/image 에 그대로 넣었더니 화면 대신 빨간 에러 화면이 뜬 적 있으실 겁니다. "Invalid src prop ... hostname ... is not configured under images in your next.config.js"라는 메시지인데, 이미지 URL 자체는 멀쩡한데도 나는 에러라 처음 보면 당황스럽습니다. next/image가 외부 호스트를 화이트리스트로 관리하는 이유 일반 <img> 태그는 브라우저가 어떤 URL이든 그냥 요청해서 그리면 끝입니다. 반면 next/image 는 요청이 들어올 때마다 Next.js 서버(또는 배포 플랫폼의 이미지 최적화 API)가 원본 이미지를 가져와 크기를 줄이고 포맷을 변환한 뒤 캐싱해서 내려주는 구조입니다. 이때 서버가 아무 URL이나 제한 없이 대신 가져오게 허용하면, 그 최적화 엔드포인트가 임의의 외부 서버에 요청을 대신 보내주는 통로로 악용될 수 있습니다. 그래서 Next.js는 next.conf...

Next.js 15 params should be awaited 에러 원인과 해결 가이드

이미지
3초 요약: 이렇게 고치세요 원인: Next.js 15부터 params · searchParams 가 Promise로 바뀌었는데, 예전처럼 params.id 를 동기적으로 바로 읽어서 발생 해결: 서버 컴포넌트·Route Handler· generateMetadata → const { id } = await params; / 클라이언트 컴포넌트 → const { id } = use(params); 주의: 공식 codemod가 대부분 자동 변환해주지만, params를 조건부로 읽거나 다른 함수에 그대로 넘기는 패턴은 놓칠 수 있어 직접 확인 필요 export default async function Page({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; } ↓ 왜 이런 에러가 나는지 원리 보기 Next.js 14 프로젝트를 15로 올리자마자 params.id 를 쓰던 코드에서 "params should be awaited" 같은 경고나 타입 에러를 마주친 적 있으실 겁니다. 원인은 Next.js 15부터 동적 라우트의 params 와 searchParams 가 더 이상 일반 객체가 아니라 Promise로 바뀌었기 때문인데, 코드 몇 줄만 고치면 되는 문제지만 프로젝트 전체에 흩어져 있으면 손이 꽤 갑니다. params가 갑자기 Promise가 된 이유 Next.js 14까지는 page.tsx 가 받는 params · searchParams 가 렌더링 시점에 이미 값이 채워진 평범한 객체였습니다. Next.js 15는 이 값들을 포함해 cookies() , headers() , draftMode() 처럼 요청이 와야만 알 수 있는 값들을 전부 비동기 API로 통일...

TypeScript: Unknown vs Any 타입 차이점과 안전한 타입 가드(Type Guard)

이미지
3초 요약: 이렇게 고치세요 원인: any 는 타입 검사 자체를 포기하는 선언이라, 잘못된 속성 접근이 컴파일 시점엔 통과했다가 런타임에 터짐 해결: 외부 API 응답처럼 타입을 확신할 수 없는 데이터는 unknown 으로 받고, data is T 형태의 타입 가드로 검증한 뒤에만 사용 주의: 타입 가드 내부에서 as Record<string, unknown> 같은 임시 캐스팅으로 속성을 하나씩 검사하는 절차를 생략하면 unknown 을 쓰는 의미가 없어짐 function isUserProfile(data: unknown): data is UserProfile { ... } ↓ 왜 any가 위험한지 원리 보기 실무에서 대규모 TypeScript 프로젝트를 마이그레이션하거나, 외부 API 또는 레거시 라이브러리를 통합하다 보면 필연적으로 "타입을 확신할 수 없는 데이터"를 다루게 됩니다. 이때 많은 개발자들이 급한 마음에 가장 손쉬운 탈출구인 any 를 사용하곤 합니다. 하지만 이는 TypeScript의 가장 큰 무기인 타입 안정성을 스스로 내려놓는 행위이자, 미래의 나에게 런타임 에러라는 시한폭탄을 던지는 것과 같습니다. any 가 왜 위험한지, TypeScript 3.0에서 도입된 unknown 이 이를 어떻게 막아주는지, 그리고 실무에서 바로 써먹을 수 있는 사용자 정의 타입 가드(User-Defined Type Guards) 작성 패턴을 정리합니다. any의 위험성과 unknown의 등장 프로덕션 환경에서 발생한 실제 에러 로그를 분석해 보면, 꽤 많은 비율이 TypeError: Cannot read properties of undefined 에서 시작합니다. 그리고 그 이면에는 높은 확률로 any 타입이 자리 잡고 있죠. any: 타입...