facebook/react · error · Error

An unknown Component is an async Client Component. Only Serv

Error message

An unknown Component is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

What it means

When a component suspends on a thenable React has not seen before (no status field), React marks it pending, attaches listeners, pings the root to retry, and throws to suspend. If the root suspends in the shell more than 100 times during a sync render without committing anything, React concludes the ping loop is infinite. The canonical cause is an async Client Component — an async function component running on the client returns a fresh promise every render that never resolves into rendered output.

Source

Thrown at packages/react-reconciler/src/ReactFiberThenable.js:267

        // This is an uncached thenable that we haven't seen before.

        // Detect infinite ping loops caused by uncached promises.
        const root = getWorkInProgressRoot();
        if (root !== null && root.shellSuspendCounter > 100) {
          // This root has suspended repeatedly in the shell without making any
          // progress (i.e. committing something). This is highly suggestive of
          // an infinite ping loop, often caused by an accidental Async Client
          // Component.
          //
          // During a transition, we can suspend the work loop until the promise
          // to resolve, but this is a sync render, so that's not an option. We
          // also can't show a fallback, because none was provided. So our last
          // resort is to throw an error.
          //
          // TODO: Remove this error in a future release. Other ways of handling
          // this case include forcing a concurrent render, or putting the whole
          // root into offscreen mode.
          throw new Error(
            'An unknown Component is an async Client Component. ' +
              'Only Server Components can be async at the moment. ' +
              'This error is often caused by accidentally ' +
              "adding `'use client'` to a module that was originally written " +
              'for the server.',
          );
        }

        const pendingThenable: PendingThenable<T> = thenable as any;
        pendingThenable.status = 'pending';
        pendingThenable.then(
          fulfilledValue => {
            if (thenable.status === 'pending') {
              const fulfilledThenable: FulfilledThenable<T> = thenable as any;
              fulfilledThenable.status = 'fulfilled';
              fulfilledThenable.value = fulfilledValue;
            }
          },

View on GitHub (pinned to eafeac097b)

Solutions

  1. Remove async from the client component: fetch data in a Server Component and pass it (or a cached promise) down as props
  2. If suspending from a client component, pass use() a stable promise — create it once per input and cache it (module-level cache or React.cache on the server)
  3. Move data fetching into actions/transitions so results update state instead of inline awaits during render
  4. Audit every 'use client' file for async function components

Example fix

// before — 'use client' module
'use client';
export default async function Notes() { // async Client Component -> ping loop
  const notes = await db.notes();
  return <List notes={notes} />;
}

// after — keep it a Server Component (no 'use client'):
// notes.tsx (server)
export default async function Notes() {
  const notes = await db.notes();
  return <List notes={notes} />; // List stays a client component
}
Defensive patterns

Strategy: validation

Validate before calling

// Cache promises per input so use() never sees a fresh thenable each render
const promiseCache = new Map<string, Promise<Data>>();
function getData(id: string): Promise<Data> {
  let p = promiseCache.get(id);
  if (!p) {
    p = fetch(`/api/${id}`).then((r) => r.json());
    promiseCache.set(id, p);
  }
  return p;
}

Type guard

// Reject async function components in client modules
function isAsyncComponent(fn: unknown): boolean {
  return (
    typeof fn === 'function' &&
    (fn.constructor?.name === 'AsyncFunction' ||
      Object.prototype.toString.call(fn) === '[object AsyncFunction]')
  );
}

Prevention

When it happens

Trigger: A synchronous render where a component repeatedly suspends on brand-new (uncached) thenables: an async function component in client code ('use client' module), or inline creation of promises passed to use() on every render (e.g. use(fetch('/x')) in the render body).

Common situations: Accidentally adding 'use client' to a module that still exports async server components (Next.js App Router); client components declared async out of habit; forgetting to cache/hoist promises consumed with use(); RSC boundaries drawn in the wrong place.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/9b85a9b6539f89a4. Report an issue: GitHub.