Writing pages
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 <html>, sitelo prepends <!DOCTYPE html> automatically. Prefer ht.js (javascript-to-html) for markup — tag functions that return strings, with no templating engine or React runtime.
1. A function returning HTML
export default ({ params, data, dev }) => `
<html>
<body><h1>Hello</h1></body>
</html>
`import { html, body, h1 } from 'javascript-to-html'
export default ({ params, data, dev }) =>
html(
body(h1('Hello'))
)export default function Page({ params, data, dev }) {
return (
<html>
<body><h1>Hello</h1></body>
</html>
)
}JSX files use *.ht.jsx / *.ht.tsx and need react + react-dom installed. See JSX limitations below — sitelo turns JSX into static HTML at build time; it is not a React app in the browser.
2. A plain string
export default `<html><body><h1>Static as it gets</h1></body></html>`3. A structured module
Keep render, data, and generateStaticParams together:
export default {
generateStaticParams: () => [{ slug: 'hello' }],
data: ({ params }) => ({ title: params.slug }),
render: ({ data }) => `<html><body><h1>${data.title}</h1></body></html>`,
}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))
),
}export default {
generateStaticParams: () => [{ slug: 'hello' }],
data: ({ params }) => ({ title: params.slug }),
render: ({ data }) => (
<html>
<body><h1>{data.title}</h1></body>
</html>
),
}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. dangerouslySetInnerHTMLworks for injecting HTML strings; interactivity still needs a separate client<script>.
For clicks, forms, and other client behavior, link a normal script through the asset pipeline, or use server islands for request-time HTML. Prefer ht.js or template literals when you don’t need JSX syntax — no React install, and the model matches “HTML at build time.”
Render context
Every page function receives one argument:
params
Route params for this page (Record<string, string | string[]>).
data
Whatever your data() function returned.
page
Route metadata (routePath, relativePath, …).
dev
true in the dev server, false at build time.