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.

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