This error occurs when you try to use the useTransition() hook in a React Server Component. In Next.js 13+, all components in the app directory are Server Components by default, and hooks like useTransition require client-side execution and must be used in Client Components with the "use client" directive.
This error indicates that you are attempting to use the useTransition() React hook in a Server Component context. React hooks like useTransition, useState, and useEffect are tightly coupled with the React rendering lifecycle and rely on client-side state management and browser runtime features. Server Components execute once per request on the server and do not have access to browser APIs, state, or lifecycle methods. In Next.js 13 and later versions using the App Router, every component is treated as a Server Component by default unless explicitly marked otherwise. The useTransition hook specifically enables non-blocking state updates and provides loading states for transitions, which are inherently client-side features. When React detects that you are trying to use this hook in a Server Component, it throws this error to prevent runtime issues. The error is part of React's architectural separation between Server and Client Components, which helps optimize performance by allowing components that don't need interactivity to run on the server, reducing the JavaScript bundle sent to the client.
Open the file containing the component that uses useTransition and add the "use client" directive as the very first line, before any imports:
"use client";
import { useTransition } from "react";
export default function MyComponent() {
const [isPending, startTransition] = useTransition();
// ... rest of your component
}The directive must be at the top of the file (before imports) and uses double quotes. This tells React and Next.js that this component and all its child components should be treated as Client Components.
Before adding "use client", confirm that your component actually requires client-side features. Ask yourself:
- Does it need to handle user interactions (clicks, form inputs)?
- Does it manage client-side state with useState or useTransition?
- Does it use browser APIs or effects?
If the answer is yes, proceed with the "use client" directive. If not, consider refactoring to use Server Actions or moving the interactive logic to a separate Client Component:
// Server Component (parent)
import ClientButton from "./ClientButton";
export default function Page() {
return (
<div>
<h1>Server-rendered content</h1>
<ClientButton /> {/* Only this is a Client Component */}
</div>
);
}If only part of your component needs useTransition, separate it into a smaller Client Component to minimize the JavaScript bundle sent to the client:
// app/components/TransitionButton.tsx (Client Component)
"use client";
import { useTransition } from "react";
export function TransitionButton({ onUpdate }: { onUpdate: () => Promise<void> }) {
const [isPending, startTransition] = useTransition();
return (
<button
onClick={() => startTransition(() => onUpdate())}
disabled={isPending}
>
{isPending ? "Updating..." : "Update"}
</button>
);
}// app/page.tsx (Server Component)
import { TransitionButton } from "./components/TransitionButton";
export default function Page() {
async function handleUpdate() {
"use server";
// Server Action logic
}
return <TransitionButton onUpdate={handleUpdate} />;
}This pattern keeps most of your component as a Server Component while isolating the interactive part.
If the error persists after adding "use client", verify that you are not importing the component into a Server Component or that custom hooks are properly marked:
// hooks/useCustomTransition.ts
"use client"; // Custom hooks also need this directive
import { useTransition } from "react";
export function useCustomTransition() {
const [isPending, startTransition] = useTransition();
// ... custom logic
return { isPending, startTransition };
}Ensure every file in the chain from your page to the hook has "use client" or is properly imported into a Client Component boundary.
If you need transition behavior outside of a component (such as in utility functions or event handlers defined outside components), use the standalone startTransition function instead:
import { startTransition } from "react";
// This can be used outside components
export function updateWithTransition(callback: () => void) {
startTransition(() => {
callback();
});
}Note that startTransition does not provide the isPending flag. Use it when you only need to mark updates as transitions without tracking loading state.
After adding the "use client" directive, clear the Next.js build cache and restart the development server to ensure changes are recognized:
rm -rf .next
npm run devFor production builds:
rm -rf .next
npm run build
npm run startNext.js caches component metadata, and stale cache can sometimes cause the error to persist even after fixing the code.
Confirm that your component is properly registered as a Client Component by checking the browser DevTools:
1. Open React DevTools in your browser
2. Locate your component in the component tree
3. Check for the component name - Client Components are rendered with their actual names, while Server Components may show as serialized data
4. Verify that useTransition returns valid [isPending, startTransition] values
You can also add a console.log to verify execution context:
"use client";
import { useTransition, useEffect } from "react";
export default function MyComponent() {
const [isPending, startTransition] = useTransition();
useEffect(() => {
console.log("Running on client:", typeof window !== "undefined");
}, []);
return <div>Client Component</div>;
}If the console.log shows true, your component is correctly running on the client.
When using useTransition with Next.js Server Actions, be aware that Server Actions are asynchronous while transitions must be synchronous. However, you can wrap async Server Actions inside startTransition, and React will correctly mark state updates as transitions. In Next.js 15, there is a known issue where calling revalidatePath() from within a Server Action wrapped in startTransition() can cause the isPending flag to never reset, leaving the UI stuck in a pending state. This works correctly in Next.js 14, so consider staying on v14 if you rely heavily on this pattern.
The "use client" directive creates a boundary in your component tree. All components imported into a Client Component become part of the client bundle, even if they do not use hooks themselves. To optimize bundle size, place "use client" as deep in the component tree as possible, isolating interactive components from their server-rendered parents.
For library authors creating reusable components that use hooks, always include "use client" at the top of component files to ensure they work correctly when imported into Next.js Server Components. If you are building a component library, consider providing both Server and Client versions of components where appropriate.
The useTransition hook is particularly useful for wrapping expensive state updates that might block user input, such as filtering large lists or updating complex UI. The isPending flag can be used to show loading indicators while the transition is in progress. For simple async operations without state updates, consider using the standalone startTransition function or the experimental useOptimistic hook for optimistic UI updates.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite