TechFeedTechFeed
Frontend

이벤트 핸들러, 클라이언트 컴포넌트, 서버 컴포넌트 | 클릭 함수만 넘기면 막히면?

Event handlers cannot be passed는 서버 컴포넌트가 onClick 함수를 클라이언트 속성으로 넘긴 직렬화 오류입니다. 버튼 파일로 나누고 주문 번호만 넘기며, 서버 함수만 예외입니다. useState 지시자 오류와 줄을 가릅니다. Next.js, use client, 프론트, 한국 1인 개발. 2026년 8월 경계 문서.

by

Event handlers cannot be passed to Client Component props는 훅 실수가 아니라, 서버 컴포넌트가 onClick 함수를 클라이언트 속성으로 넘기다 직렬화가 거절된 줄입니다. 버튼만 'use client' 파일로 옮기고, 서버에서는 주문 번호만 넘기세요.


앱 라우터 페이지는 기본이 서버입니다. 그 파일에서 함수를 넘기면 런타임이 그 속성을 가리키며 멈춥니다. 예외는 'use server'로 표시한 서버 함수입니다.


useState를 서버 파일에서 부른 문장과는 다릅니다. 그 줄은 지시자가 없는 훅이고, 이번 줄은 함수를 경계 너머로 보낸 직렬화입니다.


서버가 함수를 클라이언트로 못 넘긴다

경계 너머로 가는 것은 값이 아니라 함수라서 멈춥니다.


넥스트 경계 가이드는 두 규칙을 나눕니다. 코드는 import를 통해 클라이언트 번들로 들어가고, 데이터는 속성으로 건너며 직렬화되어야 합니다. 그래서 서버 컴포넌트에서 클라이언트 컴포넌트로 함수를 넘기면 던진다고 적습니다. onClick은 그 예입니다. 에러는 문제 속성 옆에 function이라고 표시합니다.


서버 컴포넌트는 브라우저에서 그 함수를 다시 실행할 자바스크립트를 보내지 않습니다. 클릭 리스너는 브라우저에 있어야 하므로, 서버가 만든 함수 객체를 JSON처럼 실어 나를 방법이 없습니다. 페이지 전체를 클라이언트 파일로 바꾸기 전에, 어느 속성이 함수인지 에러가 가리키는 이름부터 보면 범위가 작아집니다.


에러가 가리키는 속성 | onClick={function}처럼 찍힌 이름만 클라이언트 파일 안으로 옮기면 됩니다. 페이지의 데이터 조회까지 옮길 필요는 없습니다.


모니터 두 대가 놓인 책상. 서버 컴포넌트와 클라이언트 버튼 파일을 나누는 작업 장면
서버 페이지는 값을 넘기고, 클릭 함수는 클라이언트 파일 안에 둔다

버튼만 파일로 쪼개기

핸들러는 클라이언트 파일 안에서 만듭니다.


서버 페이지는 주문을 읽고 orderId만 넘깁니다. 버튼 파일 첫 줄에 'use client'를 두고, onClick은 그 파일의 함수로 둡니다. 자식도 클라이언트여야 함수 속성을 받을 수 있습니다. 부모만 서버이고 자식만 클라이언트인 채로 onClick={handle}을 넘기면 같은 에러가 남습니다.


페이지 최상단에 지시자를 붙이면 그 파일이 import하는 모듈까지 클라이언트 번들로 딸려 옵니다. 조회와 비밀 키가 그 그래프에 있으면 브라우저로 내려갑니다. 버튼, 토글, 입력처럼 이벤트가 있는 조각만 분리하는 편이 번들이 작습니다. 서버에서 만든 리액트 요소는 직렬화되므로, children으로 서버 트리를 클라이언트 슬롯에 넣는 패턴은 그대로 허용됩니다.


서버 페이지는 값만, 버튼 파일이 클릭을 가짐
// app/orders/[id]/page.js (서버 컴포넌트) import PayButton from './pay-button'; export default async function Page({ params }) { const { id } = await params; return <PayButton orderId={id} />; } // app/orders/[id]/pay-button.js 'use client'; export default function PayButton({ orderId }) { return ( <button type="button" onClick={() => console.log(orderId)}> 결제 </button> ); }

서버 함수만 경계로 건너간다

일반 함수는 거절되고, use server 함수는 참조로 넘어갑니다.


넥스트는 'use server'로 표시한 서버 함수가 참조로 경계를 지난다고 적습니다. 타입만 봐서는 일반 함수와 구분이 안 되어, 타입스크립트 플러그인은 속성 이름이 action이거나 Action으로 끝날 때만 함수 속성을 허용하고 나머지는 표시합니다. 런타임은 서버 함수가 아닌 함수가 넘어오면 여전히 거절합니다.


클라이언트에서 서버 변경이 필요하면 액션 파일에 서버 함수를 두고, 버튼의 onClick 안에서 그 함수를 호출합니다. 서버 페이지가 액션을 속성으로 넘길 수도 있습니다. 변경 데이터 문서의 예는 updateItemAction을 클라이언트 폼의 action에 연결합니다. 클릭마다 서버 왕복이 생기므로, 화면 열림만 바꾸는 토글은 서버 함수로 만들지 않습니다.


클릭 안에서 서버 함수 호출
// app/orders/actions.js 'use server'; export async function markPaid(orderId) { // 서버에서만 실행. 비밀 키는 이 파일에 둔다. return { ok: true, orderId }; } // pay-button.js 'use client'; import { markPaid } from '../actions'; export default function PayButton({ orderId }) { return ( <button type="button" onClick={() => markPaid(orderId)}> 결제 완료 </button> ); }

useState 지시자 에러와 다른 줄

훅 문장과 함수 속성 문장은 고치는 파일이 다릅니다.


콘솔의미고치는 곳
useState는 클라이언트에서만서버 파일이 훅을 호출훅이 있는 파일에 use client
Event handlers cannot be passed함수 속성이 경계를 넘음핸들러를 클라이언트 파일 안으로
함수는 클라이언트로 직접 전달 불가서버 함수 표시 없는 함수use server 이거나 값만 전달
Dynamic server usage, cookies정적 생성 중 요청 API동적 렌더 또는 쿠키를 요청 밖으로
Hydration failed첫 HTML과 브라우저 렌더가 다름첫 렌더 값을 서버와 맞추기

지시자를 이미 붙였는데도 함수 속성 에러가 남으면, 그 함수가 아직 서버 부모에서 만들어져 내려온 것입니다. 클라이언트 파일끼리 함수를 주고받는 것은 둘 다 브라우저 그래프 안이라 허용됩니다. 조상 중 서버가 그 함수를 만든 경우만 거절입니다. 한국에서 넥스트 앱을 혼자 올리는 경우, 공용 버튼 컴포넌트만 클라이언트로 두고 페이지에서 onClick을 넘기다가 이 줄을 만납니다.


넘겨도 되는 값

문자열, 숫자, 평범한 객체, 서버 함수 참조, 렌더된 요소는 넘어갑니다.


날짜 객체나 클래스 인스턴스, 맵, 심벌은 직렬화에서 또 걸릴 수 있으니 문자열이나 숫자로 바꿉니다. 사용자 레코드 전체를 넘기면 비밀번호 해시까지 브라우저 페이로드에 실릴 수 있습니다. 버튼에 필요한 필드만 고릅니다. 서버 전용 모듈을 클라이언트 파일이 import하면 경계 에러가 한 줄 더 납니다. 그 모듈은 서버 페이지와 서버 함수 쪽에 둡니다.


에러 바운더리는 렌더 중 예외를 잡고, 이벤트 핸들러 안의 예외는 잡지 않는다고 넥스트 에러 처리 문서가 적습니다. 클릭 함수 안의 실패는 클라이언트에서 직접 잡아 상태에 넣어야 화면에 남습니다. 경계를 고친 뒤 버튼이 조용히 아무 일도 안 하면, 그 핸들러의 예외를 따로 보세요.


코드가 흐릿한 모니터와 키보드. 클릭 핸들러를 클라이언트 컴포넌트로 옮긴 뒤의 작업 화면
직렬화되는 값만 서버 페이지 속성으로 남기면 함수 전달 에러는 멈춘다
  • [ ] 에러가 가리킨 속성 이름이 함수인지 확인했다
  • [ ] 그 핸들러를 use client 파일 안으로 옮겼다
  • [ ] 서버에서는 아이디와 라벨만 넘겼다
  • [ ] 서버 변경만 use server 함수로 호출했다
  • [ ] 페이지 전체에 지시자를 붙여 조회 코드를 번들에 넣지 않았다

참고 자료


내부 연계: useState와 use client, 하이드레이션 미스매치, 렌더 중 setState


경계 규칙은 2026년 9월 넥스트 공개 문서 기준입니다.


자주 묻는 질문

자식에 use client를 붙였는데도 같은 줄이에요.

함수를 여전히 서버 부모가 만들어 속성으로 넘기면 자식 지시자와 관계없이 거절됩니다. 핸들러 선언을 자식 파일 안으로 옮기거나, 부모 파일도 클라이언트여야 합니다. 주문 번호만 넘기는 쪽이 번들이 작습니다.


페이지 첫 줄에 use client를 넣으면 끝나나요?

그 파일의 클릭은 살아납니다. 그 파일이 import하는 조회와 서버 전용 모듈까지 클라이언트 그래프로 들어갑니다. 버튼 파일만 나누는 편이 안전합니다.


서버 액션을 onClick에 바로 넣어도 되나요?

서버 함수는 참조로 넘어갑니다. 클라이언트 버튼이 그 함수를 import해 onClick 안에서 호출하는 예가 변경 데이터 문서에 있습니다. 화면 상태만 바꾸는 함수에 use server를 붙이지는 마세요.


useState 에러랑 같이 보여요.

훅을 서버 파일에서 호출한 줄과, 함수를 넘긴 줄은 따로입니다. 훅이 있는 파일에 지시자를 두고, 서버 부모의 함수 속성은 제거하세요. 한 파일에 둘을 섞으면 지시자 하나로 둘 다 숨겨져 번들만 커집니다.


children으로 감싸면 서버 조회가 클라이언트로 가나요?

렌더된 요소는 직렬화되는 데이터라 서버 컴포넌트를 자식으로 넣을 수 있습니다. 클라이언트 파일이 그 서버 파일을 import하면 안 됩니다. 페이지가 서버 트리를 만들어 children으로 넘기면 조회는 서버에 남습니다.


클릭은 되는데 에러가 화면에 안 남아요.

에러 바운더리는 렌더 중 예외를 잡고 이벤트 핸들러 예외는 잡지 않습니다. onClick 안에서 잡아 상태로 보여 주세요. 경계 에러가 사라진 것과 결제 함수의 실패는 다른 칸입니다.


이 에러는 서버에서 만든 함수를 클라이언트 속성에 넣은 직렬화입니다. 버튼 파일 안에 핸들러를 두고, 서버 변경만 서버 함수로 부르면 됩니다. 관련 글: use client, 하이드레이션, 무한 렌더.


이벤트 핸들러클라이언트 컴포넌트서버 컴포넌트onClickuse client서버 함수넥스트직렬화프론트개발자

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기 →