Server islands
Sometimes one region of an otherwise-static page needs fresh, per-request data — comments under a cached blog post, a stock badge on a product page. Server islands keep the page static and render just that region on a server when the page is viewed. Experimental: the API may change.
1. Write the island
An island is a fragment module under src/islands/ — a plain .js or .ts file (not .ht.js, because islands are fragments, not pages). Same idea as everywhere else in sitelo: a function that returns HTML.
export default async function comments({ props, request }) {
const comments = await fetchComments(props.postId)
return `<ul>${comments.map((c) => `<li>${c.text}</li>`).join('')}</ul>`
}It receives { name, props, request } and must return an HTML string. Island modules are server-only — unreferenced code under src/ never ships to the browser.
2. Place it in a page
Import island() from sitelo/islands. The static build ships the fallback HTML; props are embedded in the placeholder, so keep them small and non-secret.
import { island } from 'sitelo/islands'
export default ({ params }) => `
<html>
<body>
<article>…static content…</article>
${island('comments', { postId: params.slug }, '<p>Loading comments…</p>')}
<script type="module" src="/islands.js"></script>
</body>
</html>
`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 }, '<p>Loading comments…</p>'),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Post({ params }) {
return (
<html>
<body>
<article>…static content…</article>
{island('comments', { postId: params.slug }, '<p>Loading comments…</p>')}
<script type="module" src="/islands.js" />
</body>
</html>
)
}3. Add the client loader
A tiny script fetches each rendered fragment and swaps it in. It goes through the normal asset pipeline, so a plain src/islands.js entry is all you need:
import { mountIslands } from 'sitelo/islands/client'
mountIslands()In dev this already works — sitelo dev serves islands at /_sitelo/islands/<name> straight from src/islands/.
Production
Your static host keeps serving the pages. Mount a small handler wherever you run server code — Node, serverless, or an edge function — and it renders the same island modules. For a full walkthrough with a runnable Node host, see the Server islands example.
// e.g. a Node server, or a serverless/edge function
import { createIslandsHandler } from 'sitelo/islands/server'
const handleIslands = createIslandsHandler({
islands: {
comments: () => import('./src/islands/comments.js'),
},
})
// Web Request → Response | null (null = not an island request)
export default { fetch: (request) => handleIslands(request) }On plain Node http or express, use createIslandsNodeHandler(options) instead — same options, (req, res, next) signature. If the loader fetches from a different origin or path, pass mountIslands({ endpoint: 'https://api.example.com/islands' }) and match it with the handler’s endpoint option.
Good to know
- No island endpoint deployed? The fallback HTML simply stays — pages degrade gracefully.
- Requests are
GETwith props in the query string, so responses are cacheable — set the handler’scacheControloption if you want a CDN to hold fragments briefly. - While loading, the placeholder carries
data-sitelo-island-state="loading"(thenloadedorerror) — handy for CSS.