Island server
In questa pagina
A volte una sola regione di una pagina per il resto statica ha bisogno di dati freschi a ogni richiesta — i commenti sotto un articolo in cache, un badge di disponibilità su una pagina prodotto. Le island server tengono la pagina statica e renderizzano solo quella regione su un server quando la pagina viene visitata.
1. Scrivi l’island
Un’island è un modulo-frammento sotto src/islands/ — un semplice file .js o .ts (non .ht.js, perché le island sono frammenti, non pagine). Stessa idea che vale ovunque in sitelo: una funzione che restituisce 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>`
}Riceve { name, props, request } e deve restituire una stringa HTML. I moduli island vivono solo sul server — il codice non referenziato sotto src/ non arriva mai al browser.
2. Mettila in una pagina
Importa island() da sitelo/islands. La build statica pubblica l’HTML di riserva; le props sono incorporate nel segnaposto, quindi tienile piccole e non segrete.
import { island } from 'sitelo/islands'
export default ({ params }) => `
<html>
<body>
<article>…contenuto statico…</article>
${island('comments', { postId: params.slug }, '<p>Caricamento dei commenti…</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('…contenuto statico…'),
island('comments', { postId: params.slug }, '<p>Caricamento dei commenti…</p>'),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Post({ params }) {
return (
<html>
<body>
<article>…contenuto statico…</article>
{island('comments', { postId: params.slug }, '<p>Caricamento dei commenti…</p>')}
<script type="module" src="/islands.js" />
</body>
</html>
)
}3. Aggiungi il loader client
Un minuscolo script scarica ogni frammento renderizzato e lo scambia al suo posto. Passa dalla normale pipeline delle risorse, quindi basta un semplice punto di ingresso src/islands.js:
import { mountIslands } from 'sitelo/islands/client'
mountIslands()Con sitelo (sviluppo) e sitelo preview funziona già — entrambi servono le island su /_sitelo/islands/<nome> a partire da src/islands/. La preview carica moduli .js / .mjs nativi (come un host Node); in sviluppo le island TypeScript sono supportate tramite Vite.
Produzione
Il tuo host statico continua a servire le pagine. Monta un piccolo handler ovunque tu esegua codice server — Node, serverless o una funzione edge — e renderizzerà gli stessi moduli island. Per una guida completa con un host Node eseguibile più gli stub per Netlify e Vercel, vedi l’esempio sulle island server.
// ad esempio un server Node, oppure una funzione serverless/edge
import { createIslandsHandler } from 'sitelo/islands/server'
const handleIslands = createIslandsHandler({
islands: {
comments: () => import('./src/islands/comments.js'),
},
})
// Request web → Response | null (null = non è una richiesta island)
export default { fetch: (request) => handleIslands(request) }Su http di Node o su express, usa invece createIslandsNodeHandler(options) — stesse opzioni, firma (req, res, next). Collega automaticamente ogni modulo .js / .mjs sotto src/islands/ con createIslandsFromDirectory. Se il loader scarica da un’origine o da un percorso diverso, passa mountIslands({ endpoint: 'https://api.example.com/islands' }) e fallo combaciare con l’opzione endpoint dell’handler.
Checklist di stabilità
island()/mountIslands()/createIslandsHandler/createIslandsNodeHandler— API pubblica, considerata stabilesiteloesitelo previewservono entrambi/_sitelo/islandsa partire dasrc/islands/createIslandsFromDirectory— la stessa mappa per gli host Node e per la preview (.js/.mjs/.cjsnativi)- Stub di host nell’esempio sulle island server:
server.jsper Node, function + rewrite per Netlify, serverless + rewrite per Vercel - Le props restano piccole e non segrete (query string di una GET) — è voluto; recupera i segreti dentro l’island, sul server
- Le props arrivano dal client — validale, oppure firmale con
SITELO_ISLANDS_SECRET
Strategie di caricamento
Per impostazione predefinita ogni island viene scaricata appena la pagina si carica, quindi una pagina con otto island fa otto richieste contemporanee durante il primo paint. Passa when per rimandare quelle che non sono subito visibili.
// Carica appena lo fa la pagina — il comportamento predefinito.
island('cart', { id }, '<p>…</p>')
// Aspetta una callback di inattività.
island('recommendations', { id }, '<p>…</p>', { when: 'idle' })
// Aspetta che entri nella vista.
island('comments', { postId }, '<p>Caricamento dei commenti…</p>', {
when: 'visible',
rootMargin: '400px', // inizia a caricare 400px prima
})'load'(predefinita) — subito, insieme a ogni altra island'idle'— surequestIdleCallback(con ripiego su un timeout)'visible'— quando entra nella vista, tramite IntersectionObserver
rootMargin vale solo per 'visible' e per impostazione predefinita è '200px'. Le island vanno anche in timeout invece di girare a vuoto per sempre:
mountIslands({
timeout: 5000, // per island; 0 disabilita. Predefinito 10000
rootMargin: '300px', // predefinito per le island con `when: 'visible'`
})mountIslands() si risolve quando le island immediate si sono assestate — quelle rimandate si caricano più tardi per conto loro e deliberatamente non vengono attese. Un’island fallita o andata in timeout si tiene il suo HTML di riserva.
Le props sono input non fidato
Le props di un’island arrivano dal client. Sono incorporate nella pagina, rispedite con la richiesta, e chiunque può modificarle prima:
GET /_sitelo/islands/profile?props={"userId":"someone-else"}Tratta le props che la tua island riceve esattamente come un parametro di query — validale, e non usarle mai per recuperare dati che chi guarda non ha già il diritto di vedere.
Quando le props selezionano dati riservati, firmale. Imposta un segreto e sitelo firma ogni segnaposto in fase di build, respingendo tutto il resto con un 403:
SITELO_ISLANDS_SECRET=$(openssl rand -hex 32) sitelo buildLa stessa variabile viene letta da sitelo, sitelo preview e createIslandsHandler, così sviluppo, preview e produzione vanno d’accordo. Dai lo stesso segreto al tuo host di produzione. Preferisci impostarlo nel codice?
import { configureIslands } from 'sitelo/islands'
configureIslands({ secret: process.env.MY_SECRET })Le firme sono HMAC-SHA256 sul nome dell’island e sulle sue props, quindi una firma emessa per un’island non può essere riusata su un’altra. Firmare dimostra che le props vengono dalla tua build — non le nasconde, perciò devono comunque restare non segrete. Senza un segreto le props vengono accettate così come sono, e validarle è interamente compito del tuo modulo island.
Buono a sapersi
- Nessun endpoint per le island pubblicato? L’HTML di riserva semplicemente resta — le pagine degradano con grazia.
- Le richieste sono
GETcon le props nella query string, quindi le risposte si possono mettere in cache — imposta l’opzionecacheControldell’handler se vuoi che una CDN trattenga i frammenti per un po’. - Durante il caricamento il segnaposto porta
data-sitelo-island-state="loading"(poiloadedoppureerror) — comodo per il CSS.