---
title: "Production CSS optimization"
description: "Structural passes, variable obfuscation and prerender-only class obfuscation."
requested_language: hu
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/hu/styling/optimization
version: unreleased (main)
docs_index: https://docs.systhema.app/hu/llms.txt
---
> This page isn't translated yet. Showing English.


Systhema can post-process its CSS for production: three structural passes that simplify the token output, and two obfuscators that shorten variable and class names. Everything sits under one `optimization` key in `systhema.config`, everything is off by default, and every pass runs only when `NODE_ENV` is `production`, so development output stays readable.

## Choosing what to turn on

| Setting                    | What it does                                    | Constraint                                                              |
| -------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------- |
| `optimization: true`       | The three structural passes. Never obfuscation. | None.                                                                   |
| `obfuscateVariables: true` | Renames Systhema's CSS custom properties.       | Variable names used in CMS content need an `ignore` entry.              |
| `obfuscateClasses: true`   | Renames Systhema's class names after the build. | Fully prerendered sites only; needs the `systhema-core obfuscate` step. |

Be clear about what each buys. The structural passes remove a good share of the raw CSS, but the bytes they remove are the repetitive ones that brotli already compresses to almost nothing, so the file a visitor downloads stays about the same size. They make the stylesheet cheaper to decompress and parse, not smaller to transfer. The transfer savings come from the obfuscators: variable obfuscation cuts roughly a tenth of the compressed front-end CSS on the default tokens, and class obfuscation a few percent more. Each obfuscator has a constraint, so turn them on deliberately.

## Structural passes

`optimization: true` turns on the three structural passes:

```ts title="systhema.config.ts"
import type { SysthemaConfig } from '@systhemaui/core'

const config: SysthemaConfig = {
  optimization: true,
}

export default config
```

The object form names each pass, so you can pick them one by one and add the obfuscators:

```ts title="systhema.config.ts"
import type { SysthemaConfig } from '@systhemaui/core'

const config: SysthemaConfig = {
  optimization: {
    pruneUnchangedBreakpoints: true,
    mergeThemeDuplicates: true,
    variableReferencesResolution: true,
    obfuscateVariables: true,
    obfuscateClasses: false,
  },
}

export default config
```

- **`pruneUnchangedBreakpoints`** drops a breakpoint's variable declarations when they repeat the value of the breakpoint below.
- **`mergeThemeDuplicates`** merges color modes that produce identical variable sets into one rule, for example a custom theme that inherits a mode unchanged.
- **`variableReferencesResolution`** replaces a `var(--x)` with its value when `--x` is defined in exactly one place.

None of them changes how a page renders. The Systhema templates ship with the structural passes on.

## Obfuscation

Both obfuscators accept `true` for the defaults, or an options object of the type `SysthemaObfuscationStyle` (exported from `@systhemaui/core`):

```ts title="systhema.config.ts"
import type { SysthemaConfig } from '@systhemaui/core'

const config: SysthemaConfig = {
  optimization: {
    obfuscateVariables: { prefix: '--x', ignore: ['--color-foundations-'] },
    obfuscateClasses: { prefix: 'ui-', ignore: ['keep-*'] },
  },
}

export default config
```

| Option   | Default (variables / classes) | Meaning                                                                                  |
| -------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
| `prefix` | `--s` / `sys-`                | Prefix of every generated name.                                                          |
| `suffix` | `''`                          | Suffix of every generated name.                                                          |
| `method` | `sequential`                  | `sequential` (short names, best compression), `random`, `hash`, or `none` (prefix only). |
| `length` | `4`                           | Name length for `random` and `hash`.                                                     |
| `ignore` | `[]`                          | Names to keep: exact (`'foo'`) or a family (`'foo-'` or `'foo*'`).                       |
| `seed`   | `'systhema'`                  | Seed for `random`, so builds are reproducible.                                           |

> [!TIP]
> Keep `method: 'sequential'`. Its names (`--sa`, `sys-a`) compress very well. `random` and `hash` make names unguessable but enlarge the compressed download, even though the raw file shrinks.

### Variable obfuscation

`obfuscateVariables` renames every custom property Systhema defines, at the place it is defined and in every `var()` that references it, while the CSS is generated (`--color-foundations-bg` becomes something like `--sa`). It is a pure CSS transform, independent of your bundler, and safe on every project, Payload sites included. It leaves alone the `--screen-*` and `--breakpoint-*` variables that the [`vw` utilities](https://docs.systhema.app/hu/styling/viewport-units.md) read at runtime, Tailwind's own variables, and anything in `ignore`.

Names written outside Systhema's stylesheet still say `--color-foundations-bg` after the build, and without help would resolve to nothing: a border disappears, a heading turns black. Two places do this:

1. Your own CSS that references a Systhema variable by name.
2. Inline styles stored in the CMS, such as a [text state](https://docs.systhema.app/hu/payload/editor/text-states.md) whose CSS is `color: var(--color-foundations-decorative)`.

Run `systhema-core obfuscate` after `next build` (see [Next.js setup](#nextjs-setup)) and it repairs what it can see: it rewrites every remaining `var(--name)` in the emitted CSS, HTML, RSC payloads and JavaScript, re-points your own overrides (`:root { --color-foundations-line: … }`) at the new names, and prints a warning listing every reference it could not resolve, with the `ignore` entry to add.

It cannot see content authored after the build. A page published tomorrow renders its inline style from the database with the original name.

> [!IMPORTANT]
> Add every variable name your CMS content references to `ignore`, exactly (`'--color-foundations-decorative'`) or as a family (`'--color-foundations-'`).

Two habits follow from this:

- Write readable token names everywhere, in project CSS and in inline styles. Don't replace a token with its literal value so it "survives" the build; that only breaks theming.
- Don't check for a variable by name on an optimized build. Searching the production CSS for `--color-foundations-bg`, or reading it with `getComputedStyle(el).getPropertyValue('--color-foundations-bg')`, finds nothing because the name is gone. Check the computed value (`getComputedStyle(el).backgroundColor`), or check a development build.

## Class obfuscation

`obfuscateClasses` configures a post-build step, `systhema-core obfuscate`, which renames Systhema's front-end class names to short tokens (`text-h1` becomes `sys-a`) consistently across the compiled CSS, the prerendered HTML, the RSC payloads and the client JavaScript. It renames only class names in Systhema's own set, and only where they appear as classes, so URLs, data attributes and other strings stay untouched. The flag on its own does nothing: without the post-build step there is no rename and no error.

> [!WARNING]
> Class obfuscation is for fully prerendered sites only. The step rewrites the prerendered output but not the server bundle, so a page rendered after the build (ISR revalidation, on-demand revalidation, `force-dynamic`, or an editor publishing in Payload) is produced with the original class names against a stylesheet that only knows the new ones, and loses all styling. Nothing fails at build time; the page breaks the first time content changes.

So `systhema-core obfuscate` refuses to run the class pass when it finds anything that renders at runtime: a Payload project, ISR routes, on-demand dynamic routes, or a `force-dynamic` or `revalidate` export. It exits with a non-zero code and lists the reasons. A site you know is fully prerendered can override the check with `--allow-dynamic`. Variable obfuscation has no such restriction.

### Next.js setup

Add a separate build script; the regular `build` stays as it is:

```json title="package.json"
{
  "scripts": {
    "build:obfuscated": "next build && cross-env NODE_ENV=production systhema-core obfuscate"
  }
}
```

`systhema-core obfuscate [dir]` works on `.next` by default and reads `optimization` from your `systhema.config`. It does nothing unless `obfuscateClasses` or `obfuscateVariables` is on and `NODE_ENV` is `production`, which `cross-env` guarantees here (add it with `pnpm add -D cross-env`). Wire the script whenever either obfuscator is on: with `obfuscateVariables` alone it still repairs variable references and reports orphans. Running it twice is harmless.

[`systhema doctor`](https://docs.systhema.app/hu/cli/doctor.md) warns when `obfuscateClasses` is on without this script, and when it is on in a Payload project at all.

### Classes that keep their names

Some classes must stay literal because something other than the stylesheet produces or looks them up, and Systhema keeps them automatically:

- class families that components assemble at runtime (`bg-layout-*`, `button-*`, `card-*`, `accordion-*`, `column-span-*`, `color-*`, the posts listing and archive filter families, and the animation classes);
- classes Systhema's listeners find by selector (`menu-*` and the header toggles, `accordion*`, `parallax`, `archive-listing`) and the state classes they toggle (`is-open`, `is-active`, `is-scrolling-down`, `scroll-on-top`);
- classes written by bundled libraries: Swiper (`swiper-slide`, `swiper-wrapper`) and the cookie banner (`cc--systhema` and its parts).

Add your own to `obfuscateClasses.ignore`: any class you build from strings, query with `querySelector`, or toggle from a script.

Two more things to know:

- The rename map comes from the stylesheets the prerendered homepage links. A route that loads its own stylesheet and styles a Systhema class there (`.text-h1 span { … }`) keeps the old selector while its markup is renamed, so that rule stops applying. Move such rules into the shared stylesheet, or add the classes to `ignore`.
- In client JavaScript a string has no syntactic "class position", so a Systhema class that is also an HTML tag name (`article`, `header`, `footer`, `menu`) is protected by recognising JSX element types. A third-party script that passes such a bare tag name as data, as in `document.createElement('footer')`, could have it renamed.

The Payload Admin is never touched: its classes are not in the set.

## Related

- [systhema-core](https://docs.systhema.app/hu/cli/systhema-core.md) (the `obfuscate` command)
- [systhema doctor](https://docs.systhema.app/hu/cli/doctor.md)
- [CSS variables](https://docs.systhema.app/hu/styling/css-variables.md)
- [systhema.config reference](https://docs.systhema.app/hu/reference/config.md#optimization)
