Ilhas de servidor
Nesta página
Às vezes uma região de uma página, de resto estática, precisa de dados frescos a cada pedido — os comentários por baixo de um artigo em cache, uma etiqueta de stock numa página de produto. As ilhas de servidor mantêm a página estática e renderizam só essa região num servidor, quando a página é vista.
1. Escreve a ilha
Uma ilha é um módulo de fragmento dentro de src/islands/ — um ficheiro .js ou .ts normal (não .ht.js, porque as ilhas são fragmentos, não páginas). A mesma ideia de todo o sitelo: uma função que devolve 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>`
}Recebe { name, props, request } e tem de devolver uma string HTML. Os módulos de ilha são exclusivos do servidor — o código sem referências dentro de src/ nunca chega ao navegador.
2. Coloca-a numa página
Importa island() de sitelo/islands. A compilação estática publica o HTML de reserva; as props são embutidas no marcador, por isso mantém-nas pequenas e sem segredos.
import { island } from 'sitelo/islands'
export default ({ params }) => `
<html>
<body>
<article>…conteúdo estático…</article>
${island('comments', { postId: params.slug }, '<p>A carregar comentários…</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('…conteúdo estático…'),
island('comments', { postId: params.slug }, '<p>A carregar comentários…</p>'),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Post({ params }) {
return (
<html>
<body>
<article>…conteúdo estático…</article>
{island('comments', { postId: params.slug }, '<p>A carregar comentários…</p>')}
<script type="module" src="/islands.js" />
</body>
</html>
)
}3. Acrescenta o carregador de cliente
Um script minúsculo vai buscar cada fragmento renderizado e troca-o. Passa pelo pipeline de recursos habitual, por isso basta um ponto de entrada src/islands.js normal:
import { mountIslands } from 'sitelo/islands/client'
mountIslands()Em sitelo (dev) e em sitelo preview isto já funciona — ambos servem as ilhas em /_sitelo/islands/<name> a partir de src/islands/. O preview carrega módulos .js / .mjs nativos (tal como um host Node); as ilhas em TypeScript funcionam em desenvolvimento graças ao Vite.
Produção
O teu alojamento estático continua a servir as páginas. Monta um pequeno handler onde correres código de servidor — Node, serverless ou uma função edge — e ele renderiza os mesmos módulos de ilha. Para um percurso completo com um host Node executável e esboços para Netlify e Vercel, vê o exemplo de ilhas de servidor.
// por exemplo um servidor Node, ou uma função serverless/edge
import { createIslandsHandler } from 'sitelo/islands/server'
const handleIslands = createIslandsHandler({
islands: {
comments: () => import('./src/islands/comments.js'),
},
})
// Web Request → Response | null (null = não é um pedido de ilha)
export default { fetch: (request) => handleIslands(request) }Com http do Node simples ou com express, usa antes createIslandsNodeHandler(options) — as mesmas opções, assinatura (req, res, next). Podes ligar automaticamente todos os módulos .js / .mjs de src/islands/ com createIslandsFromDirectory. Se o carregador for buscar a outra origem ou caminho, passa mountIslands({ endpoint: 'https://api.example.com/islands' }) e faz corresponder à opção endpoint do handler.
Lista de estabilidade
island()/mountIslands()/createIslandsHandler/createIslandsNodeHandler— API pública, considerada estávelsiteloesitelo previewservem ambos/_sitelo/islandsa partir desrc/islands/createIslandsFromDirectory— o mesmo mapa para hosts Node e para o preview (nativo.js/.mjs/.cjs)- Esboços de host em exemplo de ilhas de servidor:
server.jspara Node, função Netlify + rewrite, serverless Vercel + rewrite - As props ficam pequenas e sem segredos (query string de GET) — é intencional; vai buscar os segredos dentro da ilha, no servidor
- As props vêm do cliente — valida-as, ou assina-as com
SITELO_ISLANDS_SECRET
Estratégias de carregamento
Por omissão cada ilha vai buscar os dados assim que a página carrega, por isso uma página com oito ilhas faz oito pedidos em simultâneo durante a primeira pintura. Passa when para adiar as que não estão logo à vista.
// Carrega assim que a página carrega — o valor por omissão.
island('cart', { id }, '<p>…</p>')
// Espera por um callback de inatividade.
island('recommendations', { id }, '<p>…</p>', { when: 'idle' })
// Espera até entrar no ecrã.
island('comments', { postId }, '<p>A carregar comentários…</p>', {
when: 'visible',
rootMargin: '400px', // começa a carregar 400px antes
})'load'(por omissão) — de imediato, junto com todas as outras ilhas'idle'— numrequestIdleCallback(com um temporizador como alternativa)'visible'— quando entra no ecrã, via IntersectionObserver
rootMargin só se aplica a 'visible' e vale '200px' por omissão. As ilhas também expiram em vez de ficarem a rodar para sempre:
mountIslands({
timeout: 5000, // por ilha; 0 desativa. Por omissão 10000
rootMargin: '300px', // valor por omissão para ilhas com `when: 'visible'`
})mountIslands() resolve assim que as ilhas imediatas assentam — as adiadas carregam mais tarde por si e, deliberadamente, não são esperadas. Uma ilha que falhe ou expire mantém o seu HTML de reserva.
As props são entrada não fiável
As props de uma ilha vêm do cliente. São embutidas na página, devolvidas no pedido, e qualquer pessoa as pode editar pelo caminho:
GET /_sitelo/islands/profile?props={"userId":"someone-else"}Trata as props que a tua ilha recebe exatamente como um parâmetro de query — valida-as, e nunca as uses para ir buscar dados a que quem visita não tenha já direito.
Quando as props selecionam dados privilegiados, assina-as. Define um segredo e o sitelo assina cada marcador na compilação, rejeitando tudo o resto com um 403:
SITELO_ISLANDS_SECRET=$(openssl rand -hex 32) sitelo buildA mesma variável é lida por sitelo, sitelo preview e createIslandsHandler, por isso desenvolvimento, preview e produção concordam. Dá o mesmo segredo ao teu host de produção. Preferes defini-lo em código?
import { configureIslands } from 'sitelo/islands'
configureIslands({ secret: process.env.MY_SECRET })As assinaturas são HMAC-SHA256 sobre o nome da ilha e as suas props, por isso uma assinatura emitida para uma ilha não pode ser reutilizada noutra. Assinar prova que as props vieram da tua compilação — não as esconde, por isso continuam a não poder ser segredos. Sem segredo, as props são aceites tal como estão e validá-las cabe inteiramente ao teu módulo de ilha.
Coisas a saber
- Não implementaste nenhum endpoint de ilhas? Fica simplesmente o HTML de reserva — as páginas degradam-se com elegância.
- Os pedidos são
GETcom as props na query string, por isso as respostas são cacheáveis — define a opçãocacheControldo handler se quiseres que uma CDN segure os fragmentos por um bocado. - Durante o carregamento, o marcador leva
data-sitelo-island-state="loading"(e depoisloadedouerror) — prático para o CSS.