This example demonstrates how to integrate @clickhouse/click-ui with Next.js 16 App Router and Server-Side Rendering (SSR).
Before installing Click UI, configure transpilePackages (available in Next.js 13+) in your Next.js config next.config.ts:
module.exports = {
transpilePackages: ['@clickhouse/click-ui'],
};Important
The transpilePackages option is required because Click UI distributes CSS colocated with components. Next.js restricts global CSS imports from node_modules by default. Transpiling the package allows Next.js to process the CSS through its build pipeline, preventing the "Global CSS cannot be imported from within node_modules" error.
Install the package:
npm i @clickhouse/click-ui@latestCreate a client-side theme provider that wraps your application with ClickUIProvider, e.g. app/ThemeProvider.tsx:
"use client";
import { useState, type ReactNode } from "react";
import { ClickUIProvider, ThemeName, useInitialTheme } from '@clickhouse/click-ui';
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const { theme, mounted } = useInitialTheme({
defaultTheme: 'light',
});
const [currentTheme, setCurrentTheme] = useState<ThemeName | null>(theme);
if (!mounted || !currentTheme) {
return null;
}
return (
<ClickUIProvider theme={currentTheme} persistTheme>
{children}
</ClickUIProvider>
);
};Configure the root layout with InitCUIThemeScript in the <head> to prevent theme flash on initial load.
Include suppressHydrationWarning on <html> and <body> elements to suppress warnings caused by theme switching:
import type { Metadata } from "next";
import { ThemeProvider } from './ThemeProvider'
import { InitCUIThemeScript } from '@clickhouse/click-ui';
export const metadata: Metadata = {
title: "Next.js + Click UI",
description: "SSR example with Click UI",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<InitCUIThemeScript defaultTheme="light" />
</head>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
);
}Important
The suppressHydrationWarning prop is required in root html element. This suppresses React hydration mismatch warnings that occur when the server-rendered HTML differs from the client-side hydrated HTML due to theme state. This is a standard practice in component libraries like Material UI when dealing with theme variables.
Add a theme toggle to your application:
"use client";
import { useState, type ReactNode } from "react";
import { ClickUIProvider, Container, Switch, ThemeName, useInitialTheme } from '@clickhouse/click-ui';
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const { theme, mounted } = useInitialTheme({
defaultTheme: 'light',
});
const [currentTheme, setCurrentTheme] = useState<ThemeName | null>(theme);
if (!mounted || !currentTheme) {
return null;
}
return (
<ClickUIProvider theme={currentTheme} persistTheme>
<Container orientation='horizontal' gap='sm' alignItems='center'>
<Switch
checked={currentTheme === 'dark'}
onCheckedChange={(checked) => setCurrentTheme(checked ? 'dark' : 'light')}
label='Dark mode'
/>
</Container>
{children}
</ClickUIProvider>
);
};Note
Themes are plain CSS custom properties switched by the data-cui-theme attribute on the root <html> element. There is no runtime style computation, so the only possible flash is the wrong theme on first paint, which InitCUIThemeScript prevents.
The data-cui-theme attribute on the root <html> element allows you to extend theming to your own components with vanilla CSS or CSS modules:
[data-cui-theme="light"] {
--custom-bg: #fff;
--custom-border: #e5e5e5;
}
[data-cui-theme="dark"] {
--custom-bg: #121212;
--custom-border: #333;
}
.my-card {
background: var(--custom-bg);
border: 1px solid var(--custom-border);
}This works immediately on page load without flash because InitCUIThemeScript sets the attribute before React hydration.
Use Click UI components in both server and client components:
import { Button, Container, Title } from "@clickhouse/click-ui";
export default function Home() {
return (
<Container>
<Title type="h1">Hello ClickHouse</Title>
<Button type="primary" label="Get Started" />
</Container>
);
}Note
The useInitialTheme hook handles the initial theme state from localStorage and prevents hydration mismatches by returning mounted: false until the client-side effect runs. This ensures the first render matches the server output.
Tip
Enable persistTheme on ClickUIProvider to automatically save theme changes to localStorage. The InitCUIThemeScript reads this value and applies it immediately before React hydration to prevent theme flashing.