---
title: Server components and islands
description: routes/*.tsx and routes/*.md rendered on the server; islands where something moves.
section: Server
order: 2
---

# Server components and islands

<p class="lead">A file in <code>routes/</code> is a page rendered to HTML on the server. A <code>.tsx</code> page uses the same JSX runtime, kit and SDK as the browser; a <code>.md</code> page is Markdown. The browser receives only the islands' JavaScript.</p>

```tsx title="routes/notes.tsx"
import { Card, Badge } from "sluurp/kit";
import { Sluurp } from "sluurp";
import AddNote, { total } from "../islands/add-note.tsx";

// a component may wait for its data
async function Newest() {
  const page = await new Sluurp().collection("notes").list({ sort: "title" });
  return <ul>{page.items.map((n) => <li>{n.title}</li>)}</ul>;
}

export default async function Page() {
  // a "use server" function, called in place
  const count = await total();
  return (
    <main>
      <h1>Notes</h1>
      <Card><Badge>{count} notes</Badge><Newest /></Card>
      <AddNote start={count} since={new Date()}><em>Written on the server.</em></AddNote>
    </main>
  );
}
```

## Routes

| File | URL |
|---|---|
| `routes/index.tsx` | `/` |
| `routes/docs/sync.md` | `/docs/sync` |
| `routes/posts/[slug].tsx` | `/posts/anything`, as `params.slug` |
| `routes/files/[...rest].tsx` | `/files/a/b/c` |
| `routes/_layout.js` | the document every page is wrapped in |
| `routes/docs/_layout.js` | wraps the pages in `docs/`, inside the one above |

A page needs no declarations. It is public unless it exports a `rule` (`export const rule = "@request.auth.id != null"`), and its title is its first `<h1>` unless `load` returns another. What a page shows, it reads as the visitor, so collection rules still decide what that is.

## Markdown pages

```md title="routes/docs/sync.md"
---
title: Sync
section: Data
order: 3
---

# Sync

A shape is a collection narrowed by a filter…

<sluurp-island src="chart-playground"></sluurp-island>
```

The frontmatter is the page's data. A folder's `_layout.js` receives every Markdown page beside it, with each page's URL and frontmatter, so this site's sidebar builds itself. Code fences can take a `title="…"`. An island is just its HTML tag, which the browser wakes like any other island.

Each Markdown page's source is also served at its URL plus `.md`: `/docs/sync.md`. From the same pages, a site gets [`/llms.txt`](/llms.txt), an index of every page for language models grouped by `section`, and [`/llms-full.txt`](/llms-full.txt), every page's text in one file. Whatever `routes/_llms.md` says goes first: a title, a line on what the site is, how to use it. Its `sections` sets the order of the sections. A site with its own `llms.txt` file keeps it.

## A whole page as an island

A route that begins with `"use client"` is drawn on the server and then taken over, whole, by the browser. The browser is sent the file itself, with its `"use server"` bodies left out, so nothing else in it should be secret. Its default export receives `{ data, query, params }` on both sides.

```tsx title="routes/index.tsx"
"use client";

export const schema = { todos: { fields: { title: "text!", done: "bool" }, rules: "" } } as const;

export async function load() { /* on the server */ }
export default function Page({ data }) { /* on the server, then in the browser */ }
```

`export const schema` can go in any route. It is read as a literal, never run, and applied with the app's `schema.json` when the server starts. Each field is a type (with `!` for required) or a field as `schema.json` writes it, and `rules` is one rule for all five or an object with each.

Its rows get a type from it, so the fields are written once. Add `as const` to the schema, and import its type in the island:

```tsx title="islands/todos.tsx"
import type { RowOf } from "sluurp/sync";
import type { schema } from "../routes/index.tsx";

type Todo = RowOf<typeof schema.todos>; // { id: string; title: string; done: boolean }
```

`import type` leaves no import behind, so the island never loads the route. A required field (`"text!"`) is its type; any other may be `null`, except a `bool`, which is always true or false.

## Islands

A component imported from `islands/` is rendered on the server with the page, then taken over by the browser. Its props must be data, not functions. They are written as devalue, so a Date prop is still a Date in the browser. JSX children and markup props travel as templates.

- `client="visible"` wakes an island when it scrolls into view, `"idle"` when the browser is idle, and `"media:(min-width: 768px)"` when a media query matches. By default (`"load"`), it wakes at once.
- The browser adopts what the server drew: text typed, the focus and scroll positions set before an island wakes are all still there after.
- `isolated` draws an island inside its own shadow root, with only Sluurp's styles: it looks exactly the same wherever it is embedded, whatever the page around it does. The [Todos Collab](/examples) example on this site is one.
- `new Sluurp().collection("notes")` works the same on both sides, reading as the visitor on the server and in the browser.
