|
| 1 | +# xote · SSR template |
| 2 | + |
| 3 | +A server-rendered starter for [xote](https://github.com/brnrdog/xote) apps. Renders HTML on a Node server, then hydrates on the client so reactivity takes over without re-rendering. |
| 4 | + |
| 5 | +## Stack |
| 6 | + |
| 7 | +- ReScript v12 (JSX transform: `XoteJSX`) |
| 8 | +- xote ^6.2 — uses `SSR.renderToString`, `SSRState`, and `Hydration` |
| 9 | +- rescript-signals |
| 10 | +- Vite 7 — runs in middleware mode for dev, and produces both client and SSR bundles for production |
| 11 | +- Tailwind CSS v4 (`@tailwindcss/vite`) |
| 12 | + |
| 13 | +## Layout |
| 14 | + |
| 15 | +``` |
| 16 | +. |
| 17 | +├── index.html # template with <!--app-html--> and <!--app-state--> placeholders |
| 18 | +├── server.mjs # Node HTTP server (Vite middleware in dev, static + SSR in prod) |
| 19 | +├── vite.config.mjs |
| 20 | +├── rescript.json |
| 21 | +├── package.json |
| 22 | +└── src/ |
| 23 | + ├── styles.css |
| 24 | + ├── Counter.res # uses SSRState.signal so state survives hydration |
| 25 | + ├── Page.res # landing page composition |
| 26 | + ├── App.res # shared `view` thunk used by server and client |
| 27 | + ├── Server.res # exports render() → { html, stateScript } |
| 28 | + └── Client.res # imports styles, calls Hydration.hydrateById |
| 29 | +``` |
| 30 | + |
| 31 | +## How it fits together |
| 32 | + |
| 33 | +1. `server.mjs` reads `index.html` and (in dev) hands it to Vite for asset/CSS transformation. |
| 34 | +2. It imports `Server.res` and calls `render()`, which uses `SSR.renderToString(App.view)` to produce HTML and `SSRState.generateScript()` to produce a `<script>` tag carrying initial signal values. |
| 35 | +3. The placeholders `<!--app-html-->` and `<!--app-state-->` in `index.html` are replaced with those two strings. |
| 36 | +4. The browser loads `Client.res.mjs`, which calls `Hydration.hydrateById(App.view, "root")`. xote walks the existing DOM, attaches reactive bindings, and reads back the serialized `SSRState` so signals start with the same values the server used. |
| 37 | + |
| 38 | +## Scripts |
| 39 | + |
| 40 | +| Script | What it does | |
| 41 | +| --- | --- | |
| 42 | +| `npm run res:dev` | Watches and compiles ReScript on save. Run during development. | |
| 43 | +| `npm run res:build` | One-shot ReScript build. | |
| 44 | +| `npm run res:clean` | Removes ReScript build artifacts. | |
| 45 | +| `npm run dev` | Runs `node server.mjs` with Vite in middleware mode. | |
| 46 | +| `npm run build` | Builds the client bundle and the SSR bundle. | |
| 47 | +| `npm run build:client` | Just the client bundle (`dist/client/`). | |
| 48 | +| `npm run build:ssr` | Just the SSR bundle (`dist/server/Server.res.js`). | |
| 49 | +| `npm run start` | Runs the server in production mode against the built bundles. | |
| 50 | + |
| 51 | +## Develop |
| 52 | + |
| 53 | +```bash |
| 54 | +npm install |
| 55 | +npm run res:dev |
| 56 | +# in another terminal: |
| 57 | +npm run dev |
| 58 | +``` |
| 59 | + |
| 60 | +Open http://localhost:3000. The page is server-rendered (view source — the counter HTML is already there) and becomes interactive after hydration. |
| 61 | + |
| 62 | +## Build & run in production |
| 63 | + |
| 64 | +```bash |
| 65 | +npm run res:build |
| 66 | +npm run build |
| 67 | +npm run start |
| 68 | +``` |
| 69 | + |
| 70 | +`server.mjs` defaults to port 3000; override with `PORT=8080 npm run start`. |
0 commit comments