Mesrai
Back to blog
// essayTechnical Deep Dive

Server Component Importing Client Hook: The Boundary Bug

Real PR catch: server component using useState. Build error. Split into client child fix.

Mesrai TeamAugust 7, 20268 min read

A Next.js server component using `useState`. Build error. Subtle for newcomers to the App Router — the rules around server vs client components are not obvious.

The vulnerable diff

tsxapp/dashboard/page.tsx
// app/dashboard/page.tsx — server component by default
import { useState } from "react"; // BUG

export default function Dashboard() {
  // BUG: useState only works in client components
  const [tab, setTab] = useState("overview");
  return <div>{tab}</div>;
}

What is wrong

Next.js App Router treats every file as a server component by default — no React hooks (useState, useEffect, useRef, useMemo, useCallback, etc), no browser APIs (window, document, localStorage). Client hooks throw a build error. The fix is to split the interactive piece into a separate file with `"use client"` at the top — that file can use any client hook, and the server component imports and renders it. Component boundaries become the client/server seam.

The attack

Build output:

textexploit
Error: useState can not be used in a Server Component.
        Mark the file with "use client" to use it in a client component
        or move it to a Client Component.

Or worse: a missing `"use client"` directive lets the file compile but state is silently lost on hydration.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 30 sec ago

[mesrai] [code-review] [Language] [RSC] [high]

useState in a server component. Move the interactive piece into a
client component:

  // app/dashboard/components/TabSwitcher.tsx
  "use client";
  import { useState } from "react";

  export function TabSwitcher() {
    const [tab, setTab] = useState("overview");
    return <div>...</div>;
  }

  // app/dashboard/page.tsx (stays server component)
  import { TabSwitcher } from "./components/TabSwitcher";

  export default function Dashboard() {
    // Server-fetch any data needed (DB, fetch, headers, cookies)
    return <TabSwitcher />;
  }

Keep server components for data fetching + composition. Push
interactivity to leaf client components.

The fix

tsxapp/dashboard/ (fixed)
// app/dashboard/components/TabSwitcher.tsx
"use client";
import { useState } from "react";

export function TabSwitcher() {
  const [tab, setTab] = useState("overview");
  return <div onClick={() => setTab("billing")}>{tab}</div>;
}

// app/dashboard/page.tsx (stays a server component)
import { TabSwitcher } from "./components/TabSwitcher";
export default async function Dashboard() {
  const data = await db.users.findUnique({ where: { id: "..." } });
  return <TabSwitcher />;
}

Server component does data fetching (which now runs on the server with direct DB access, no API roundtrip). Client component handles interactivity (with state + handlers). The boundary lives at the file with `"use client"`.

Why human review missed it

RSC rules are 18 months old as of 2026 and many teams have not internalized them. The error message is clear when it fires, but more subtle missing-directive cases (no error, silent broken behavior) are harder. Mesrai catches `"use client"` mismatches and any client-hook import in a server context.

Related rules + further reading

Mesrai rule pack: language/rsc-boundary — flags client hook usage in server component files.

Next.js docs: Server and Client Components.

React Server Components RFC.

Takeaway

Server components for data + composition. Client components for state + interactivity. Split at the file with `"use client"`. Mesrai catches the boundary every time.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.