React Server Components
Import RSC-safe layout primitives from the ./server entry for use in Next.js App Router Server Components, with a client boundary pattern for interactive components.
Overview
Next.js App Router renders Server Components (RSC) by default. React hooks, context, and "use client" are not allowed in Server Components. Stareezy UI ships a dedicated "./server" entry that exports hook-free layout primitives safe for use in RSC.
"./server" entry contains no useState, useEffect, useContext, or any other React hook. It also has no "use client" directive — it is fully server-safe.Server-safe primitives
The following primitives are exported from the "./server" entry. They resolve theme tokens via CSS custom properties (var(--token-id)) instead of React hooks:
BoxViewStackTextDivider// Import from the server entry — safe in any Server Component
import { Box, View, Stack, Text, Divider } from '@stareezy-ui/components/server'Client entry — interactive components
The default "." client entry is unchanged and continues to export all interactive components with their "use client" boundaries:
// Default client entry — use inside Client Components
import { Button, Input, Modal, Tabs, Dropdown } from '@stareezy-ui/components'Next.js App Router example
Here is a complete example of a page that uses both RSC-safe primitives and interactive client components:
// app/page.tsx — Server Component (default in App Router)
import { Box, Stack, Text } from '@stareezy-ui/components/server'
import { HeroActions } from './HeroActions' // Client Component below
export default function HomePage() {
// Server-side data fetch — no useEffect, no useState
const data = await fetchData()
return (
<Box p={{ base: 16, md: 32 }}>
<Stack gap={16}>
<Text style={{ fontSize: 32, fontWeight: 800 }}>
Welcome
</Text>
<Text style={{ fontSize: 16, opacity: 0.7 }}>
{data.tagline}
</Text>
{/* Client Component wrapped in a boundary */}
<HeroActions />
</Stack>
</Box>
)
}// app/HeroActions.tsx — Client Component
'use client'
import { Button, Modal } from '@stareezy-ui/components'
import { useState } from 'react'
export function HeroActions() {
const [open, setOpen] = useState(false)
return (
<>
<Button onPress={() => setOpen(true)} variant="primary">
Get started
</Button>
<Modal open={open} onClose={() => setOpen(false)} title="Get started">
{/* Modal content */}
</Modal>
</>
)
}// app/layout.tsx
import { Box, Stack } from '@stareezy-ui/components/server'
import { ThemeProvider } from '@stareezy-ui/tokens'
import { NavBar } from '@/components/NavBar' // 'use client'
import './stareezy.config'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<ThemeProvider theme="aurora">
{/* Server primitive wraps everything */}
<Stack minH="100vh">
{/* Client boundary for interactive nav */}
<NavBar />
{/* Page content (may be Server or Client Components) */}
<Box flex={1} p={{ base: 16, md: 32 }}>
{children}
</Box>
</Stack>
</ThemeProvider>
</body>
</html>
)
}Client boundary pattern
The recommended pattern is to push "use client" as deep as possible — only the components that actually need hooks or browser APIs need to be Client Components.
// ✅ Good — boundary is at the interactive leaf
// Server Component:
import { Box, Stack, Text } from '@stareezy-ui/components/server'
import { LikeButton } from './LikeButton' // 'use client'
export default function Post({ post }) {
return (
<Box p={20}>
<Text style={{ fontWeight: 700 }}>{post.title}</Text>
<Text>{post.body}</Text>
<LikeButton postId={post.id} /> {/* Only this needs client */}
</Box>
)
}
// ❌ Avoid — marking the whole page as client
'use client'
import { Box, Stack, Text } from '@stareezy-ui/components' // ← not needed
...Box, Stack, etc.) from the "./server" entry can be used as wrappers around Client Components. Props including BoxLayoutProps work exactly as on the client entry.ThemeProvider in App Router
ThemeProvider from @stareezy-ui/tokens is a "use client" component because it manages theme state. Wrap it in a client component shell when used in the root layout:
// app/Providers.tsx
'use client'
import { ThemeProvider } from '@stareezy-ui/tokens'
import type { ReactNode } from 'react'
export function Providers({ children }: { children: ReactNode }) {
return <ThemeProvider theme="aurora">{children}</ThemeProvider>
}
// app/layout.tsx — import Providers, keep layout a Server Component
import { Providers } from './Providers'
import { Box } from '@stareezy-ui/components/server'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Providers>
<Box minH="100vh">{children}</Box>
</Providers>
</body>
</html>
)
}ThemeProvider. As long as ThemeProvider is an ancestor in the component tree, the server primitives render with the correct theme colors — even though they have no React hooks themselves.