---
title: Static sites
description: Publish an app as plain files, for GitHub Pages or any file host, with its islands still alive.
section: Operations
order: 3
---

# Static sites

<p class="lead">When a site needs no server of its own, as a landing page or docs usually don't, Sluurp can write it out as plain files. Pages arrive already drawn, and islands still wake in the browser. This site is published that way.</p>

```sh title="Terminal"
sluurp static --public website --public todos=examples/todos/app --out dist
```

It takes `--public` exactly as `serve` does, a folder or a [repository's address](/docs/getting-started), then runs the app on your machine and reads it the way a visitor would. Starting from `/` and each mounted app, it follows every link, script, stylesheet, island, import-map entry, module import and font, and writes what it gets:

- a page as `path/index.html`, so `/docs/sync` becomes `docs/sync/index.html`;
- everything else at its own path, with modules compiled from TypeScript saved as `.js`, so any file host sends them as scripts.

## What works, and what does not

Everything drawn on the server comes through as it is: pages, Markdown docs, highlighted code and the UI kit. Islands keep working when they only draw and react: charts, forms, a Page editor, the chart playground.

What needs a server stays behind: the API, sync, `"use server"` functions and live cursors. An island that talks to the server loads, but has nobody to answer it. The Todos Collab example on this site is one of those.

## GitHub Pages

The output includes a `.nojekyll` file, so GitHub publishes `/_/`, the folder with the theme and styles, instead of dropping it the way Jekyll does with underscore folders. A workflow to publish on every push:

```yaml title=".github/workflows/pages.yml"
name: Pages
on:
  push:
    branches: [master]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: github-pages
    steps:
      - uses: actions/checkout@v4
      - run: curl -fsSL https://raw.githubusercontent.com/SluurpHQ/sluurp/master/install.sh | sh
      - run: ~/.sluurp/bin/sluurp static --public website --out dist
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist
      - uses: actions/deploy-pages@v4
```

## Served from a folder

A project's GitHub Pages site is served from a folder named after the repository: `username.github.io/repo/`. Give that folder as `--base`:

```sh title="Terminal"
sluurp static --public website --out dist --base repo
```

Everything a page or stylesheet names from the root (`/docs`, `/_/theme.css`) is moved under `/repo/`, and each page's import map sends the root's folders there too. What a module imports, and the islands loaded by name, arrive under the base with no module's text changed. At a custom domain or in a `username.github.io` repository, the site is at the root, and no `--base` is needed.

Your own code that builds a root path from a string, such as `link.href = "/theme.css"`, isn't seen by the build. Resolve it from the module instead: `new URL("../theme.css", import.meta.url)`.
