Using cascivo with Next.js (App Router / RSC)

cascivo works in Next.js App Router projects out of the box: components ship with 'use client' preserved in the published bundle, so React Server Components treat them as client components without any wrapper on your side. This page covers the wiring and how the server/client split falls out.

Prerequisite reading: GETTING-STARTED.md for the two install paths. Everything below applies to both; snippets use the prebuilt @cascivo/react package.

Install

pnpm add @cascivo/react @cascivo/themes @preact/signals-react

Import the themes CSS in the root layout

Do this once, in a Server Component — app/layout.tsx is the natural place. Next.js supports global CSS imports in Server Components, and this keeps the theme out of every client bundle:

// app/layout.tsx — a Server Component (no 'use client')
import '@cascivo/themes/light-dark.css' // tokens (once) + base typography + light & dark

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" data-theme="light">
      <body>{children}</body>
    </html>
  )
}

data-theme on <html> themes the whole app; it can also scope any subtree (<aside data-theme="dark">). See THEMING.md.

Theme switching without a flash (SSR)

The static data-theme="light" above is a fine default. For a user-toggleable theme that survives reload with no flash of the wrong theme, use the theme runtime from @cascivo/react: inline themePreloadScript() in the root layout so the persisted theme paints on the first byte, then toggle from a client component with useTheme().

// app/layout.tsx — Server Component. The pre-paint script sets data-theme before the
// app bundle runs, so there is no flash; the client then owns toggling.
import '@cascivo/themes/light-dark.css'
import { themePreloadScript } from '@cascivo/react'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <script dangerouslySetInnerHTML={{ __html: themePreloadScript() }} />
      </head>
      <body>{children}</body>
    </html>
  )
}

Wrap your client tree in <ThemeProvider> and toggle with useTheme() (a 'use client' component). Full API in THEMING.md.

Component CSS is bundled automatically

Each component pulls in its own stylesheet when you import it — Next.js (webpack or Turbopack) includes the styles only for the components you actually use and tree-shakes the rest. There is no component-CSS import to add or maintain; the themes import above is the only global CSS wiring.

This holds for Server Components as well as client ones. RSC resolves a dependency with the react-server export condition, which @cascivo/react points at its CSS-bearing build; the CSS-free node/ twin stays reserved for bare-Node SSR loaders that cannot import a .css file at all.

Fixed in 0.18.0. Before that release there was no react-server condition, so RSC fell through to node and every component without a 'use client' directive — exactly the clientJs: 'none' ones — rendered on the server from a build with no CSS edges. Their stylesheets were never collected and they painted unstyled. The documented workaround was to import the 328 KB aggregate @cascivo/react/styles.css in the root layout, which is why an SSR page shipped ~384 KB of CSS. If you are still doing that, delete the import: measured on apps/examples/react-next, the same page drops to 34 KB. test/rsc-css.mjs asserts every class in the prerendered HTML has a matching rule, so this cannot regress silently again.

How the server/client split works

// app/page.tsx — Server Component: static composition is fine
import { Card, CardContent, Badge } from '@cascivo/react'
import { SaveButton } from './save-button'

export default function Page() {
  return (
    <Card>
      <CardContent>
        <Badge>Beta</Badge>
        <SaveButton />
      </CardContent>
    </Card>
  )
}
// app/save-button.tsx — your client boundary for interactivity
'use client'
import { Button } from '@cascivo/react'

export function SaveButton() {
  return <Button onClick={() => save()}>Save</Button>
}

If your own client component reads a signal's .value during render, call useSignals() (from @cascivo/core) as its first statement — see the gotcha in TESTING.md and TROUBLESHOOTING.md.

Copy-paste flow in Next.js

npx cascivo init + npx cascivo add <component> work the same in a Next.js repo: source is copied to outputDir (default src/components/ui) with 'use client' already in the source files. Import from there instead of @cascivo/react; the theming and boundary rules above are identical.

Client-side routing for nav components

cascivo's config-driven nav (SideNav, ShellHeader, Header, Breadcrumb, …) renders plain <a href> by default. To route those links through Next.js <Link> — keeping client-side transitions and prefetching — register the link component once at app start (a module singleton; no provider). Do it in a small client component mounted in your root layout:

'use client'
import { setLinkComponent } from '@cascivo/react'
import Link from 'next/link'

// next/link already takes `href`, so the cascivo prop bag maps 1:1.
setLinkComponent(Link)

Import setLinkComponent (and the LinkComponentProps type, if you write a custom adapter) from @cascivo/react, not @cascivo/core — see the phantom-dependency note in HEADLESS.md. After this, every href-based nav item navigates via the router and inherits its prefetching.

Naming collisions with Next.js globals

Two cascivo exports shadow Next.js modules — alias them on import:

import { Image as CascivoImage, Link as CascivoLink } from '@cascivo/react'

Editor auto-import sometimes picks the wrong Image/Link; if styles or routing break after adding one, check the import resolves where you meant. Full list in the @cascivo/react README.

FAQ

Can I use cascivo with React Server Components at all, given it's signal-driven? Yes. Components are RSC-compatible and mark themselves 'use client' where they need interactivity — the signals runtime only ever executes in the browser. Server Components compose them freely as long as the props they pass are serializable.

Where's a working example? apps/examples/react-next is the Next.js App Router example in this repo, demonstrating 'use client' boundary placement for signal-driven components.

See also

As Markdown: /docs/using-with-nextjs.md

← All guides