Routing
Routes come straight from the filesystem under src/.
src/
index.ht.js → /
about.ht.js → /about
blog/
index.ht.js → /blog
[slug].ht.js → /blog/:slug
docs/
[...path]?.ht.js → /docs, /docs/a, /docs/a/b, ...
(admin)/
users.ht.js → /users
404.ht.js → dist/404.htmlRoute table
| Feature | File | URL |
|---|---|---|
| Static | index.ht.js | / |
| Nested | blog/index.ht.js | /blog |
| Dynamic | blog/[slug].ht.js | /blog/my-post |
| Multiple params | blog/[year]/[slug].ht.js | /blog/2026/my-post |
| Catch-all | docs/[...path].ht.js | /docs/api/auth |
| Optional catch-all | docs/[...path]?.ht.js | /docs + deeper |
| Route groups | (admin)/users.ht.js | /users |
More specific routes win: static beats dynamic, dynamic beats catch-alls. Two files generating the same URL is a build error.
generateStaticParams
Dynamic routes declare which pages to emit at build time. In sitelo (dev), dynamic routes still render on demand without listing every param.
export function generateStaticParams() {
return [
{ slug: 'hello-world' },
{ slug: 'my-first-post' },
]
}
export default ({ params }) => `
<html><body><h1>${params.slug}</h1></body></html>
`import { html, body, h1 } from 'javascript-to-html'
export function generateStaticParams() {
return [
{ slug: 'hello-world' },
{ slug: 'my-first-post' },
]
}
export default ({ params }) =>
html(
body(h1(params.slug))
)export function generateStaticParams() {
return [
{ slug: 'hello-world' },
{ slug: 'my-first-post' },
]
}
export default function Post({ params }) {
return (
<html>
<body>
<h1>{params.slug}</h1>
</body>
</html>
)
}Values can be strings, numbers, or booleans — they are stringified and URL-encoded. Catch-all params accept arrays ({ path: ['a', 'b'] }) or slash-separated strings ({ path: 'a/b' }).
A dynamic page that generates zero routes prints a warning so it cannot silently vanish from your site.