{"id":"d2e8b22a021b5befd99d818953a59014c62b5cd47e859a214e7e1e886914d0b4","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","created_at":1774456562,"kind":30817,"tags":[["d","nip-nsite"],["title","Static Websites"],["k","15128"],["k","35128"],["client","nostrhub.io"]],"content":"NIP-5A\n======\n\nPubkey Static Websites\n----------------------\n\n`draft` `optional`\n\nThis nip describes a method by which static websites can be hosted under public keys using specialized host servers\n\n### Site Manifest Definition\n\nA site manifest event MUST be a replaceable or an addressable event as defined in [NIP-01](01.md). There are two types of site manifest event kinds:\n\n- **Root site**: Uses kind `15128` and MUST NOT include a `d` tag. This is a single replaceable event per pubkey and serves as the root site for the pubkey.\n- **Named sites**: Uses kind `35128` and MUST have a `d` tag containing the site identifier. These can be smaller websites under a pubkey and can be throught of as sub-domains.\n\nThe event MUST include one or more `path` tags that map absolute paths to sha256 hashes. Each `path` tag MUST have the format `[\"path\", \"/absolute/path\", \"sha256hash\"]` where:\n- The first element is the literal string `\"path\"`\n- The second element is an absolute path ending with a filename and extension\n- The third element is the sha256 hash of the file that will be served under this path\n\nThe event MAY include `server` tags that hint at which blossom servers can be used to find the blobs associated with the hashes.\n\nThe event MAY include `title` and `description` tags that provide simple site information.\n\nThe event MAY include a `source` tag that links to the site's source code repository or source archive. The `source` tag MUST have the format `[\"source\", \"<url>\"]`, where `<url>` is an absolute `http` or `https` URL.\n\nThe site icon SHOULD be provided by setting the `/favicon.ico` path in the manifest.\n\nFor example, a root site manifest:\n\n```jsonc\n{\n  \"content\": \"\",\n  \"created_at\": 1727373475,\n  \"id\": \"5324d695ed7abf7cdd2a48deb881c93b7f4e43de702989bbfb55a1b97b35a3de\",\n  \"kind\": 15128,\n  \"pubkey\": \"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5\",\n  \"sig\": \"f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094cf2c05d5a3ac4aebc1227a4ebc3d56867286c15a6df92d55045658bb428fd5fb5\",\n  \"tags\": [\n    // path mappings: absolute path -> sha256 hash\n    [\"path\", \"/index.html\", \"186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99\"],\n    [\"path\", \"/about.html\", \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"],\n    [\"path\", \"/favicon.ico\", \"fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321\"],\n    // optional: blossom server hints\n    [\"server\", \"https://blossom.example.com\"],\n    // optional: site metadata\n    [\"title\", \"My Nostr Site\"],\n    [\"description\", \"A static website hosted on Nostr\"],\n    // optional: source code location\n    [\"source\", \"https://github.com/example/my-nostr-site\"]\n  ]\n}\n```\n\nAnd a named site manifest:\n\n```jsonc\n{\n  \"content\": \"\",\n  \"created_at\": 1727373475,\n  \"id\": \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\",\n  \"kind\": 35128,\n  \"pubkey\": \"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5\",\n  \"sig\": \"f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094cf2c05d5a3ac4aebc1227a4ebc3d56867286c15a6df92d55045658bb428fd5fb5\",\n  \"tags\": [\n    // site identifier\n    [\"d\", \"blog\"],\n    // path mappings: absolute path -> sha256 hash\n    [\"path\", \"/index.html\", \"186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99\"],\n    [\"path\", \"/post.html\", \"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456\"],\n    // optional: blossom server hints\n    [\"server\", \"https://blossom.example.com\"],\n    // optional: site metadata\n    [\"title\", \"My Blog\"],\n    [\"description\", \"A blog hosted on Nostr\"],\n    // optional: source code location\n    [\"source\", \"https://github.com/example/my-nostr-blog\"]\n  ]\n}\n```\n\n### Host server implementation\n\nA host server is a HTTP server that is responsible for serving pubkey static websites\n\n#### Resolving Pubkeys\n\nFor interoperability, host servers SHOULD use the following canonical URL formats:\n\n- Root site: `<npub>.nsite-host.com`\n- Named site: `<pubkeyB36><dTag>.nsite-host.com`\n\n`pubkeyB36` is the author's raw 32-byte pubkey encoded with base36 (lowercase, digits `0-9` then letters `a-z`, no padding) and is always exactly 50 characters.\n\n`dTag` is the site identifier (`d` tag value) as plain text. It is appended directly after `pubkeyB36` with no separator.\n\nFor canonical named-site URLs, `dTag` MUST match `^[a-z0-9-]{1,13}$` and MUST NOT end with `-`.\n\nBecause DNS labels are limited to 63 characters and `pubkeyB36` uses 50 of them, `dTag` MUST be 1-13 characters.\n\nThis single-label format avoids wildcard certificate limitations with multi-level subdomains.\n\nIf the host server is using subdomain routing it MAY serve anything at its own root domain `nsite-host.com` (a landing page for example).\n\nExample subdomains:\n- Root site: `npub10phxfsms72rhafrklqdyhempujs9h67nye0p67qe424dyvcx0dkqgvap0e.nsite-host.com`\n- Named site: `<50-char-pubkeyB36><dTag>.nsite-host.com`\n\n#### Resolving Paths\n\nWhen the host server receives a request and is able to determine the pubkey and identifier, it should fetch the users `10002` [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md) relay list and lookup the site manifest event for the pubkey and identifier.\n\nFor canonical subdomain formats, the host server MUST parse the left-most DNS label as follows:\n\n1. If the label is a valid `npub`, decode it and query for the root site manifest.\n2. Otherwise, if the label matches `^[0-9a-z]{50}[a-z0-9-]{1,13}$` and does not end with `-`, treat it as a named-site label where:\n   - `pubkeyB36` is the first 50 characters\n   - `dTag` is the remaining 1-13 characters\n   - decode `pubkeyB36` to a 32-byte pubkey\n   - use `dTag` as the identifier (`d` tag value)\n\nIf parsing fails, the host server MUST treat the site as not found.\n\nThe host server should query for the site manifest event:\n\n```jsonc\n// For root site (kind 15128, no d tag)\n{ \"kinds\": [15128], \"authors\": [<pubkey>] }\n\n// For named site (kind 35128, with d tag)\n{ \"kinds\": [35128], \"authors\": [<pubkey>], \"#d\": [<identifier>] }\n```\n\nOnce the site manifest event is found, the host server MUST extract the path-to-hash mappings from the `path` tags in the manifest. The host server should look for a `path` tag where the second element matches the requested path.\n\nIf the request path does not end with a filename the host server MUST fallback to using the `index.html` filename\n\nFor example: `/` -> `/index.html` or `/blog/` -> `/blog/index.html`\n\n#### Resolving Files\n\nOnce the host server has found the site manifest event and located the matching `path` tag for the requested path, it should use the sha256 hash defined in the third element of the `path` tag to retrieve the file.\n\nThe host server SHOULD prioritize using `server` tags from the site manifest event as hints for which blossom servers to query. If the manifest includes `server` tags, the host server SHOULD attempt to retrieve the file from those servers first.\n\nIf the pubkey has a `10063` [BUD-03 user servers](https://github.com/hzrd149/blossom/blob/master/buds/03.md) event the server MUST attempt to retrieve the file from the listed servers using the path defined in [BUD-01](https://github.com/hzrd149/blossom/blob/master/buds/01.md#get-sha256---get-blob)\nIf a pubkey does not have a `10063` event and no `server` tags are found in the manifest, the host server MUST respond with a status code 404\n\nThe host server MUST forward the `Content-Type`, and `Content-Length` headers from the Blossom server. If none are defined the host server MAY set `Content-Type` from the file extension in the requested path\n\n#### Handling Not Found\n\nIf a host server is unable to find a site manifest event or a matching `path` tag for the requested path, it MUST use `/404.html` as a fallback path\n\n### Legacy Support\n\nKind `34128` is marked as legacy/deprecated. This kind was used for individual static file events where each file was represented by a separate event with a `d` tag for the path and an `x` tag for the sha256 hash.\n\nHost servers MAY still support kind `34128` for backward compatibility with existing sites, but new sites SHOULD use kind `15128` (root site manifest) or kind `35128` (named site manifest) instead.\n\nRead the [legacy version](https://github.com/hzrd149/nips/blob/41e77b45a1e8a8d170097e363f7d7254797cc5c5/nsite.md) for more details.","sig":"0db366ef988ce4349ceed0096435a249d2571c7cb89fa0668ce3a7e2e85eafc363cfeb88bae0d4daf8b16e601700954b07e3f46b4b9db5fc7d77dfd7307088c0"}