# 2. Put state on the server

Stop Hello World, then run:

```sh
npm run counter
```

Open http://localhost:8181 in two tabs. Click either button. Both counters should change.


```tsx
import { action, defineApp, page, state } from 'redweb';

@page('/', { shared: true })
class CounterPage {
    @state() count = 0;

    @action()
    increment() { this.count += 1; }

    render() {
        return <main>
            <h1>A counter owned by the server</h1>
            <button rw-click="increment">Count {this.count}</button>
        </main>;
    }
}

const app = defineApp({ pages: [CounterPage] });

await app.run();
```


`@state()` marks a value that Redweb should observe. `@action()` exposes one server method to this page. `rw-click="increment"` connects the button to that method. `{this.count}` is enough: Redweb creates the update binding when it renders the expression.

`shared: true` means visitors share this page instance **inside one server process**. It does not save the counter to disk or synchronize multiple machines.

Imagine two people looking through different windows at the same scoreboard. The scoreboard lives inside the building; each window displays what the server says.

## What changes for a game?

The counter can use Redweb's built-in page-action socket. A game benefits from its own explicit vocabulary: `join`, `move`, and `resume` at `/match`. We will keep TSX for the HTML, but use a routed game socket for those messages. We will not bolt an `action` field onto a generic message handler.

**Checkpoint:** changing server state changes both views. Reloading is not required; restarting the server resets this intentionally in-memory counter.
