facebook/react · error · Error

Hooks are not supported inside an async component. This erro

Error message

Hooks are not supported inside an async component. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

What it means

React async components are only supported on the server. When a module marked 'use client' exports an async component, hooks that suspend (use() and Suspense-backed hooks) throw React's internal SuspenseException; inside an async function that exception escapes into the promise rejection machinery instead of reaching React. checkIfUseWrappedInAsyncCatch inspects every rejection caught inside an async component and, if it is SuspenseException or SuspenseActionException, replaces it with this readable error. The check runs in production builds too, because the leaked exception would otherwise resurface later as a far more confusing asynchronous error.

Source

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

      return true;
    }
  }
  return false;
}

export function checkIfUseWrappedInAsyncCatch(rejectedReason: any) {
  // This check runs in prod, too, because it prevents a more confusing
  // downstream error, where SuspenseException is caught by a promise and
  // thrown asynchronously.
  // TODO: Another way to prevent SuspenseException from leaking into an async
  // execution context is to check the dispatcher every time `use` is called,
  // or some equivalent. That might be preferable for other reasons, too, since
  // it matches how we prevent similar mistakes for other hooks.
  if (
    rejectedReason === SuspenseException ||
    rejectedReason === SuspenseActionException
  ) {
    throw new Error(
      'Hooks are not supported inside an async component. This ' +
        "error is often caused by accidentally adding `'use client'` " +
        'to a module that was originally written for the server.',
    );
  }
}

function areSameKeyPath(a: Fiber, b: Fiber): boolean {
  if (a === b) {
    return true;
  }
  if (
    a.tag !== b.tag ||
    a.type !== b.type ||
    a.key !== b.key ||
    a.index !== b.index
  ) {
    return false;

View on GitHub (pinned to eafeac097b)

Solutions

  1. Remove 'use client' from the module and keep the async component as a Server Component that passes data down to non-async client children
  2. If the component must be interactive, make it synchronous and read data with the use() hook wrapped in <Suspense>, or fetch in effects
  3. Split the file: keep the await-based data fetch in a server component and move interactive logic to a separate non-async client component

Example fix

// before
'use client';
export default async function Profile() {
  const data = await fetchUser(); // suspends; SuspenseException leaks into async context
  return <Details data={data} />;
}

// after
// Profile.js (server, no 'use client')
import Details from './Details';
export default async function Profile() {
  const data = await fetchUser();
  return <Details data={data} />;
}
// Details.js ('use client', synchronous)
'use client';
export default function Details({data}) {
  return <pre>{JSON.stringify(data)}</pre>;
}
Defensive patterns

Strategy: validation

Validate before calling

// CI check: flag modules that are client-side AND export async components
const isAsyncClientModule = (source) =>
  /['"]use client['"]/.test(source) &&
  /export\s+(default\s+)?async\s+function/.test(source);
if (isAsyncClientModule(source)) {
  fail(`${file}: async client components cannot use hooks — move awaits to a server component`);
}

Prevention

When it happens

Trigger: Exporting an async function component from a file with 'use client' at the top, then letting a hook suspend inside it: the thrown SuspenseException is captured by the async function's own try/catch or an await rejection path, routed through checkIfUseWrappedInAsyncCatch, and converted into this throw.

Common situations: Adding 'use client' to a file that was originally a Server Component so it can use state while keeping async/await data fetching; copying RSC patterns (async function Page() { const d = await ... }) into a client bundle; Next.js App Router conversions where a page needs browser APIs but keeps await-style fetching.

Related errors


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