Skip to content

React components for Next.js and Server Components

Where the client boundary goes, the error you get if you miss it, and what the CSS costs.

The short answer

oks-ui works in the Next.js App Router and with static export. Since oks-ui 1.3.1 each component's own entry (oks-ui/button, oks-ui/modal, and so on) is marked as a client component, so you can import it straight into a Server Component and Next prerenders its HTML. The exceptions are oks-ui/text-editor and oks-ui/pagination, which also export plain functions a server can call, and the main oks-ui entry: render their components only from a file that starts with "use client". Import each component's stylesheet next to it, or oks-ui/styles.css once in your root layout.

Tested, not assumed

The client-boundary results come from a new Next.js app built on with Next.js 16.3.7, React 19.3.0 and oks-ui 1.3.1, and compared with oks-ui 1.2.2 on the same page. The CSS and static-export results come from an earlier app built on with Next.js 16.3.0 (Turbopack), React 19.2.8 and oks-ui 1.2.1. Only the App Router was tested.

1. Import a component’s own entry, even in a Server Component

Since oks-ui 1.3.1, oks-ui/button, oks-ui/modal and the other per-component entries (plus oks-ui/motion) start with "use client" themselves. A Server Component can import them directly, and Next still prerenders their HTML: the built page contained the <button> markup. Props that are functions, such as onPress, cannot be passed from a Server Component, so keep those in a file that starts with "use client".

// app/page.tsx  (a Server Component, no "use client")
import { Button } from "oks-ui/button";
import "oks-ui/button.css";

const Page = () => {
  return (
    <main>
      <h1>Welcome</h1>
      <Button color="primary">Get started</Button>
    </main>
  );
};

export default Page;
// SaveButton.tsx  (interactive, so it is a client file)
"use client";
import { Button } from "oks-ui/button";
import "oks-ui/button.css";

export const SaveButton = ({ id }: { id: string }) => {
  return (
    <Button color="primary" onPress={() => console.log("save", id)}>
      Save
    </Button>
  );
};

What still fails

Two entries are deliberately not marked, because they also export plain functions that a server can call: oks-ui/text-editor (blocksToHtml, blocksToMarkdown) and oks-ui/pagination (paginationRange). The main oks-ui entry is unmarked for the same reason (it exports helpers such as oksVar). Importing a component from any of these into a Server Component fails the build, and so does importing any component on oks-ui 1.2.x. This is the real message from the test app, the same on both:

// app/page.tsx  (a Server Component)
import { Button } from "oks-ui";   // <- the main entry is not a client entry

const Page = () => {
  return <Button color="primary">Direct</Button>;
};

export default Page;

// next build:
// Error occurred prerendering page "/".
// TypeError: (0 , c.useState) is not a function or its return value is not iterable

The fix is the pattern above: import from the component’s own entry, or move the import into a file that starts with "use client".

2. Load the CSS, or nothing is styled

With Turbopack, the stylesheet import inside a component’s JavaScript is not followed. In the test app, a client component that imported a Modal without adding any CSS shipped no stylesheet at all. You have two options, and they differ in size:

  • Everything, once. Import oks-ui/styles.css in the root layout. In the test app that was about 45 KB gzipped (352 KB raw), containing every component’s styles and the design tokens, on every page.
  • Only what you use. Import each component’s own stylesheet next to it. The Button stylesheet was about 2.7 KB gzipped in the same build.
// app/layout.tsx
import "oks-ui/styles.css";

const RootLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
};

export default RootLayout;

3. Static export

The same app built with output: "export" and prerendered every page. oks-ui needs no configuration of its own; these are Next’s own settings for a static host:

// next.config.mjs, for static export
export default {
  output: "export",
  images: { unoptimized: true },
  trailingSlash: true, // if you host on S3 and CloudFront
};

Questions

Does oks-ui work with the Next.js App Router?

Yes. The client boundary was tested on 30 September 2026 in a new Next.js 16.3.7 app with React 19.3.0 and oks-ui 1.3.1. The CSS and static-export results are from a test on 24 September 2026 with Next.js 16.3.0, React 19.2.8 and oks-ui 1.2.1. Only the App Router was tested.

Do I need "use client" to use oks-ui in Next.js?

Not for a component's own entry from oks-ui 1.3.1: import oks-ui/button straight into a Server Component and Next prerenders its HTML. You still need "use client" for components from oks-ui/text-editor, oks-ui/pagination or the main oks-ui entry, if you pass function props such as onPress from a Server Component, or if you are on oks-ui 1.2.x, where no entry is marked.

Why do I get "useState is not a function" with oks-ui in Next.js?

You imported a component into a Server Component from the main oks-ui entry, from oks-ui/text-editor or oks-ui/pagination, or you are on oks-ui 1.2.x. The component uses React state, which does not exist on the server, so the build fails with TypeError: (0, c.useState) is not a function or its return value is not iterable. Import from the component's own entry (for example oks-ui/button, on 1.3.1 or later), or move the import into a file that starts with "use client".

Why are my oks-ui components unstyled in Next.js?

Under Next.js with Turbopack the stylesheet import inside a component's JavaScript is not followed, so nothing loads unless you add the CSS yourself. Import oks-ui/styles.css once in your root layout, or add each component's own CSS, for example import "oks-ui/button.css".

Does oks-ui work with Next.js static export?

Yes. A test app with output: "export" built and prerendered an oks-ui Button into the static HTML. Set images.unoptimized to true, and trailingSlash to true if you host on S3 and CloudFront. oks-ui itself needs no special configuration.