ValyroUI Русская версия

Getting started: Waku

React Server Components: components without function props render directly in server components, and interactive parts live in 'use client' files

1. Create the app

Tested with Waku 1.0.0-rc.3 (the latest release is an RC; there's no stable 1.0 yet).

npm create waku@latest -- --project-name my-app
cd my-app

2. Install the package

Works with react and react-dom 17, 18 and 19 (React 17: ids from useId appear right after mount). The package ships ES modules (plus a CommonJS copy for require()), has no runtime dependencies and has one file per module, so your bundle includes only the components you import; to load only their CSS as well, see “Only the CSS you need” below. Fonts are separate files: the vercel theme (the ThemeProvider default) uses Geist, so import valyro-ui/fonts/geist.css and valyro-ui/fonts/geist-mono.css next to the styles, or text falls back to the system font; glass uses system fonts and needs none. Until the package is published, build and pack it in the repository (npm run build:core && npm pack ./dist/core) and install the tarball from the repository root: npm install /path/to/ValyroUI/valyro-ui-0.1.0.tgz.

npm install valyro-ui

3. Theme on <html>

Without _root.tsx, Waku generates <html> itself, and the server-rendered HTML has no theme attributes. Create a root element so the first paint is already themed.

import type { ReactNode } from 'react';

export default async function RootElement({ children }: { children: ReactNode }) {
  return (
    <html lang="en" data-theme="glass" data-mode="light">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>{children}</body>
    </html>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

4. Styles and ThemeProvider in the layout

The layout is a server component: ThemeProvider receives only strings, so you can import it right here. Waku outputs the CSS into <head> on its own. In the template layout, remove the wrapper with the Nunito font (it overrides the theme font) and the fixed Header/Footer; you can keep Tailwind in styles.css — its preflight sits in @layer base and doesn't interfere with ValyroUI styles. With SSR, pass locale to ThemeProvider. Otherwise numbers and dates are formatted in the server locale on the server and in the user locale in the browser, and hydration fails on the mismatched text (“1,200” vs “1 200”).

import '../styles.css';
import 'valyro-ui/styles.css';

import type { ReactNode } from 'react';
import { ThemeProvider } from 'valyro-ui';

type RootLayoutProps = { children: ReactNode };

export default async function RootLayout({ children }: RootLayoutProps) {
  return (
    <ThemeProvider theme="glass" mode="light" locale="en-US">
      {children}
    </ThemeProvider>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

5. The page as a server component

The package is marked 'use client', so Card, Badge and other components with serializable props render directly from a server component. Anything with handlers goes in a 'use client' file: the Showcase from the Next.js guide lives unchanged in src/components/Showcase.tsx. Passing a function prop from a server component (<Button onClick={…}>) fails the build: “Event handlers cannot be passed to Client Component props”.

import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from 'valyro-ui';
import { Showcase } from '../components/Showcase';

// Server Component: ValyroUI components with serializable props render here directly;
// anything with handlers or state lives in the 'use client' Showcase.
export default async function HomePage() {
  return (
    <>
      <title>ValyroUI on Waku</title>
      <div style={{ padding: '32px 32px 0', maxWidth: 720 }}>
        <Card>
          <CardHeader>
            <CardTitle>Server Component</CardTitle>
            <CardDescription>Rendered on the server</CardDescription>
          </CardHeader>
          <CardContent>
            <Badge color="green" dot>
              RSC
            </Badge>
          </CardContent>
        </Card>
      </div>
      <Showcase />
    </>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

6. Build and run

npx tsc --noEmit
npm run build
npm run start

Other frameworks