Wyspy serwerowe
Na tej stronie
Czasem jeden obszar skądinąd statycznej strony potrzebuje świeżych danych przy każdym żądaniu — komentarze pod zbuforowanym wpisem, informacja o dostępności na stronie produktu. Wyspy serwerowe utrzymują stronę statyczną i renderują na serwerze tylko ten obszar, w chwili gdy ktoś stronę ogląda.
1. Napisz wyspę
Wyspa to moduł-fragment pod src/islands/ — zwykły plik .js albo .ts (nie .ht.js, bo wyspy są fragmentami, a nie stronami). Ta sama myśl co wszędzie indziej w sitelo: funkcja zwracająca 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>`
}Dostaje { name, props, request } i musi zwrócić ciąg HTML. Moduły wysp żyją wyłącznie na serwerze — kod bez odwołań pod src/ nigdy nie trafia do przeglądarki.
2. Umieść ją na stronie
Zaimportuj island() z sitelo/islands. Statyczny build publikuje HTML zastępczy; propsy są osadzone w miejscu na wyspę, więc trzymaj je małe i niepoufne.
import { island } from 'sitelo/islands'
export default ({ params }) => `
<html>
<body>
<article>…statyczna treść…</article>
${island('comments', { postId: params.slug }, '<p>Wczytywanie komentarzy…</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('…statyczna treść…'),
island('comments', { postId: params.slug }, '<p>Wczytywanie komentarzy…</p>'),
script({ type: 'module', src: '/islands.js' }),
),
)import { island } from 'sitelo/islands'
export default function Post({ params }) {
return (
<html>
<body>
<article>…statyczna treść…</article>
{island('comments', { postId: params.slug }, '<p>Wczytywanie komentarzy…</p>')}
<script type="module" src="/islands.js" />
</body>
</html>
)
}3. Dodaj loader kliencki
Maleńki skrypt pobiera każdy wyrenderowany fragment i podmienia go na miejscu. Przechodzi zwykłym potokiem zasobów, więc wystarczy prosty punkt wejścia src/islands.js:
import { mountIslands } from 'sitelo/islands/client'
mountIslands()W sitelo (dev) i w sitelo preview to już działa — oba serwują wyspy pod /_sitelo/islands/<nazwa> z katalogu src/islands/. Podgląd ładuje natywne moduły .js / .mjs (tak jak host Node); wyspy w TypeScripcie działają w dev dzięki Vite.
Produkcja
Twój hosting statyczny dalej serwuje strony. Zamontuj mały handler tam, gdzie uruchamiasz kod serwerowy — Node, serverless albo funkcja edge — a wyrenderuje te same moduły wysp. Pełny przewodnik z uruchamialnym hostem Node oraz zaczątkami dla Netlify i Vercela znajdziesz w przykładzie wysp serwerowych.
// np. serwer Node albo funkcja serverless/edge
import { createIslandsHandler } from 'sitelo/islands/server'
const handleIslands = createIslandsHandler({
islands: {
comments: () => import('./src/islands/comments.js'),
},
})
// webowy Request → Response | null (null = to nie żądanie wyspy)
export default { fetch: (request) => handleIslands(request) }Na zwykłym http Node’a albo na expressie użyj zamiast tego createIslandsNodeHandler(options) — te same opcje, sygnatura (req, res, next). Podłącz automatycznie każdy moduł .js / .mjs spod src/islands/ przez createIslandsFromDirectory. Jeśli loader pobiera z innego źródła albo z innej ścieżki, podaj mountIslands({ endpoint: 'https://api.example.com/islands' }) i dopasuj do tego opcję endpoint handlera.
Lista kontrolna stabilności
island()/mountIslands()/createIslandsHandler/createIslandsNodeHandler— publiczne API, traktowane jako stabilnesiteloisitelo previewoba serwują/_sitelo/islandsz katalogusrc/islands/createIslandsFromDirectory— ta sama mapa dla hostów Node i dla podglądu (natywne.js/.mjs/.cjs)- Zaczątki hostów w przykładzie wysp serwerowych:
server.jsdla Node’a, funkcja + przepisanie dla Netlify, serverless + przepisanie dla Vercela - Propsy zostają małe i niepoufne (query string żądania GET) — to celowe; sekrety pobieraj wewnątrz wyspy, na serwerze
- Propsy pochodzą od klienta — waliduj je albo podpisuj przez
SITELO_ISLANDS_SECRET
Strategie ładowania
Domyślnie każda wyspa pobiera się, gdy tylko strona się załaduje, więc strona z ośmioma wyspami robi osiem równoczesnych żądań podczas pierwszego malowania. Podaj when, żeby odłożyć te, których nie widać od razu.
// Ładuj razem ze stroną — domyślnie.
island('cart', { id }, '<p>…</p>')
// Poczekaj na wywołanie w bezczynności.
island('recommendations', { id }, '<p>…</p>', { when: 'idle' })
// Poczekaj, aż wejdzie w pole widzenia.
island('comments', { postId }, '<p>Wczytywanie komentarzy…</p>', {
when: 'visible',
rootMargin: '400px', // zacznij ładować 400px wcześniej
})'load'(domyślnie) — natychmiast, razem z każdą inną wyspą'idle'— przyrequestIdleCallback(z zapasowym limitem czasu)'visible'— gdy wjedzie w pole widzenia, przez IntersectionObserver
rootMargin dotyczy tylko 'visible' i domyślnie wynosi '200px'. Wyspy mają też limit czasu, zamiast kręcić się w nieskończoność:
mountIslands({
timeout: 5000, // na wyspę; 0 wyłącza. Domyślnie 10000
rootMargin: '300px', // domyślne dla wysp z `when: 'visible'`
})mountIslands() kończy się, gdy natychmiastowe wyspy się ustabilizują — odłożone ładują się później samodzielnie i celowo nie czeka się na nie. Wyspa, która zawiodła albo przekroczyła limit czasu, zachowuje swój HTML zastępczy.
Propsy to niezaufane wejście
Propsy wyspy pochodzą od klienta. Są osadzone w stronie, odsyłane z żądaniem i każdy może je wcześniej zmienić:
GET /_sitelo/islands/profile?props={"userId":"someone-else"}Traktuj propsy, które dostaje Twoja wyspa, dokładnie jak parametr zapytania — waliduj je i nigdy nie używaj ich do sięgania po dane, których oglądający i tak nie ma prawa zobaczyć.
Gdy propsy wybierają dane uprzywilejowane, podpisz je. Ustaw sekret, a sitelo podpisze każde miejsce na wyspę podczas buildu i odrzuci wszystko inne kodem 403:
SITELO_ISLANDS_SECRET=$(openssl rand -hex 32) sitelo buildTę samą zmienną czytają sitelo, sitelo preview i createIslandsHandler, więc dev, podgląd i produkcja są zgodne. Daj ten sam sekret swojemu hostingowi produkcyjnemu. Wolisz ustawić go w kodzie?
import { configureIslands } from 'sitelo/islands'
configureIslands({ secret: process.env.MY_SECRET })Podpisy to HMAC-SHA256 z nazwy wyspy i jej propsów, więc podpis wystawiony dla jednej wyspy nie da się odtworzyć na innej. Podpisywanie dowodzi, że propsy pochodzą z Twojego buildu — nie ukrywa ich, więc nadal muszą być niepoufne. Bez sekretu propsy są przyjmowane takie, jakie są, a ich walidacja to w całości zadanie Twojego modułu wyspy.
Warto wiedzieć
- Nie wdrożyłeś punktu końcowego wysp? HTML zastępczy po prostu zostaje — strony degradują się łagodnie.
- Żądania są typu
GETz propsami w query stringu, więc odpowiedzi da się buforować — ustaw opcjęcacheControlhandlera, jeśli chcesz, by CDN przez chwilę trzymał fragmenty. - Podczas ładowania miejsce na wyspę ma
data-sitelo-island-state="loading"(potemloadedalboerror) — wygodne dla CSS-a.