🚀 Publish and manage static websites on the Nostr network - Deploy, version, monitor, and maintain decentralized websites with a comprehensive CLI toolkit.
Nostr Web Publisher (nw-publisher) is a full-featured CLI tool for managing static websites on Nostr. Deploy sites as signed Nostr events, track versions, monitor relay status, sync across relays, and clean up old deployments - all from one command-line interface.
To view sites published with nw-publisher, install the Nostr Web Browser extension:
|
|
|
- 📦 Deploy static sites - Publish HTML, CSS, JS to Nostr relays
- 🔒 Content-addressed - SHA256 hashes for all assets
- 📝 Version management - Track, compare, and query site versions
- 🔗 Multi-relay publishing - Parallel uploads with retry logic
- 💾 Smart caching - Reuse unchanged assets between deploys
- 🔄 Cross-relay sync - Ensure all relays have complete data
- 🧹 Event cleanup - Remove old or orphaned events
- 📊 Status monitoring - Check relay connectivity and site health
- 📄 DNS TXT generation - Ready-to-paste records for your domain
Install globally from npm:
npm install -g nw-publisherThis installs the nw-publisher command globally.
If you want to contribute or need the latest development version:
git clone https://github.com/Shugur-Network/nw-publisher.git
cd nw-publisher
npm install
npm linknpm install -g nw-publisherCreate your HTML/CSS/JS files in a directory:
mkdir my-website
cd my-website
# Create index.html, style.css, etc.Generate a Nostr keypair and create a .env file:
# Generate keypair with nostr-tools
npx nostr-tools keygen
# Create .env file with your private key
echo "NOSTR_SK_HEX=your_private_key_hex" > .env
echo "RELAYS=wss://shu01.shugur.net,wss://nos.lol" >> .envnw-publisher deploy .The publisher outputs _nweb.txt with instructions. Copy the JSON value into a TXT record:
Host: _nweb.yourdomain.com
Type: TXT
Value: {"pk":"npub1...","relays":["wss://shu01.shugur.net",...]}
Static Site Folder
├─> Scan files (HTML, CSS, JS)
├─> Compute SHA256 hashes
├─> Sign as Nostr events
│ └─> Kind 1125: All assets (HTML, CSS, JS, etc.)
├─> Publish to relays (parallel)
├─> Create page manifests (kind 1126)
├─> Update site index (kind 31126)
└─> Update entrypoint (kind 11126)
Immutable assets (kind 1125) are deduplicated by querying relays:
- Cache source: Nostr relays (queries on every deploy)
- Cache key:
${kind}:${content-hash}(content-addressed) - Behavior: If file unchanged and found on relays, reuses cached event ID
- Benefit: Only publishes new/changed assets
- Reliability: Always reflects true relay state, no stale local files
Addressable events (31126 - Site Index) and replaceable events (11126 - Entrypoint) are republished when content changes. This ensures:
- Extension detects which site is newest
- Multiple sites with same pubkey stay synchronized
- DNS changes propagate immediately
All assets include SHA256 tags for verification:
{
"kind": 1125,
"tags": [
["m", "text/html"],
["x", "abc123..."],
["alt", "Home Page"]
],
"content": "<!DOCTYPE html>..."
}Deploy your website to Nostr relays.
Options:
--version=X.Y.Z- Set a custom version (e.g., 2.0.0)--rebuild-cache- Force rebuild cache from relays
# Basic deployment
nw-publisher deploy .
nw-publisher deploy ./my-site
nw-publisher deploy examples/hello-world
# Deploy with custom version
nw-publisher deploy . --version=2.0.0
# Rebuild cache from relays
nw-publisher deploy . --rebuild-cacheVersion Management:
- Without
--version: Automatically increments based on changes- patch (0.0.x): Content changes only
- minor (0.x.0): New routes added/removed
- With
--version: Uses your specified version (format: X.Y.Z)
Check relay connectivity and deployment status.
# Your site (uses .env)
nw-publisher status
# Another site (no private key needed)
nw-publisher status npub1abc123...Manage and query site versions.
nw-publisher versions list
nw-publisher versions show 1.0.0
nw-publisher versions compare 0.9.0 1.0.0
nw-publisher versions list npub1abc123...Ensure all versions exist on all configured relays.
nw-publisher syncRemove events from Nostr relays (all events, orphaned events, or a specific version).
nw-publisher cleanup # Delete all events (with confirmation)
nw-publisher cleanup --version 0.1.0 # Delete a specific version
nw-publisher cleanup --orphans # Delete orphaned events only
nw-publisher cleanup --dry-run # Preview without deleting
nw-publisher cleanup --relay wss://... # Target specific relay(s)Options:
--all, -a- Delete all events (default)--orphans, -o- Delete only orphaned events--version <ver>, -v- Delete a specific version and its assets--relay <url>, -r- Target specific relay(s) (can be used multiple times)--dry-run, -d- Show what would be deleted without deleting
nw-publisher deploy .# Your own site
nw-publisher status
# Another site
nw-publisher status npub1abc123...# After adding new relays
nw-publisher sync# Preview what will be deleted
nw-publisher cleanup --orphans --dry-run
# Delete orphaned events
nw-publisher cleanup --orphans
# Delete a specific version
nw-publisher cleanup --version 0.1.0
# Delete version from specific relay
nw-publisher cleanup --version 0.2.0 --relay wss://relay.example.com| Variable | Required | Description | Example |
|---|---|---|---|
NOSTR_SK_HEX |
Yes | Nostr private key (hex) | a1b2c3d4... |
RELAYS |
Yes | Comma-separated relay URLs | wss://relay1.com,... |
NWEB_HOST |
No | Your domain (for DNS record) | yourdomain.com |
Input (static site):
my-site/
├─ index.html # Home page
├─ style.css # Stylesheet
├─ app.js # JavaScript
└─ about.html # Subpage (optional)
Output:
my-site/
├─ _nweb.txt # DNS setup instructions
└─ _nweb.txt.json # DNS TXT JSON (ready to paste)
Published to Nostr:
- Asset events (1125) → HTML, CSS, JS, fonts, images
- Page manifests (1126) → Per-route metadata
- Site index (31126) → Route mapping (addressable)
- Entrypoint (11126) → Points to current site index
| Variable | Required | Description | Example |
|---|---|---|---|
NOSTR_SK_HEX |
✅ Yes | Nostr private key (64-char hex) | a1b2c3d4... |
RELAYS |
✅ Yes | Comma-separated relay URLs | wss://shu01.shugur.net,... |
NWEB_HOST |
Your domain | yourdomain.com |
nw-publisher queries relays on every deployment to check for existing assets and versions:
- Asset deduplication: Content-addressed matching (SHA256) prevents re-uploading unchanged files
- Version history: Reconstructed from site index events (kind 31126) on relays
- No local cache files: Relays are the single source of truth
- Multi-project safe: Different projects can coexist without cache conflicts
Why no local cache?
- Local files can become stale after
clean --allor manual deletions - Multiple projects deploying to same pubkey would share/corrupt cache
- Relay queries are fast and ensure accuracy
- Simplifies workflows (no cache management needed)
Benefits:
- ✅ No dependency on local files
- ✅ Works across multiple machines
- ✅ Team members see the same state
- ✅ CI/CD doesn't need cache files
- ✅ Relays are the single source of truth
If you need to reset your site, remove old versions, or clean up orphaned events:
# Delete everything (full reset)
nw-publisher cleanup --all
# Delete a specific version
nw-publisher cleanup --version 0.1.0
# Delete orphaned events only
nw-publisher cleanup --orphans
# Preview without deleting
nw-publisher cleanup --version 0.2.0 --dry-runThis will:
- Query events from configured relays
- Show summary of what will be deleted
- Ask for confirmation (type "DELETE")
- Send deletion requests (kind 5 events) to all relays
- Provide detailed deletion statistics per relay
Cleanup Modes:
--all: Delete all events (full reset)--version <ver>: Delete a specific version and its assets--orphans: Delete only unreferenced events (orphaned assets/manifests)
See the cleanup help for more details: nw-publisher cleanup --help
Solution: Run npm install first
Solution: Create .env file:
echo "NOSTR_SK_HEX=your_hex_key_here" > .envCauses:
- Relays offline (try different relays)
- Events too large (relay limits ~64KB)
- Rate limiting (wait a few minutes)
Solution: Use the cleanup tool to remove orphaned events or do a full reset, then republish:
# Option 1: Clean up orphans only
nw-publisher cleanup --orphans
nw-publisher deploy <site-folder>
# Option 2: Full reset
nw-publisher cleanup --all
nw-publisher deploy <site-folder>Check:
- DNS TXT record at
_nweb.<yourdomain.com> - JSON format valid
- Pubkey in DNS matches events
- Nostr Web Browser extension installed (Chrome / Firefox)
- Extension v0.9.6+ (fetches site index fresh)
Reset: If site is inconsistent, use cleanup tool:
# Clean up orphans
nw-publisher cleanup --orphans
nw-publisher deploy <site-folder>
# Or do a full reset
nw-publisher cleanup --all
nw-publisher deploy <site-folder>| Kind | Name | Type | Purpose |
|---|---|---|---|
1125 |
Asset | Regular | All web assets (HTML, CSS, JS, etc.) |
1126 |
Page Manifest | Regular | Links assets per page |
31126 |
Site Index | Addressable | Maps routes (content-addressed) |
11126 |
Entrypoint | Replaceable | Points to current site index |
Regular events (1125, 1126):
- Content-addressed by SHA256 (via
xtag) - Immutable once published
- Cached indefinitely
- Never republished if unchanged
Addressable event (31126 - Site Index):
- Uses content-addressed
dtag (first 7-12 chars of content hash) - Different content = different event
- Each version is preserved on relays
Replaceable event (11126 - Entrypoint):
- Only latest event per author is kept
- Points to current site index via
atag - Updated when site index changes
Asset (Kind 1125):
{
"kind": 1125,
"tags": [
["m", "text/html"],
["x", "abc123..."],
["alt", "Home Page"]
],
"content": "<!DOCTYPE html>..."
}Page Manifest (Kind 1126):
{
"kind": 1126,
"tags": [
["route", "/"],
["title", "Home"],
["e", "html_event_id", "wss://relay.example.com"],
["e", "css_event_id", "wss://relay.example.com"],
["e", "js_event_id", "wss://relay.example.com"]
],
"content": ""
}Site Index (Kind 31126):
{
"kind": 31126,
"tags": [
["d", "a1b2c3d"],
["x", "a1b2c3d4e5f6...full-hash..."],
["alt", "main"]
],
"content": "{
\"/\": \"<manifest-event-id-1>\",
\"/about\": \"<manifest-event-id-2>\"
}"
}Entrypoint (Kind 11126):
{
"kind": 11126,
"tags": [["a", "31126:<pubkey>:a1b2c3d", "wss://relay.example.com"]],
"content": ""
}npm testnpm run test:watchnpm run test:coverage