Q&A
Next.js 14에서 서버 컴포넌트 에러 해결 방법
상욱
2일 전
7201
Next.js 14 App Router를 사용하는데 서버 컴포넌트에서 다음과 같은 에러가 발생합니다:
Error: async/await is not yet supported in Client Components
서버 컴포넌트로 설정했는데도 이런 에러가 나는 이유가 뭔가요? 어떻게 해결해야 하나요?
댓글 1개
관
관리자2일 전
왜 발생하는 오류인가요?
Next.js 14 App Router에서 Server Component와 Client Component는 파일 단위로 구분됩니다.
- 파일 최상단에
“use client”
가 있으면 Client Component로 간주됩니다. - 그렇지 않으면 기본적으로 Server Component가 됩니다.
async/await
은 Server Component에서는 자유롭게 사용할 수 있지만, Client Component에서는 아직 지원되지 않으므로 다음과 같은 에러가 뜹니다.
Error: async/await is not yet supported in Client Components
주요 원인
- 현재 파일(또는 그 파일이 import 하는 하위 컴포넌트) 에
“use client”
가 선언돼 있는 경우. await
를 사용하는 함수가 Client Component 내부(예:useEffect
안)에서 직접 호출된 경우.next/dynamic
으로ssr: false
로 로드한 컴포넌트가 내부에서async
로 구현돼 있는 경우.
해결 방법
1️⃣ 파일이 Server Component인지 확인
// src/app/dashboard/page.tsx ← Server Component (default)
export default async function DashboardPage() {
const data = await fetchData(); // ✅ 정상
return <div>{JSON.stringify(data)}</div>;
}
반대로 Client Component 로 만들고 싶다면 최상단에 “use client”
를 명시합니다.
// src/app/dashboard/ClientWidget.tsx
"use client";
import { useEffect, useState } from "react";
export default function ClientWidget() {
const [data, setData] = useState(null);
useEffect(() => {
// 여기서는 async/await 를 직접 사용할 수 없음
fetch("/api/data")
.then((res) => res.json())
.then(setData);
}, []);
return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}
핵심: await
를 쓰고 싶다면 해당 파일에 “use client”
가 없어야 합니다.
2️⃣ 하위 컴포넌트도 확인
Server Component가 await
를 사용하면서 Client Component 를 import 하면 에러가 발생합니다.
// ❌ Bad: Server Component가 Client Component를 직접 import
import ClientWidget from "./ClientWidget";
export default async function Page() {
const data = await getData();
return (
<>
<ClientWidget /> // ← 여기서 에러
<pre>{JSON.stringify(data)}</pre>
</>
);
}
해결: Client Component는 별도의 파일에서 dynamic
으로 ssr: false
로 로드하거나, Server Component 내부에서 렌더링만 하고 로직을 분리합니다.
// ✅ Good: 동적 import (SSR 비활성화)
import dynamic from "next/dynamic";
const ClientWidget = dynamic(() => import("./ClientWidget"), {
ssr: false,
});
export default async function Page() {
const data = await getData();
return (
<>
<ClientWidget />
<pre>{JSON.stringify(data)}</pre>
</>
);
}
3️⃣ 클라이언트 측에서 비동기 로직을 처리하는 패턴
useEffect
+fetch
/axios
SWR
,React Query
등 데이터 페칭 라이브러리- Server Action (Next.js 14에서 제공) 사용 시
use server
를 명시하고, 클라이언트에서는formAction
으로 호출
"use client";
import { useState } from "react";
export default function SubmitForm() {
const [msg, setMsg] = useState("");
async function handleSubmit(e) {
e.preventDefault();
const form = e.target;
const data = new FormData(form);
const res = await fetch("/api/submit", { method: "POST", body: data });
const result = await res.json();
setMsg(result.message);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" required />
<button type="submit">전송</button>
<p>{msg}</p>
</form>
);
}
4️⃣ 서버 전용 로직을 별도 파일에 두기
복잡한 데이터 조회·가공 로직은 src/lib/
같은 디렉터리에서 순수 Node 환경 함수로 구현하고, Server Component에서만 import 합니다.
// src/lib/getPosts.ts
export async function getPosts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
return res.json();
}
// src/app/posts/page.tsx (Server Component)
import { getPosts } from "@/lib/getPosts";
export default async function PostsPage() {
const posts = await getPosts(); // ✅ Server side
return (
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
);
}
요약 체크리스트
파일 최상단에 “use client” 가 있나요? | 없애면 Server Component, 필요하면 명시 |
하위 컴포넌트가 Client Component인가? | dynamic(..., { ssr: false }) 로 로드하거나 별도 파일에서 사용 |
await 를 클라이언트 코드 안에서 직접 사용했나요? | useEffect /데이터 페칭 라이브러리로 대체 |
서버 전용 로직을 src/lib/ 등으로 분리했나요? | Server Component에서만 import |