# 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. - Human walkthrough with screenshots: https://npub12edc7326qsryw5rw5yw0yh57fmj9r8jf4c8xazz6333w305qgnms9ypvj2.nsite.lol/docs.html - Templates, browsable: https://npub12edc7326qsryw5rw5yw0yh57fmj9r8jf4c8xazz6333w305qgnms9ypvj2.nsite.lol/templates.html - 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 two 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. --- ## 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. | Template | For | Look | |---|---|---| | `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. --- ## 5. Getting it online ```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/`. A CNAME at any gateway gives a custom domain. `--site=blog` makes a second, separate site under the same key. 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 Two attributes cover most of it. - `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. Mark everything a person would plausibly want to change. A template nobody can edit without opening a text editor has missed the point. Aim for twenty or more editable regions on a content page. 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. --- ## 9. When the page is an app, not a document A board, a checklist or a directory needs more than typing. Put the controls on the thing they act on and mark them `nc-gear`, so they appear only for a signed-in owner who is editing: ```html

Fix Blossom sync

``` **Do not put `clay="no-save"` on a gear.** That strips it from the save, so the pattern works exactly once and the published page has no controls. A gear is part of the app and belongs in the file; the rules above keep it away from readers. `clay="no-save"` is for markup that must never be written at all, such as a composer holding a half-typed line. To have a control that both persists and is never itself written, draw it from a script that persists. `nc.dom` gives you `clone`, `remove`, `move`, `insert`, `addFrom` a `