---
title: "Client and server code"
description: "The client-safe entry points and the import rules that keep tokens and the CMS out of visitor bundles."
url: https://docs.systhema.app/concepts/client-and-server
version: unreleased (main)
docs_index: https://docs.systhema.app/llms.txt
---

Systhema's tokens exist to generate CSS at build time; the browser never needs the token dataset, only the CSS and a few resolved values. This page explains which entry points a `'use client'` module may import so the dataset, and in a Payload project the CMS, stay out of visitor bundles.

The rules at a glance:

- In a `'use client'` module, import runtime values from `@systhemaui/core/client` or `@systhemaui/core/utils`, never from `@systhemaui/core`. Type-only imports are fine from either.
- Read token values in client components from the forwarded snapshot (`clientVariants`, `clientTokenValue`), at render time.
- Keep `SysthemaProvider` at the root of every layout; it forwards that snapshot.
- In a Payload project, import client code from the dedicated client entries, never the package root.

## The client-safe entry

**Never import `@systhemaui/core` from a `'use client'` module.** The main entry statically reaches `handleTokens` → the generated token map → all 14 design-token JSON files. JSON modules are whole-module (there is nothing for a bundler to tree-shake), so a single line like

```ts
// Wrong: ships the entire design-token dataset to the browser
import { getSysthemaConfigSync } from '@systhemaui/core'
```

in one client component puts the whole dataset — over a megabyte in a typical project — into the browser bundle. Those tokens exist to generate CSS at build time; they have no runtime job in the browser.

Import from `@systhemaui/core/client` instead:

```ts
// Right: a few KB, no token data
import {
  getSysthemaConfig,
  getSysthemaConfigSync,
  setSysthemaConfigSync,
  getSysthemaCookieConsentConfig,
  getSysthemaClientTokens,
  setSysthemaClientTokens,
  clientBreakpoints,
  clientScopes,
  clientTokenValue,
  clientVariants,
  clientThemes,
  clientLayoutBackgrounds,
  resolveTokenPx,
  // plus the locale helpers: defineSysthemaLocales, isSysthemaLocale,
  // isSysthemaLocalePrefixedOnHost, getSysthemaLocalizedPathname,
  // getSysthemaInternalLocalePathname, getSysthemaPublicHref,
  // getSysthemaLocaleOrigin, getSysthemaLocaleDirection,
  // getSysthemaLocaleMissingBehavior, getSysthemaAlternates
  // plus the pure utils: getFirstKey, toKebabCase, toUrlCase, toSnakeCase,
  // toTitleCase, deepMerge, deepMergeReplace, isObject, isArray
} from '@systhemaui/core/client'
```

The locale helpers are pure path and host math over a resolved `SysthemaLocales` contract — no token data, no filesystem — so both implementations share the same module.

Everything the main entry exports as a **type** (`SysthemaConfig`, `ColorSystem`, `ButtonVariant`, `AspectRatio`, …) is re-exported here too, so type-only imports can point at either.

## How it resolves

`./client` is a conditional export with two implementations behind one specifier:

| Condition                                             | Resolves to              | Backed by                                                          |
| ----------------------------------------------------- | ------------------------ | ------------------------------------------------------------------ |
| `browser` (client bundles — Turbopack, webpack, Vite) | `dist/client.browser.js` | The in-memory config store and the forwarded client-token snapshot |
| `default` (Node, RSC, SSR, any other bundler)         | `dist/client.js`         | The real `systhema.config.*` loader and the real token data        |

So server rendering is byte-identical to reading the tokens directly, while the browser never sees them. If a bundler doesn't understand the `browser` condition, it falls back to the full implementation — slower, never wrong.

## Utilities are client-safe

The pure helpers under `@systhemaui/core/utils` do not reach the token data, so importing from `/utils` in a client component is fine.

## Your own client components

The same rule applies to your own components: import `@systhemaui/core/client` (or `@systhemaui/core/utils`) from them, never the main entry.

If a client component needs a resolved token value, read it from the forwarded snapshot rather than the tokens:

```tsx
'use client'
import { clientVariants, clientTokenValue } from '@systhemaui/core/client'

export function MyThing({ variant }: { variant?: string }) {
  // Read at render time — module bodies run before the snapshot is seeded.
  const variants = clientVariants('button')
  const resolved = variant ?? variants[0]
  return <button className={`button-${resolved}`} />
}
```

See [Client tokens](https://docs.systhema.app/reference/core-client.md#client-tokens) for the snapshot and its readers.

## Swiper is loaded on demand

`<Carousel>` and `<GalleryWrapper>` keep Swiper (~120 KB) behind `lazy(() => import(…))`, so a page with no carousel or gallery never fetches it.

While the chunk resolves, a `<Suspense>` fallback renders the same `.swiper` / `.swiper-wrapper` / `.swiper-slide` structure Swiper produces — Systhema owns that CSS, so the layout is unchanged. The tradeoff: for that moment the slides are a static, non-draggable row (no inline `spaceBetween` gaps, no transforms). Under SSR the dynamic import resolves on the server, so the published HTML still contains the real markup and there is no content gap for crawlers.

## Payload code in client components

In a Payload project, a `'use client'` file imports from the dedicated client entries, never from the package root. See [Imports in 'use client' files](https://docs.systhema.app/payload/with-systhema.md#imports-in-use-client-files) and [Payload import paths](https://docs.systhema.app/reference/payload-imports.md).

## Related

- [@systhemaui/core/client](https://docs.systhema.app/reference/core-client.md)
- [Browser support](https://docs.systhema.app/concepts/browser-support.md)
