SSR & bundlers
The grid renders on the server and hydrates normally. Nothing touches window,
document or ResizeObserver outside an effect, so a page containing a grid
does not need ssr: false or a typeof window guard.
This is covered by tests that render the component to a string in a DOM-free environment — including with the toolbar, filter row, group panel, pager, summary bar and a remote data source all switched on.
What the server renders
| On the server | After hydration | |
|---|---|---|
| Root, header, ARIA roles | rendered | unchanged |
| Column captions | rendered | unchanged |
| Row window | empty | measured and filled |
| Web Worker | never constructed | constructed above the threshold |
Remote DataSource | load() called once, not awaited | loaded by the client |
The row window is empty because the viewport has no size until the browser measures it. Guessing a row count server-side would render rows the client immediately replaces.
On a remote source the server render calls load() once and does not wait for
it — the markup goes out without rows and the client loads its own after
hydration. With an absolute URL that is one extra request per page render whose
answer is discarded; a relative fetch('/api/...') simply fails on the server,
and the grid absorbs the failure without breaking the render.
The markup still contains the grid skeleton and the column headers, so the pre-hydration page is not a blank box.
Next.js (App Router)
The grid is a client component — it holds state and subscribes to events. Mark the file that renders it:
'use client';
import { DataGrid } from '@kanunilabs/datagrid-react';
import '@kanunilabs/datagrid-react/styles.css';
export function SalesGrid({ rows }) {
return <DataGrid gridId="sales" dataSource={rows} rowKey="id" columns={columns} />;
}
A server component can fetch the rows and pass them down as a prop; only the grid itself needs the client boundary.
Import the stylesheet once — in the root layout or in the client component.
The worker in Next.js
Nothing to do. The worker is compiled into the package and starts from a Blob URL, so Next never has to resolve a worker file out of a dependency — which it does not do.
Two cases still need the file, and it ships in the package for them:
- a Content-Security-Policy without
worker-src … blob:— the browser refuses the Blob, and only a real URL will start the worker; - wanting the worker as its own cacheable file rather than inlined in your bundle.
Either way, serve the file from your own origin: a browser will not start a worker script from another domain, so a CDN on a different host does not work.
cp node_modules/@kanunilabs/datagrid-core/dist/datagrid.worker.js public/
<DataGrid worker={{ url: '/datagrid.worker.js' }} /* ... */ />
If the worker cannot start for any reason the grid still works: it runs the same pipeline on the main thread and warns once in development. The only difference is that a large sort blocks the UI instead of running off-thread.
Vite
Also nothing to do. If you need the file route (CSP or CDN, as above):
import workerUrl from '@kanunilabs/datagrid-core/dist/datagrid.worker.js?url';
<DataGrid worker={{ url: workerUrl }} /* ... */ />
Other bundlers
Any setup that can serve a static .js file works: copy
node_modules/@kanunilabs/datagrid-core/dist/datagrid.worker.js somewhere
public and pass its URL. The file is self-contained — it has no imports of its
own, so no additional chunk resolution is involved.
Turning the worker off
<DataGrid worker={{ enabled: false }} /> {/* always main thread */}
<DataGrid worker={{ threshold: 50_000 }} /> {/* offload later than the default 20.000 */}
Below the threshold the main thread is genuinely faster — the round trip costs more than the sort saves — so small grids never use the worker regardless.
Content Security Policy
The default worker starts from a Blob URL, so a policy that restricts workers
needs worker-src 'self' blob:. If you serve the file yourself instead (see
above), it is loaded from your own origin and worker-src 'self' is enough.
The grid does not use eval, inline scripts or remote resources.
Next: performance.