Server-Islands
Auf dieser Seite
Manchmal braucht ein Bereich einer ansonsten statischen Seite frische Daten pro Anfrage — Kommentare unter einem gecachten Blogbeitrag, ein Lagerbestand auf einer Produktseite. Server-Islands halten die Seite statisch und rendern genau diesen Bereich auf einem Server, wenn die Seite aufgerufen wird.
1. Die Island schreiben
Eine Island ist ein Fragment-Modul unter src/islands/ — eine schlichte .js- oder .ts-Datei (nicht .ht.js, denn Islands sind Fragmente, keine Seiten). Dieselbe Idee wie überall in sitelo: eine Funktion, die HTML zurückgibt.
export default async function comments({ props, request }) {
const comments = await fetchComments(props.postId)
return `<ul>${comments.map((c) => `<li>${c.text}</li>`).join('')}</ul>`
}Sie erhält { name, props, request } und muss einen HTML-String zurückgeben. Island-Module laufen ausschließlich auf dem Server — nicht referenzierter Code unter src/ gelangt nie in den Browser.
2. In einer Seite platzieren
Importiere island() aus sitelo/islands. Der statische Build liefert das Fallback-HTML aus; die Props stecken im Platzhalter, halte sie also klein und frei von Geheimnissen.
import { island } from 'sitelo/islands'
export default ({ params }) => `
<html>
<body>
<article>…statischer Inhalt…</article>
${island('comments', { postId: params.slug }, '<p>Kommentare werden geladen…</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('…statischer Inhalt…'),
island('comments', { postId: params.slug }, '<p>Kommentare werden geladen…</p>'),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Post({ params }) {
return (
<html>
<body>
<article>…statischer Inhalt…</article>
{island('comments', { postId: params.slug }, '<p>Kommentare werden geladen…</p>')}
<script type="module" src="/islands.js" />
</body>
</html>
)
}3. Den Client-Loader ergänzen
Ein winziges Skript holt jedes gerenderte Fragment und tauscht es ein. Es läuft durch die normale Asset-Pipeline, ein schlichter src/islands.js-Einstiegspunkt genügt also:
import { mountIslands } from 'sitelo/islands/client'
mountIslands()In sitelo (dev) und sitelo preview funktioniert das bereits — beide liefern Islands unter /_sitelo/islands/<name> aus src/islands/ aus. Preview lädt native .js- / .mjs-Module (wie ein Node-Host); TypeScript-Islands werden in der Entwicklung über Vite unterstützt.
Produktion
Dein statischer Hoster liefert die Seiten weiterhin aus. Hänge einen kleinen Handler dort ein, wo du Server-Code ausführst — Node, Serverless oder eine Edge-Funktion — und er rendert dieselben Island-Module. Für einen vollständigen Durchgang mit lauffähigem Node-Host sowie Netlify- und Vercel-Vorlagen siehe das Server-Islands-Beispiel.
// z. B. ein Node-Server oder eine Serverless-/Edge-Funktion
import { createIslandsHandler } from 'sitelo/islands/server'
const handleIslands = createIslandsHandler({
islands: {
comments: () => import('./src/islands/comments.js'),
},
})
// Web Request → Response | null (null = keine Island-Anfrage)
export default { fetch: (request) => handleIslands(request) }Mit schlichtem Node-http oder express nimmst du stattdessen createIslandsNodeHandler(options) — dieselben Optionen, Signatur (req, res, next). Jedes .js- / .mjs-Modul unter src/islands/ lässt sich mit createIslandsFromDirectory automatisch verdrahten. Holt der Loader von einem anderen Ursprung oder Pfad, übergib mountIslands({ endpoint: 'https://api.example.com/islands' }) und stimme es mit der endpoint-Option des Handlers ab.
Stabilitäts-Checkliste
island()/mountIslands()/createIslandsHandler/createIslandsNodeHandler— öffentliche API, gilt als stabilsiteloundsitelo previewliefern beide/_sitelo/islandsaussrc/islands/auscreateIslandsFromDirectory— dieselbe Zuordnung für Node-Hosts und Preview (nativ.js/.mjs/.cjs)- Host-Vorlagen in Server-Islands-Beispiel: Node-
server.js, Netlify-Funktion + Rewrite, Vercel-Serverless + Rewrite - Props bleiben klein und frei von Geheimnissen (GET-Query-String) — das ist Absicht; hole Geheimnisse innerhalb der Island auf dem Server
- Props kommen vom Client — validiere sie, oder signiere sie mit
SITELO_ISLANDS_SECRET
Ladestrategien
Standardmäßig lädt jede Island, sobald die Seite lädt; eine Seite mit acht Islands stellt also acht gleichzeitige Anfragen während des ersten Aufbaus. Übergib when, um die nicht sofort sichtbaren zu verzögern.
// Lädt, sobald die Seite lädt — der Standard.
island('cart', { id }, '<p>…</p>')
// Wartet auf einen Idle-Callback.
island('recommendations', { id }, '<p>…</p>', { when: 'idle' })
// Wartet, bis das Element sichtbar wird.
island('comments', { postId }, '<p>Kommentare werden geladen…</p>', {
when: 'visible',
rootMargin: '400px', // beginnt 400px früher zu laden
})'load'(Standard) — sofort, zusammen mit allen anderen Islands'idle'— perrequestIdleCallback(mit Timeout als Rückfall)'visible'— sobald sie ins Bild scrollt, per IntersectionObserver
rootMargin gilt nur für 'visible' und steht standardmäßig auf '200px'. Außerdem laufen Islands in einen Timeout, statt ewig zu drehen:
mountIslands({
timeout: 5000, // pro Island; 0 deaktiviert. Standard 10000
rootMargin: '300px', // Standard für Islands mit `when: 'visible'`
})mountIslands() löst auf, sobald die sofortigen Islands fertig sind — verzögerte laden später von selbst und werden bewusst nicht abgewartet. Eine fehlgeschlagene oder abgelaufene Island behält ihr Fallback-HTML.
Props sind nicht vertrauenswürdig
Island-Props kommen vom Client. Sie stecken in der Seite, werden mit der Anfrage zurückgeschickt, und jede Person kann sie vorher bearbeiten:
GET /_sitelo/islands/profile?props={"userId":"someone-else"}Behandle die Props, die deine Island empfängt, genau wie einen Query-Parameter — validiere sie, und nutze sie nie, um Daten nachzuschlagen, die die betrachtende Person nicht ohnehin sehen darf.
Wählen Props privilegierte Daten aus, dann signiere sie. Setze ein Secret, und sitelo signiert jeden Platzhalter zur Build-Zeit und weist alles andere mit einem 403 ab:
SITELO_ISLANDS_SECRET=$(openssl rand -hex 32) sitelo buildDieselbe Variable lesen sitelo, sitelo preview und createIslandsHandler, sodass Entwicklung, Preview und Produktion übereinstimmen. Gib deinem Produktions-Host dasselbe Secret. Lieber im Code setzen?
import { configureIslands } from 'sitelo/islands'
configureIslands({ secret: process.env.MY_SECRET })Signaturen sind HMAC-SHA256 über den Island-Namen und ihre Props, eine für eine Island ausgestellte Signatur lässt sich also nicht gegen eine andere wiederverwenden. Das Signieren belegt, dass die Props aus deinem Build stammen — es verbirgt sie nicht, sie müssen also weiterhin frei von Geheimnissen sein. Ohne Secret werden Props unverändert akzeptiert, und ihre Validierung liegt ganz bei deinem Island-Modul.
Gut zu wissen
- Kein Island-Endpunkt ausgerollt? Dann bleibt schlicht das Fallback-HTML stehen — die Seiten verhalten sich gutmütig.
- Anfragen sind
GETmit den Props im Query-String, die Antworten sind also cachebar — setze diecacheControl-Option des Handlers, wenn ein CDN Fragmente kurz halten soll. - Während des Ladens trägt der Platzhalter
data-sitelo-island-state="loading"(danachloadedodererror) — praktisch fürs CSS.