2026. 7. 8.
Next.js 버튼 오류에서 use client를 붙일 파일만 고르는 순서
Next.js App Router에서 버튼, useState, onClick 오류가 뜰 때 페이지 전체에 use client를 붙이기 전에 서버 파일과 클라이언트 버튼 파일을 나누는 순서를 확인합니다.

Next.js App Router에서 AI가 만들어 준 버튼을 누르자마자 Event handlers cannot be passed to Client Component props 또는 useState only works in Client Components 같은 오류가 뜨면, 초보자는 보통 page.tsx 맨 위에 "use client"를 붙입니다. 화면은 당장 움직일 수 있습니다. 하지만 그 순간 데이터 조회, 정적 본문, SEO에 필요한 영역까지 클라이언트 컴포넌트 쪽으로 밀릴 수 있습니다. 오늘 목표는 Next.js 이론을 길게 외우는 것이 아니라, 오류가 난 파일 전체를 바꾸기 전에 클라이언트가 필요한 작은 조각만 찾는 것입니다.
Next.js 공식 문서의 use client 설명은 이 지시어가 상태 관리, 이벤트 처리, 브라우저 API가 필요한 UI의 클라이언트 진입점을 선언한다고 안내합니다. 같은 문서에서 모든 파일에 붙일 필요는 없고, 서버 컴포넌트 안에서 직접 렌더링할 클라이언트 진입 파일에만 붙이면 된다고 설명합니다. React의 use client 문서도 onClick 같은 이벤트 핸들러는 클라이언트에서 등록되고 실행되어야 하며, 서버 컴포넌트에서 클라이언트 컴포넌트로 넘기는 props는 직렬화 가능한 값이어야 한다고 정리합니다.
이미 window is not defined처럼 브라우저 API 때문에 멈춘 상황이라면 Next.js window 오류를 3분 안에 분리하는 글도 같은 기준으로 보면 됩니다. 단, 이번 글은 브라우저 API보다 더 자주 보이는 버튼, 입력, 상태 변경 경계에 집중합니다.
먼저 오류 줄이 이벤트인지 상태인지 확인합니다
첫 행동은 "use client"를 붙이는 것이 아니라 오류 메시지에서 문제 줄을 찾는 것입니다. 아래처럼 onClick, onChange, useState, useEffect, localStorage, window 중 하나가 보이면 클라이언트 경계가 필요한 신호입니다.
Error: Event handlers cannot be passed to Client Component props.
<button onClick={function}>
You're importing a component that needs useState.
It only works in a Client Component.
여기서 중요한 질문은 하나입니다. 이 줄이 페이지 전체에 필요한가, 작은 버튼이나 폼에만 필요한가입니다. 대부분의 초보자 프로젝트에서는 작은 버튼 컴포넌트에만 필요합니다.
// app/page.tsx
export default async function Page() {
const posts = await getPosts();
return (
<main>
<h1>게시글</h1>
<LikeButton />
<pre>{JSON.stringify(posts, null, 2)}</pre>
</main>
);
}
이 구조에서 getPosts()와 본문은 서버 쪽에 남겨도 됩니다. 클릭 횟수나 좋아요 버튼만 브라우저에서 움직이면 됩니다. AI에게 다시 요청할 때도 이 기준을 넣어야 합니다.
page.tsx 전체에 "use client"를 붙이지 말고,
클릭 상태가 필요한 LikeButton만 별도 클라이언트 컴포넌트로 분리해 주세요.
서버에서 가져오는 posts 데이터 흐름은 유지해 주세요.
클라이언트 파일은 버튼이 시작되는 곳에만 만듭니다
분리할 파일을 정했다면 그 파일 맨 위에만 "use client"를 둡니다. 지시어는 import보다 위에 있어야 합니다. 버튼 안에서 상태와 이벤트 핸들러를 만들면 서버 컴포넌트에서 함수 props를 넘길 필요도 줄어듭니다.
// app/components/like-button.tsx
"use client";
import { useState } from "react";
export function LikeButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount((value) => value + 1)}>
좋아요 {count}
</button>
);
}
서버 페이지는 이 버튼을 가져와 렌더링만 합니다.
// app/page.tsx
import { LikeButton } from "./components/like-button";
export default async function Page() {
const posts = await getPosts();
return (
<main>
<h1>게시글</h1>
<LikeButton />
<pre>{JSON.stringify(posts, null, 2)}</pre>
</main>
);
}
이 패턴은 React 버튼 onClick 연결을 확인하는 글과도 이어집니다. React 자체에서는 onClick={handleClick} 연결을 먼저 봅니다. Next.js App Router에서는 그다음 단계로 그 연결이 클라이언트 컴포넌트 안에 있는지를 봅니다.
서버에서 만든 일반 함수를 onClick으로 넘기지 않습니다
초보자가 자주 막히는 지점은 “버튼 파일만 클라이언트로 만들었는데도 오류가 난다”는 경우입니다. 이때는 서버 컴포넌트에서 만든 일반 함수를 클라이언트 컴포넌트 props로 넘기고 있지 않은지 봅니다.
// 피해야 할 형태
export default function Page() {
function handleClick() {
console.log("clicked");
}
return <ClientButton onClick={handleClick} />;
}
React와 Next.js 문서 기준으로 서버에서 클라이언트로 넘기는 props는 직렬화 가능한 값이어야 합니다. 일반 함수는 화면에 보낼 수 있는 데이터가 아닙니다. 그래서 버튼이 해야 할 일이 단순한 UI 상태 변경이라면 클라이언트 컴포넌트 안에서 함수를 만듭니다.
"use client";
export function ClientButton() {
function handleClick() {
console.log("clicked");
}
return <button onClick={handleClick}>확인</button>;
}
서버 데이터가 필요하다면 함수 자체를 넘기려 하지 말고, 클라이언트가 표시할 문자열, 숫자, id 같은 값만 넘깁니다. 폼 제출이나 데이터 변경처럼 서버에서 실행해야 하는 일은 서버 액션 규칙을 따로 확인해야 합니다. 이 글의 범위에서는 일반 onClick 함수를 서버에서 클라이언트로 넘기지 않는 것만 지켜도 많은 오류가 사라집니다.
AI에게는 파일 경계와 금지 범위를 같이 줍니다
AI 코딩 도구에 “use client 오류 고쳐줘”라고만 쓰면 페이지 전체에 지시어를 붙이거나, 관련 없는 컴포넌트까지 한꺼번에 바꿀 수 있습니다. 요청문에는 오류 줄, 유지할 서버 파일, 클라이언트로 분리할 후보 파일, 금지할 변경을 넣습니다.
Next.js App Router에서 아래 오류가 납니다.
오류 메시지:
Event handlers cannot be passed to Client Component props.
문제 파일:
app/page.tsx
오류 줄:
<button onClick={...}>
요청:
1. page.tsx 전체에 "use client"를 붙이지 마세요.
2. 클릭 상태가 필요한 UI만 app/components/like-button.tsx로 분리해 주세요.
3. 서버에서 가져오는 데이터 조회 코드는 서버 컴포넌트에 남겨 주세요.
4. 서버 컴포넌트에서 일반 함수를 클라이언트 props로 넘기지 않게 고쳐 주세요.
5. 수정 파일을 2개 이하로 제한해 주세요.
이렇게 쓰면 AI가 해야 할 일이 좁아집니다. “버튼이 안 눌림”이라는 증상에서 시작하더라도, 결과물은 파일 경계와 props 경계로 검증할 수 있습니다. 필요하면 TypeScript 빨간 줄을 고치는 순서처럼 오류 줄을 먼저 고정한 뒤 작은 수정부터 요청합니다.
저장 전에는 세 줄만 확인합니다
수정이 끝났다면 저장하기 전에 아래 세 줄을 채웁니다.
서버에 남긴 파일:
클라이언트로 분리한 파일:
서버에서 클라이언트로 넘기는 값:
예시는 이렇습니다.
서버에 남긴 파일: app/page.tsx
클라이언트로 분리한 파일: app/components/like-button.tsx
서버에서 클라이언트로 넘기는 값: postId 문자열만 전달
이 세 줄이 비어 있으면 아직 수정 범위를 이해하지 못한 상태입니다. 그때는 바로 커밋하지 말고 AI에게 “방금 수정에서 서버 컴포넌트와 클라이언트 컴포넌트 경계를 설명해 달라”고 다시 묻습니다. 오늘 고칠 것은 Next.js 전체 구조가 아닙니다. 상호작용이 필요한 작은 파일만 클라이언트 경계로 옮기는 것입니다.
참고 출처
- Next.js Docs, use client directive: https://nextjs.org/docs/app/api-reference/directives/use-client
- Next.js Docs, Server and Client Components: https://nextjs.org/docs/app/getting-started/server-and-client-components
- React Docs, use client directive: https://react.dev/reference/rsc/use-client
다음으로 읽을 기사
같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.
첫 번째 댓글을 남겨보세요
여러분의 생각이 다른 독자에게 도움이 됩니다.
댓글 0
이 글을 읽은 독자들의 생각을 나눠보세요.