---
title: Plan at compile time, render anywhere
description: The Markless compiler decides state, updates, and event code before anything runs, and the same compiled component renders in the browser, on a server, at build time, or in tests.
sidebar: { label: The big idea }
---

Last page put compile-time planning first. Here is what the compiler decides, and where its output can run.

## Decided before anything runs

**Misconception: a framework must run your component to learn what it does.** Markless reads it instead. At build time, the compiler works out three things from `Counter.tsrx`:

1. **The state.** `count` becomes the graph node `state:count`.
2. **The updates.** The text `Count {count}` becomes one planned text write.
3. **The event code.** The click becomes its own *symbol*: a small module that the runtime loads by ID.

One compile also emits two render modules: one for the browser and one for a server.

Pick an environment. Then click **Count**.

<HowBigIdeaFigure />

Notice that the planned outputs stay the same. The body runs once, and only the first click loads the click code.

## Rendered where you need it

| Environment | How you call it | Notes |
| --- | --- | --- |
| Browser | `render(App, { target })` from `@markless/core` | No server needed |
| Server | `renderToString()` from `@markless/core`, or `renderToStream()` from `@markless/web` | HTML plus the plan as JSON |
| Multi-page app | `@markless/router` | File routes on Nitro, which deploys to many targets |
| Build time | `MARKLESS_PRERENDER=1` in the demos | Preview, not stable |
| Tests | `render()` and `renderSSR()` from `@markless/vitest-browser` | `renderSSR()` renders in Node, then loads the HTML in the test browser |
| Native hosts | UIKit and AppKit, see [native targets](/how-it-works/native-targets) | Proofs, not wired to the compiler yet |

A browser-only app needs no server:

```ts main.ts
import { render } from '@markless/core';
import App from './App.tsrx';

await render(App, { target: document.querySelector('#app')! });
```

## What stays the same

- The component body runs once, to set up the page. A browser test and a server test both count 1 run.
- No handler runs before its event. In the browser test, no symbol loads at mount. The first click loads one.
- After a write, only the text, attributes, and list rows that read the changed state update.

The environments differ only in delivery. On a server, the HTML carries the plan as JSON plus a small inline script. *Hydration* means the browser runs components again over server HTML. Markless skips it, and the browser does not run the body to start the page.

:::tip[Why does my server page ship no script?]
If the page has no handlers, behaviors, storage, or async work, `renderToString()` sends only the HTML in a container `div`.
:::

**Next:** What exactly does the compiler emit for `Counter.tsrx`? [The compiler →](/how-it-works/the-compiler)
