Next.js App Router
The React packages are client components that render on the server: a Server Component can import and render them directly, Next.js server-renders them, and the browser hydrates the markup without a mismatch. This page is about @oge-ui/react-*; the Angular packages are covered in Angular SSR.
Install and import the CSS
npm install @oge-ui/react # every MIT React family
# or one family at a time
npm install @oge-ui/react-grid @oge-ui/react-inputsThe packages ship class names, not CSS-in-JS: import the stylesheet once in the root layout. The JavaScript deliberately does not import its own CSS, so a server render or a bundler without a CSS loader never has to resolve it.
// app/layout.tsx — a Server Component: global CSS and the client providers
import type { ReactNode } from 'react';
import '@oge-ui/react/styles.css'; // or @oge-ui/react-<family>/styles.css
import '@oge-ui/core/themes/dark.css'; // optional theme, shared with Angular
import { Providers } from './providers';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="de">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Client components
Every JavaScript file of every React package starts with 'use client' — CI's use-client-check fails a build that loses it. What that means for your code:
| From | Works | Note |
|---|---|---|
| A Server Component | Yes | Render an OGE component directly, with serializable props only — arrays, plain objects, strings, numbers. |
Function props ( onClick , render props, calculateCellValue ) | In a client module | Functions cannot cross the server → client boundary; put them in your own 'use client' component. |
| Refs and imperative methods | In a client module | Same reason — useRef and component methods live on the client. |
A DataSource instance | In a client module | A class instance is not serializable; create it client-side (or pass the rows array from the server). |
// app/orders/page.tsx — a Server Component rendering an OGE client component
import { OgeGrid } from '@oge-ui/react-grid';
import { getOrders } from '@/lib/orders';
const columns = [
{ field: 'id', caption: 'Order', width: 90, dataType: 'number' as const },
{ field: 'customer', caption: 'Customer' },
{ field: 'total', caption: 'Total', dataType: 'number' as const },
];
export default async function OrdersPage() {
const orders = await getOrders();
// only serializable props cross the boundary: arrays, plain objects, strings
return <OgeGrid data={orders} keyField="id" columns={columns} locale="de-DE" />;
}'use client';
import { useState } from 'react';
import { OgeButton } from '@oge-ui/react-buttons';
import { OgeGrid } from '@oge-ui/react-grid';
interface Order {
readonly id: number;
readonly customer: string;
}
const columns = [
{ field: 'id', caption: 'Order', width: 90, dataType: 'number' as const },
{ field: 'customer', caption: 'Customer' },
];
export function OrdersBoard() {
// event handlers are functions — they need a client module of your own
const [orders, setOrders] = useState<readonly Order[]>([
{ id: 1, customer: 'Ada' },
]);
const add = () =>
setOrders((rows) => [...rows, { id: rows.length + 1, customer: 'Grace' }]);
return (
<>
<OgeButton text="Add order" onClick={add} />
<OgeGrid data={orders} keyField="id" columns={columns} locale="en-US" />
</>
);
}Providers
OgeLocaleProvider and the per-family Oge…ConfigProviders are React context, so they sit in a client module that the layout renders:
'use client';
import type { ReactNode } from 'react';
import { OgeLocaleProvider } from '@oge-ui/react';
import { de } from '@oge-ui/locales/de';
// app/providers.tsx — config and locale providers hold React context, so they
// live in a client module; the layout above stays a Server Component
export function Providers({ children }: { children: ReactNode }) {
return <OgeLocaleProvider pack={de}>{children}</OgeLocaleProvider>;
}Server rendering and hydration
apps/ssr-smoke/src/react-hydration.spec.tsxrenders every React family withrenderToStringin plain Node — any browser global touched while rendering throws, exactly as on a Next.js server — then hydrates it under<StrictMode>"seven minutes later" and fails on any warning.- Ids come from
useId(), so server and client agree. - In-memory data renders on the server: an array or an
ArrayDataSourceanswersloadSyncduring the first render, so the first page of rows is in the HTML. Remote sources (CustomDataSource,CursorDataSource,ODataDataSource) start loading after mount. - "Today" markers in the scheduler and the Gantt appear only after hydration, so a page rendered minutes earlier still hydrates cleanly.
The repository has no Next.js application under test; these guarantees come from the plain-Node render and hydrate suite above, which is what a Next.js server does with the components.
Locale on the server
Without a locale, a React family formats with navigator.language in the browser and the Node runtime's default on the server — a hydration mismatch for every reader whose language differs from the server's. Pass it explicitly (from the request, a cookie or your i18n router):
| Package | Components with locale |
|---|---|
@oge-ui/react-grid | OgeGrid |
@oge-ui/react-tree-list | OgeTreeList |
@oge-ui/react-inputs | OgeCalendar , OgeDateBox , OgeDateRangeBox , OgeNumberBox , OgeOtpInput , OgeRating |
@oge-ui/react-layout | OgeAvatar , OgeBadge , OgeCarousel , OgeDataView , OgeListView , OgeTileLayout , OgeTimeline |
@oge-ui/react-scheduler , -gantt , -kanban , -pivot | OgeScheduler , OgeGantt , OgeKanban , OgePivotGrid |
@oge-ui/react ( OgeLocaleProvider ) | Passes the pack’s locale to the families it configures that format numbers or dates (grid, inputs, editor, layout) |
Loading heavy families later
A family the first paint does not need can load after hydration with next/dynamic. Export helpers are separate entry points already (@oge-ui/react-grid/export-excel, …) and load only when imported — see Performance.
'use client';
import dynamic from 'next/dynamic';
// a heavy family the first paint does not need: load it after hydration
const Scheduler = dynamic(
() => import('@oge-ui/react-scheduler').then((m) => m.OgeScheduler),
{ ssr: false, loading: () => <p>Loading the calendar…</p> },
);