Handbook
Server Components
How to use ferry-ui in an app that has React Server Components.
Handbook
How to use ferry-ui in an app that has React Server Components.
In the package, each module of a component, a hook or a provider starts with 'use client'. A server component can render these components. It can pass serializable props to them.
import { PageContainer, PageHeader } from 'ferry-ui'
// A server component: the file has no 'use client' line.
export default function ProjectsPage() {
return (
<PageContainer>
<PageHeader title="Projects" description="The projects of your workspace." />
</PageContainer>
)
}A server component cannot call the functions of a client module. Hooks and variant helpers are in client modules.
These exports are in plain modules. A server component can use them.
| Export | Role |
|---|---|
| cn | It merges class names. |
| getErrorMessage | It gives a message that a person can read for an error. |
themeInitScript | It returns the script that applies the stored theme before the first paint. |
DEFAULT_THEME_STORAGE_KEY | The default localStorage key of the theme: 'ferry-ui-theme'. |
rowLinkProps | It returns the props of a table row that opens on a click. The click handler works only in a client component. |
isMac, modKey | The constants of the platform. On a server, they describe the OS of the server. |
These exports are in client modules. Call them from a client component only.
| Kind | Exports |
|---|---|
| Variant helpers | buttonVariants, badgeVariants, inputVariants, tabsListVariants, toggleVariants, statusBadgeVariants, iconBoxVariants |
| Hooks | Each hook, for example useTheme, useCopy and useModKey. |
The providers go in a client component. This example is for the App Router of Next.js.
'use client'
import type { ReactNode } from 'react'
import NextLink from 'next/link'
import { LinkProvider, ThemeProvider, Toaster, TooltipProvider, type LinkComponent } from 'ferry-ui'
const RouterLink: LinkComponent = ({ href, ...props }) => <NextLink href={href} {...props} />
export function Providers({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
<LinkProvider component={RouterLink}>
<TooltipProvider>
{children}
<Toaster />
</TooltipProvider>
</LinkProvider>
</ThemeProvider>
)
}React applies the theme after it loads. That is too late for the first paint. themeInitScript() returns a small script that applies the stored theme first.
The function only builds a string. Thus the server layout can call it. Put the script in the head element.
import type { ReactNode } from 'react'
import { themeInitScript } from 'ferry-ui'
import { Providers } from './providers'
import './globals.css'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
// The script sets the class before hydration: tell React the difference is expected.
<html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeInitScript() }} />
</head>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}The isMac and modKey constants read the platform one time, when the module loads. On a server, they describe the OS of the server. In markup that the server renders, use the useIsMac and useModKey hooks.
import { Button, Kbd, useModKey } from 'ferry-ui'
import { Search } from 'lucide-react'
export default function ModKeyHint() {
// "Ctrl" on the server and while React hydrates the page, then the key of the platform.
const mod = useModKey()
return (
<Button icon={<Search />}>
Search <Kbd>{mod} K</Kbd>
</Button>
)
}| Hook | On the server and during hydration | After hydration |
|---|---|---|
useIsMac() | false | true on an Apple platform. |
useModKey() | 'Ctrl' | '⌘' on an Apple platform. |
The server and the first client render give the same markup. As a result, React finds no hydration mismatch.