# nsite-clay
> A self-editable nsite. One HTML file holds a page's markup, styling, content
> and state, and republishes itself: the browser serialises the document, stores
> it on Blossom, and publishes a Nostr event saying which bytes are now the page.
> Relays hold the pointer, Blossom holds the bytes, and there is no server in
> between.
This file is the brief for an agent. If you are building someone a website with
nsite-clay, read all of it before you start. It is long on purpose: it tells you
what the thing is, what to ask for, which template to begin from, and what will
go wrong. It links the documentation rather than restating it.
- The guide, with screenshots, for the person you are building it for:
https://nsite-clay.cypherpunk.today/guide.html
- The publisher, if they would rather do it themselves in a browser:
https://nsite-clay.cypherpunk.today/deploy.html
- Templates, browsable: https://nsite-clay.cypherpunk.today/templates.html
- The block composer template, the one to read before writing a block library:
https://nsite-clay.cypherpunk.today/t/cms/
- What a template must do: https://github.com/jooray/nsite-clay/blob/main/templates/_shared/CONTRACT.md
- Runtime reference: https://github.com/jooray/nsite-clay/blob/main/docs/RUNTIME-API.md
- Where state lives: https://github.com/jooray/nsite-clay/blob/main/docs/state.md
- Source: https://github.com/jooray/nsite-clay
- What an nsite is, from the people who built the idea: https://nsite.run
---
## 1. What an nsite is
An nsite is web hosting on Nostr. Not long-form notes rendered as a blog: whole
HTML files, with their CSS, their images and their JavaScript.
Three pieces, and none of them is a server you rent:
**Blossom** stores bytes and names them by their own SHA-256. Upload a file, get
back its hash. Ask any Blossom server for that hash and you get exactly those
bytes or nothing, because the name *is* the checksum. Content-addressed storage,
so a file cannot be quietly swapped for a different one.
**A Nostr event** maps paths to hashes. `/index.html` is this hash, `/about.html`
is that one. The event is signed by the site owner's key, and it is *replaceable*,
meaning a newer one from the same key supersedes it. Publishing a new one is the
deploy. This is [NIP-5A](https://github.com/nostr-protocol/nips/blob/master/5A.md),
kind 15128 for a key's main site and 35128 for a named one.
**A gateway** is an ordinary HTTP server that reads the manifest, fetches the
blobs, checks the bytes hash to what the manifest claims, and serves them. Any
gateway can serve any site, because they all read the same public events.
### Why this works, and why it is not just a fashion
The address is derived from a key, so nobody assigns it and nobody can revoke it.
`https://.nsite.lol/` is yours because you hold the key, not because an
account exists somewhere.
Nothing is tied to one host. Six public gateways serve the same events today:
nsite.lol, nsite.run, nosto.re, nwb.tf, nsite.cloud, shakespeare.to. If one is
down, change the hostname. If all of them are down, run one yourself; the repo
ships one that fits in a file.
There is no account, no password reset, no billing relationship, and no terms of
service between a person and their own page.
### What nsite-clay adds
An nsite, on its own, is deployed from a terminal like any static site. nsite-clay
makes the page edit and republish *itself*, so the content management system is
inside the static HTML rather than behind it.
You change the DOM. Click around the page and edit text, or open your browser's
inspector and change it there; the page does not care which. The new version is
uploaded to Blossom and one replaceable event is republished saying which hash
`/` now points to. Any gateway renders it.
The idea and the editing model come from [HyperClay](https://hyperclay.com) and
[ClayJS](https://clayjs.com). This is that model with Nostr underneath instead of
a hosting platform.
---
## 2. The philosophy, briefly
**The document is the database.** There is no separate store. A checklist's state
is checkboxes in the markup, a board's cards are elements, a theme is an attribute.
Save the page and open the file in a text editor: everything is right there, in
HTML, readable by a person.
**The file at rest is the state.** No hydration step, no build. `curl` returns the
page as a reader sees it, which means crawlers index it and agents can read it
without executing anything.
**Ownership is a keypair.** "Who may edit this page" is a signature check, not a
row in somebody's users table.
**One key writes the document.** There is no shared editing, no per-visitor
content, and no conflict resolution. Two devices editing at once will lose one
side's work. Say this early rather than building toward it.
### Two places to put things
Relays are also a database, and confusing the two is the main way a page goes
wrong. The rule, in full at [docs/state.md](https://github.com/jooray/nsite-clay/blob/main/docs/state.md):
**The document holds what the page is.** Layout, copy, settings, application
state. A kanban board is the page; it has no life anywhere else.
**Relays hold what you published.** A blog post, a note, a photo: things with an
identity of their own that belong in every Nostr client, not only on this page.
Put a post only in the markup and Habla, njump and the rest never see it.
**A post can be both**, and usually should be. Publish the event, then bake a
rendered copy into the page with `nc.compose.bake(event)`. The markup carries
`nc:from="naddr1…"`, so the page is self-contained and readable with no
JavaScript and no relay, while the post stays a first-class Nostr object.
`nc.compose.refreshBaked()` pulls the copies back in line after an edit elsewhere.
**Neither** holds a draft, a filter or a scroll position. That is what the tab is
doing, not state. Serialisation captures attributes, so anything you put in
`dataset` is published to everyone, forever.
---
## 3. What you are actually building
One `index.html`. It links three files from the site root and nothing else:
```html
```
Your job is the markup, the styling and the words. Sign-in, saving, versions,
media upload, feeds and the post composer already exist. Writing your own is the
commonest mistake; check the runtime reference before implementing anything.
Never copy `nsite-clay.js` or `nsite-clay-base.css` into the site. They live at
the root so one upgrade fixes every page.
And the part that is easiest to leave out: the page has to be one they can *add*
to, not only one they can retype. Every list on it needs a way to put another
item in, every page needs the content form, and a page that grows needs a block
library. That is §8, it is not optional, and it is the difference between a site
somebody owns and a picture of one.
---
## 4. Start from a template
Do not start from a blank file.
```bash
npx nsite-clay init mysite --template=blog --npub=npub1…
npx nsite-clay deploy mysite --sec=nsec1…
```
Pick by what the page is *for*, then redesign it. The template gives you a
working page with the chrome wired up; the design is yours to change.
Ten of the twelve carry a block library, so an owner can add sections you did
not think of. `gallery` and `irc` do not, on purpose. Check yours with
`node tools/template-blocks.mjs `.
| Template | For | Look |
|---|---|---|
| `cms` | a page assembled from blocks rather than a fixed design | warm paper, one column, generous air; the owner adds and reorders blocks in the browser |
| `personal` | one person: who you are, what you write | deep blue-black, gold accent, articles as cards and notes as a timeline |
| `blog` | someone who already writes on Nostr | black, lunar, slow glow; articles in the main column, notes in a sidebar |
| `project` | a piece of software | warm paper and clay-red, numbered bands, terminal cards for the commands |
| `event` | a meetup, a gathering, a date and a place | black and white, poster-scale date and venue, downloadable calendar file |
| `links` | one page holding everything you publish | a contents page with numbered rows and printed destinations, not a stack of buttons |
| `gallery` | photographs | four rooms over one set: a canvas you drag, a sparse hang, a constellation, a plain column |
| `terminal` | a project, for people who like terminals | a shell session, phosphor green, readable top to bottom without pressing anything |
| `phrack` | a long technical write-up | a text file: monospace, 79 columns, ASCII rules, two colours |
| `brutal` | a manifesto, a zine, a loud event | thick borders, hard shadows, oversized type, flat colour |
| `eco` | an essay or a small studio | system fonts, almost no images, measures and prints its own page weight |
| `irc` | a log someone keeps adding to: notes, a journal, a changelog | a channel transcript, monospace, with a composer that appends a line and saves it |
Each is a complete page you can read for the pattern. Their rules are in
[CONTRACT.md](https://github.com/jooray/nsite-clay/blob/main/templates/_shared/CONTRACT.md),
which is short and binding: shared stylesheet from the root, `nc:edit-gate="hash"`,
the standard toolbar markup, CSS variables as the styling hook, no CDN links, no
autosave.
### Choosing when they are not sure
Ask what the page replaces. "My Linktree" is `links`. "My blog" is `blog` if they
post to Nostr already and `personal` if they mostly want an about page. "Our
project's site" is `project`, or `terminal` if the audience lives in one. "A
poster for a thing happening" is `event`. If they want to show pictures, `gallery`.
If they are a writer who cares about typography and hates decoration, `phrack` or
`eco`.
### Always ask
- **Their npub.** Required: it goes in `nc:owner` and it is the only key that can
ever change the page. Without a Nostr key nothing works.
- **What the page is for.** This picks the template.
- **What should come from Nostr**, if anything: their notes, their articles,
their photos, somebody else's.
- **What a stranger should do after thirty seconds.** Read something, come to a
thing, hire them, follow them, zap them.
- **What they do not want.** Most people have a strong opinion about at least one
thing they hate on websites, and it is cheaper to hear it now.
Also tell them, without being asked, that one key writes the document: there is
no shared editing and no second account.
### Ask as well, for a personal homepage
- Whether the page pulls their notes, their long-form posts, or both. Fetch a
sample first and design for what is actually there rather than what you
assumed: someone posting twice a day in two languages needs a different layout
from someone publishing an essay a month.
- What is at the top: a photo, a sentence, a list of what they are doing now.
- Which links matter, and whether they want a Lightning address on the page.
Take the picture, banner, name, about line, website and Lightning address from
their kind-0 profile rather than asking again. Everything above is judgement they
have and you do not.
---
## 4b. When they already have a page
Sometimes nobody wants a template. They have a page: a hand written one, an
export from a static site generator, a landing page somebody built them years
ago. Do not rebuild it from a template and do not talk them out of the design
they have. Convert it, and leave them something they can add to on their own.
The design is the easy half. The half that decides whether this was worth doing
is §8: a converted page where the owner can fix a typo but cannot add next
month's event is a screenshot of their old site, not a site.
### First, whether it can be converted at all
Say so early rather than halfway through.
- **A page a framework renders at runtime.** React, Vue, Svelte and anything
else that builds its DOM from JavaScript on load. A save serialises the
rendered DOM, so what gets published is a snapshot of one render, and on the
next load the framework re-renders over it and the edits are gone. These are
not candidates. Their build output usually is, if it is genuinely static HTML.
- **Anything that redraws itself on a timer.** A carousel, a clock, a widget
that polls. Whatever it happens to be showing at the moment of the save is
what gets published. Fine for a page where that region is decoration; not fine
where it is the content.
- **Assets on somebody else's server.** Fonts from a CDN, an analytics script,
an image hotlinked from elsewhere. They keep working, and they also mean the
page is only as durable as those hosts, which is the thing an nsite exists to
avoid. Download them into the directory you are going to deploy, or inline
them. Google Fonts in particular should be replaced with a system stack. The
analytics and the cookie banner that existed to declare it can both go.
- **A `
```
Not from a CDN, and not pasted into the document. They are files in their site,
uploaded to Blossom and named in their manifest like everything else.
The stylesheet is only needed for the runtime's own dialogs and rails. Their CSS
is untouched by it; it defines variables and styles elements whose names all
start with `nc-`.
**Three. The toolbar**, copied verbatim from
[CONTRACT.md](https://github.com/jooray/nsite-clay/blob/main/templates/_shared/CONTRACT.md)
near the end of ``. Without it there is no way to sign in or save from the
page, and a person who cannot find the save button has not been given a
self-editable site. Keep the `data-nc-cms` button in it: the shared stylesheet
hides that one until the page has an `nc:cms` block, so it costs nothing on a
page that does not yet have one and appears by itself when it does.
**Four. `editable` on everything a person would change.** §8, layer one. On a
converted page this is the bulk of the typing and none of the thinking: walk the
document top to bottom and mark every heading, paragraph, caption, list row,
button label, date, price and address. Twenty is a low count for a home page.
Do not stop after the hero.
**Five. Every list becomes a list they can add to.** §8, layer two, and the step
that separates a conversion from a screenshot. Whatever the source page repeats
is a shape that needs Add, Duplicate, Delete and reorder: the events, the posts,
the people, the links, the opening hours. You have the shapes in front of you,
so there is nothing to invent.
**Six. The content form.** An `nc:cms` rules block covering the whole page, with
a card list for every repeating shape. §8 again. Do this on every conversion
rather than only on the ones that look like a CMS, because the person who
inherits the page did not write it and will not know where to click.
**Seven. Blocks.** A converted page usually already contains its own library:
the sections that repeat are the shapes. Wrap the region that should grow in
`nc:blocks`, give each existing section an `nc:block-type`, and lift one of each
distinct shape into a `` (§9b). Do not invent shapes and do
not add CSS the page does not already use. A block added next March has to come
out looking like the ones that are there today, and on a converted page you have
the answer in front of you.
**Eight. Deploy the whole directory** (§5), then open the published page with
`#edit` and go down the list at the end of §8. Do that before handing anything
over: the checks are all things the owner will try in their first five minutes.
### One thing worth knowing
The runtime does not only save what was typed into an `editable` region. A save
serialises the live DOM, so a change made from the browser's own inspector, from
the console, or by the page's own script is published like any other. Nothing
notices such a change by default, though, so autosave will not fire and the
unload guard will not warn about it. A page whose own code edits itself can add
`nc:watch-dom` to `` to have those count as edits too. Leave it off
otherwise: on a page with a clock in it, it would mark the page unsaved forever.
---
## 5. Getting it online
Two routes to the same three steps. Pick by who is doing it.
**They publish, in a browser.**
picks a template, signs in or generates a key with a NIP-49 backup file, checks
what is already at that path, uploads the files and signs the manifest. Nothing
to install. Send a person there when they only want one of the twelve templates
with their own words in it, and point them at
for the walkthrough with
screenshots. You do not need to do anything in that case.
**You publish, from a terminal.** This is the route when you have *built*
something: a design of your own, a block library, a page that is an app. The
publisher only ships the templates that are on the project site; a page you wrote
has to go up from where you wrote it.
```bash
npx nsite-clay deploy mysite --sec=nsec1… # a raw key
NOSTR_BUNKER_URI="bunker://…" npx nsite-clay deploy mysite # a remote signer
```
Prefer the bunker. It keeps the key off the machine running the deploy, which
matters when that machine is yours rather than theirs. Pass the URI in the
environment, never as an argument: an argument is visible to every process on the
box.
The page lands at `https://.nsite.lol/`. `--site=blog` makes a
second, separate site under the same key.
`deploy` takes a directory, not a file, and publishes the whole of it as one
signed path table. That is how a site with more than one page has to go up: the
manifest names every document, every picture and the three shared files at once,
and a page whose sibling is not in the table has a link that leads nowhere. Lay
the directory out the way the URLs read, `index.html` at the root and
`coworking/index.html` for `/coworking/`, and give every document its own
`nc:path`.
You will not have their key, and should not ask for one. Two ways round it:
build the directory, hand it over and let them run the one deploy command; or
have them paste a `bunker://` URI from their signer, which signs without the key
ever reaching your machine. Either way, after the first deploy the page publishes
itself and they never need either route again.
A custom domain is a CNAME to the *npub subdomain*, not to the gateway, because
the npub is what the gateway reads out of the hostname:
`blog.example.com CNAME npub1….nsite.lol`. Do not promise HTTPS on it. The public
gateways hold a wildcard for their own domain only, so a browser asking for
`https://blog.example.com` gets a TLS `unrecognized name` alert. Say plain HTTP,
or a gateway they run themselves.
Develop against the local stack rather than public infrastructure:
```bash
npm run devnet # relay, Blossom and gateway on localhost, all in memory
```
Deploy into it with `--relays=ws://127.0.0.1:4869 --servers=http://127.0.0.1:4870`
and open `http://.localhost:4871/`. Browsers resolve anything under
`.localhost` to loopback, so the npub sits in the hostname exactly as in
production. Stop the process and everything it held is gone.
After the first deploy the page publishes itself and they never need a terminal
again. Tell them that.
---
## 6. Signing in: recommend a remote signer
Three ways in. Steer them to the second.
1. **A NIP-07 browser extension**, Alby or nos2x. Fine on their own machine.
2. **A remote signer over NIP-46**, Amber on Android or nsec.app. The key stays on
their phone and every save is approved there. The right default for a page
served by a gateway, and what you should suggest first.
3. **Pasting a key into the page.** It works, stays in the tab, is written
nowhere, and is still the weakest option. Say so rather than defaulting to it.
The toolbar handles all three. You write no sign-in code. The sign-in QR is
generated in the page, so the connection secret never leaves the browser.
Do not ask for their nsec. You do not need it and you should not have it.
---
## 7. The edit gate
Every template ships `nc:edit-gate="hash"` on ``. Readers see the page and
nothing to click. The owner adds `#edit` to the URL and the toolbar appears.
Tell them explicitly, because a page with no visible way in looks broken to
someone who has forgotten:
```
https://.nsite.lol/#edit
```
Put it in the handover note. `nc.settings.open()` turns the gate off for someone
who would rather have the toolbar always visible.
Autosave is off, deliberately: every save stores the whole page again and files a
version, so a timer publishes a dozen versions of one paragraph. Leaving with
unsaved work already warns them.
---
## 8. Making a page editable
The section people skim, and the one that decides whether what you built is a
website or a picture of one. A page whose owner can fix a typo but cannot add
next month's event comes back to you within a month, or dies quietly.
Three layers. A page is finished when it has all three, and not before.
### Layer one: the words
- `editable` on a container: rich text, where Enter starts a paragraph and
selecting text raises a toolbar with headings, lists, quotes, links, pictures,
video and Nostr feeds.
- `editable="single-line"` on a heading, a caption or a list row: no Enter, no
block menu.
Use `single-line` for anything whose element is inline, an `` that is a
button, a ``, a ``. Those cannot legally hold a paragraph, and the
runtime relies on the token to keep one out.
Mark everything a person would plausibly want to change, and read "everything"
literally: every heading, every paragraph, every caption, the label on every
button, the address in the footer, the line under the logo, the dates, the
prices, the quotes, the names. Twenty editable regions is a low count for a
content page. The question is not whether they would want to change a given
line, it is whether there is a reason it must never change, and there rarely is.
Form controls persist by default, because that is what "the document is the
database" means: a checked box is still checked for the next visitor. Put
`nc:no-persist` on a search box or a filter, where baking the last value into
the published file would be wrong.
### Layer two: the things there is more than one of
Name every kind of thing on the page. An event, a post, a person, a photograph,
a link, a price, an opening time. For each one, ask whether the owner can add
another one next month with nobody's help. Where the answer is no, you have
built a brochure with editable text, and the first thing they ask for will be
the thing you left out.
Every repeating shape gets four operations: add, duplicate, delete and reorder.
Two mechanisms provide them, they cost a few lines each, and a page should have
both, because they suit different people and different moments.
**A rail on the item**, for the person looking at the page who wants to change
the thing in front of them:
```html