# sitelo > Zero-config static site generation powered by Vite and vite-plugin-html-pages The whole of https://sitelo.dev as Markdown: the guides, the component reference and the examples, in the order the site lists them. https://sitelo.dev/llms.txt is the shorter index, and each page below also has a copy of its own at the URL above it. --- URL: https://sitelo.dev/docs # Getting started > Install sitelo and build your first static site. sitelo is a zero-config static site generator powered by Vite. Install one package, write functions that return HTML, and run `sitelo build`. ## Install `shell` ```bash npm install -D sitelo ``` Requires Node 20.19+ (or 22.12+). Vite is bundled — you do not install it separately. ## Your first page Create `src/index.ht.js` (or `.ht.jsx`). [ht.js](https://ht.js.org) is recommended: `src/index.ht.js — Template literal` ```javascript export default () => ` My website

Hello world

` ``` `src/index.ht.js — ht.js (recommended)` ```javascript import { html, head, title, body, h1 } from 'javascript-to-html' export default () => html({ lang: 'en' }, head(title('My website')), body(h1('Hello world')) ) ``` `src/index.ht.jsx — JSX` ```jsx export default function Home() { return ( My website

Hello world

) } ``` ## Run `shell` ```bash sitelo # dev server sitelo build # write dist/ sitelo preview # preview the build ``` That emits `dist/index.html` (with `` added for you) plus a default `404.html`. ## Next - [Writing pages](https://sitelo.dev/docs/pages) — template strings, JSX, structured modules - [Routing](https://sitelo.dev/docs/routing) — file-based routes and `generateStaticParams` - [Data loading](https://sitelo.dev/docs/data) — `data()` and `fetchWithCache` - [Assets & styling](https://sitelo.dev/docs/assets) — frontend JS/CSS compiled by Vite (`src/js`, `src/css`) - [Configuration](https://sitelo.dev/docs/configuration) — `sitelo.config.js` and Vite options - [Build with AI](https://sitelo.dev/docs/build-with-ai) — `llms.txt`, project rules, and agent tips --- URL: https://sitelo.dev/docs/pages # Writing pages > How to author sitelo pages — template literals, ht.js (recommended), or JSX. Any file ending in a page extension is a page. Default extensions: `.ht.js`, `.html.js`, and the TypeScript / JSX variants (`.ht.ts`, `.ht.tsx`, …). If output starts with ``, sitelo prepends `` automatically. Prefer [ht.js](https://ht.js.org) (`javascript-to-html`) for markup — tag functions that return strings, with no templating engine or React runtime. ## A function returning HTML `page.ht.js — Template literal` ```javascript export default ({ params, data, dev }) => `

Hello

` ``` `page.ht.js — ht.js (recommended)` ```javascript import { html, body, h1 } from 'javascript-to-html' export default ({ params, data, dev }) => html( body(h1('Hello')) ) ``` `page.ht.jsx — JSX` ```jsx export default function Page({ params, data, dev }) { return (

Hello

) } ``` JSX files use `*.ht.jsx` / `*.ht.tsx` and need `react` + `react-dom` installed. See [JSX limitations](https://sitelo.dev/docs/pages#jsx-limitations) below — sitelo turns JSX into static HTML at build time; it is not a React app in the browser. ## A plain string `page.ht.js` ```javascript export default `

Static as it gets

` ``` ## A structured module Keep `render`, `data`, and `generateStaticParams` together: `page.ht.js — Template literal` ```javascript export default { generateStaticParams: () => [{ slug: 'hello' }], data: ({ params }) => ({ title: params.slug }), render: ({ data }) => `

${data.title}

`, } ``` `page.ht.js — ht.js (recommended)` ```javascript import { html, body, h1 } from 'javascript-to-html' export default { generateStaticParams: () => [{ slug: 'hello' }], data: ({ params }) => ({ title: params.slug }), render: ({ data }) => html( body(h1(data.title)) ), } ``` `page.ht.jsx — JSX` ```jsx export default { generateStaticParams: () => [{ slug: 'hello' }], data: ({ params }) => ({ title: params.slug }), render: ({ data }) => (

{data.title}

), } ``` ## JSX limitations sitelo can render `.ht.jsx` / `.ht.tsx` pages with `react-dom/server` so you can write familiar JSX syntax. That is an authoring convenience — not a React application. React is built for the browser: a component tree that mounts, hydrates, and responds to events. sitelo never ships that runtime with your pages. At build time it renders your JSX to an HTML string, writes the file, and stops. There is no client React bundle, no hydration, and no virtual DOM waiting in the browser. Because of that: - Event handlers (`onClick`, `onChange`, …) do nothing in the finished site — nothing is listening. The dev server warns if it finds them. - Hooks (`useState`, `useEffect`, …) are not part of the shipped page. They may run once during the build render; they do not keep state in the browser. - Browser-only APIs (`window`, `document`) are unavailable while the page is rendered at build time. - `dangerouslySetInnerHTML` works for injecting HTML strings; interactivity still needs a separate client ` ` ``` `src/blog/[slug].ht.js — ht.js (recommended)` ```javascript import { html, body, article, script } from 'javascript-to-html' import { island } from 'sitelo/islands' export default ({ params }) => html( body( article('…static content…'), island('comments', { postId: params.slug }, '

Loading comments…

'), script({ type: 'module', src: '/islands.js' }), ), ) ``` `src/blog/[slug].ht.jsx — JSX` ```jsx import { island } from 'sitelo/islands' export default function Post({ params }) { return (
…static content…
{island('comments', { postId: params.slug }, '

Loading comments…

')}

Hello

` ``` `src/index.ht.js — ht.js (recommended)` ```javascript import { html, head, title, link, script, body, h1, button } from 'javascript-to-html' export default () => html({ lang: 'en' }, head( title('My site'), link({ rel: 'stylesheet', href: '/css/styles.css' }), script({ type: 'module', src: '/js/main.js' }), ), body( h1('Hello'), button({ id: 'count' }, '0'), ), ) ``` `src/index.ht.jsx — JSX` ```jsx export default function Home() { return ( My site
Home

Hello

Only this region is indexed.

` ``` `src/index.ht.js — ht.js (recommended)` ```javascript import { html, head, title, link, script, body, header, a, div, main, h1, p, } from 'javascript-to-html' export default () => html({ lang: 'en' }, head( title('My site'), link({ rel: 'stylesheet', href: '/styles.css' }), script({ type: 'module', src: '/main.js' }), ), body( header( a({ href: '/' }, 'Home'), div({ id: 'search' }), ), main({ 'data-pagefind-body': '' }, h1('Hello'), p('Only this region is indexed.'), ), ), ) ``` `src/index.ht.jsx — JSX` ```jsx export default function Home() { return ( My site ` ``` `src/index.ht.js — ht.js (recommended)` ```javascript import { html, head, title, link, body, h1, p, script } from 'javascript-to-html' import { island } from 'sitelo/islands' export default () => html({ lang: 'en' }, head( title('Server islands demo'), link({ rel: 'stylesheet', href: '/styles.css' }), ), body( h1('Static page, live island'), p('This HTML was built once. The box below is filled at request time.'), island( 'time', { label: 'Right now' }, '

Loading server time…

', ), script({ type: 'module', src: '/islands.js' }), ), ) ``` `src/index.ht.jsx — JSX` ```jsx import { island } from 'sitelo/islands' export default function Home() { return ( Server islands demo

Static page, live island

This HTML was built once. The box below is filled at request time.

{island( 'time', { label: 'Right now' }, '

Loading server time…

', )}