2026. 7. 16.
Client Components cannot be async functions 해결, fetch 위치를 나누는 법
Next.js의 Client Components cannot be async functions 오류에서 use client, 컴포넌트 async, fetch 위치를 확인하고 Server와 Client 역할을 다시 나누는 순서다.

'use client' 파일에서 데이터를 가져오려고 컴포넌트 앞에 async를 붙였더니 Client Components cannot be async functions가 뜰 수 있다. 이때 async만 지우거나 'use client'를 무작정 없애면 fetch 또는 클릭 기능 중 하나가 다시 깨진다.
먼저 오류 파일에서 'use client', 컴포넌트 함수의 async, fetch가 실행되는 위치를 표시한다. 브라우저 상태가 필요 없다면 fetch를 Server Component로 옮긴다. 클릭이나 입력 상태가 필요하다면 컴포넌트는 동기 함수로 두고 비동기 작업을 이벤트나 데이터 계층으로 옮긴다. 스트리밍이 필요할 때만 Server에서 만든 Promise를 Suspense 아래 Client Component로 전달해 use로 읽는다.
async를 지우기 전에 오류 파일의 세 줄을 표시한다
Next.js의 정확한 오류 설명은 Client Component 함수 자체를 async로 선언한 경우를 문제로 짚는다. 따라서 첫 수정은 패키지 재설치가 아니라 아래 세 줄의 역할을 구분하는 일이다.
'use client' // 1. Client 경계
export default async function ProductList() { // 2. 오류 지점
const items = await fetch('/api/items') // 3. 비동기 작업 위치
return <button>새로고침</button>
}
'use client'는 파일 하나의 실행 위치 표시에 그치지 않는다. Next.js Server와 Client Components 가이드에 따르면 이 지시문은 Server와 Client 모듈 그래프의 경계를 만든다. 해당 파일의 import와 직접 렌더하는 구성요소가 Client 번들에 포함되므로, 화면 전체보다 실제 상호작용이 있는 작은 구성요소에 붙이는 편이 경계를 읽기 쉽다.
이미 버튼 반응 때문에 경계가 헷갈린다면 use client를 붙일 파일만 고르는 순서를 먼저 적용해도 된다. 이 글의 차이는 버튼 오류가 아니라 컴포넌트 수준의 async와 데이터 위치를 고친다는 데 있다.
브라우저 상태가 없다면 fetch를 Server로 올린다
화면이 첫 렌더에서 목록을 보여주기만 하고 useState, onClick, window가 필요 없다면 가장 단순한 수정은 Server Component로 돌리는 것이다. 'use client'를 제거한 뒤 컴포넌트를 async로 유지하고 서버에서 fetch한다.
export default async function ProductList() {
const response = await fetch('https://example.com/api/items')
const items = await response.json()
return (
<ul>
{items.map((item: { id: string; name: string }) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
Next.js 데이터 가져오기 가이드는 Server Component를 비동기 함수로 만들고 fetch를 기다리는 패턴을 설명한다. 버튼만 필요하다면 데이터는 부모 Server Component에서 가져오고, 직렬화 가능한 값만 작은 Client Component의 props로 넘길 수 있다. 데이터가 서버에서 시작해도 클릭하는 부분까지 Server Component일 필요는 없다.
Client에 남겨야 한다면 비동기 작업 위치를 바꾼다
브라우저 상호작용이 필요한 이유를 먼저 적으면 두 경로 중 하나가 남는다.
클릭 뒤 다시 가져오는 데이터는 이벤트에서 처리한다
검색 버튼, 새로고침 버튼처럼 사용자 행동 뒤 요청한다면 컴포넌트 함수는 동기로 두고 이벤트 핸들러 안에서 비동기 함수를 실행한다. 초기 렌더 데이터까지 무조건 Effect로 옮기는 뜻은 아니다.
'use client'
import { useState } from 'react'
export default function SearchButton() {
const [result, setResult] = useState<string>('')
async function handleClick() {
const response = await fetch('/api/search?q=nextjs')
const data = await response.json()
setResult(data.message)
}
return <button onClick={handleClick}>{result || '검색'}</button>
}
첫 화면부터 Client에서 원격 데이터를 동기화해야 한다면 오류 문서는 useEffect 또는 데이터 라이브러리를 가능한 경로로 안내한다. 로딩, 오류, 캐시, 재검증 요구가 커지면 한 번의 Effect보다 해당 의미를 다루는 데이터 계층을 선택하는 편이 낫다.
Server에서 시작한 Promise는 Suspense 아래에서 읽는다
Server에서 요청을 일찍 시작하되 Client에서 결과를 읽어야 한다면 Promise를 props로 넘기고 use로 해제할 수 있다. 이 경로에서도 Client Component를 async로 만들지 않는다.
// Server Component
import { Suspense } from 'react'
export default function Page() {
const itemsPromise = getItems()
return (
<Suspense fallback={<p>불러오는 중...</p>}>
<ProductList itemsPromise={itemsPromise} />
</Suspense>
)
}
// Client Component
'use client'
import { use } from 'react'
type Props = {
itemsPromise: Promise<{ id: string; name: string }[]>
}
export function ProductList({ itemsPromise }: Props) {
const items = use(itemsPromise)
return <p>{items.length}개 상품</p>
}
React use 공식 문서는 Promise가 대기 중일 때 가장 가까운 Suspense fallback을 보여준다고 설명한다. 렌더마다 Client에서 새 Promise를 만들면 반복 suspend가 생길 수 있으므로, Server Component에서 만든 Promise나 캐시된 Promise를 전달해야 한다. 전달 결과도 React가 직렬화할 수 있는 값이어야 한다.
build와 세 상태를 확인해야 수정이 끝난다
먼저 프로젝트의 실제 build 명령을 실행한다.
npm run build
그다음 화면에서 아래 상태를 확인한다.
- 요청이 느릴 때 로딩 문구나 fallback이 보이는가
- 요청이 실패할 때 빈 화면 대신 오류 경계나 안내가 보이는가
- 버튼을 누른 뒤 입력값과 선택 상태가 의도대로 유지되는가
- Network에서 같은 요청이 불필요하게 반복되지 않는가
API 주소나 메서드가 틀려 다음 오류가 이어진다면 Next.js API 404와 405를 나누는 확인법으로 요청 경로를 점검할 수 있다. 이번 수정의 완료 기준은 오류 문구만 사라지는 것이 아니다. Server에서 가져올 데이터와 Client에서 유지할 상호작용이 코드 위치로 구분되고, build와 실제 상태가 모두 통과해야 한다.
참고 출처
- No async Client Component (Next.js)
- Server and Client Components (Next.js)
- Fetching Data (Next.js)
use(React)
지금 오류 파일을 열어 'use client', 컴포넌트의 async, fetch 위치에 각각 표시한다. Server 이동·이벤트 비동기 작업·Promise 전달 중 하나를 선택하면 다음 수정 범위가 분명해진다.
이 글은 AI 코딩과 개발 학습의 일반 정보 제공 목적입니다. 도구, 모델, 커리큘럼, 요금은 버전과 시점에 따라 달라질 수 있으므로 실습이나 도입 전 공식 문서와 최신 릴리스 노트를 확인하세요.
다음으로 읽을 기사
같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.
첫 번째 댓글을 남겨보세요
여러분의 생각이 다른 독자에게 도움이 됩니다.
댓글 0
이 글을 읽은 독자들의 생각을 나눠보세요.