| name | nip19-routing |
| description | Implement or populate the root-level NIP-19 router (/:nip19) that handles npub, nprofile, note, nevent, and naddr identifiers. Covers decoding, secure filter construction, and type-specific rendering for profiles, notes, events, and addressable events. |
NIP-19 Identifier Routing
NIP-19 defines the bech32-encoded identifiers used throughout Nostr (npub1..., note1..., naddr1..., etc.). This project routes all of them through a single root-level page at /:nip19, implemented by src/pages/NIP19Page.tsx.
Use this skill when the user wants to populate the NIP19Page sections with real views, add a new identifier type, or build links that point into the Nostr routing system.
Identifier Reference
| Prefix | Payload | Use when… |
|---|
npub1 | 32-byte public key | Simple user reference |
nprofile1 | Public key + optional relay hints + petname | User reference with relay context |
note1 | 32-byte event ID (kind:1 text notes only, per NIP-10) | Referencing a short text note/thread |
nevent1 | Event ID + optional relay hints + author pubkey + kind | Any event kind, or notes where you need relay/author context |
naddr1 | kind + pubkey + identifier (d tag) + optional relay hints | Addressable events (kind 30000-39999): articles, products |
nsec1 | Private key | Never display or route — treat as a 404 |
nrelay1 | Relay URL | Deprecated |
note1 vs nevent1
note1 carries only an event ID, and is canonically tied to kind:1 text notes.
nevent1 can reference any kind and can carry relay hints + author pubkey. Prefer nevent1 for non-kind-1 events or when you want to ship relay hints with a link.
npub1 vs nprofile1
npub1 is just a pubkey.
nprofile1 adds relay hints and a petname. Prefer it for shareable profile links where discoverability matters.
Routing Rules
- All NIP-19 identifiers are handled at the URL root:
/:nip19 in AppRouter.tsx. Never nest them under paths like /note/:id or /profile/:npub.
- Invalid, vacant, or unsupported identifiers (including
nsec1 and nrelay1) render the 404 page. The NIP19Page boilerplate already handles this.
- Addressable event URLs must include the author.
naddr1 already encodes pubkey + kind + identifier, which is exactly what a secure query filter needs. If you ever design an alternative URL, use the shape /:npub/:dtag, never /:dtag alone — otherwise anyone can publish a conflicting event with the same d tag.
Decoding and Filtering
Nostr relay filters only accept hex strings. Always decode the NIP-19 identifier before building a filter.
import { nip19 } from 'nostr-tools';
const decoded = nip19.decode(value);
switch (decoded.type) {
case 'npub': {
const pubkey = decoded.data;
return nostr.query([{ kinds: [0], authors: [pubkey], limit: 1 }]);
}
case 'nprofile': {
const { pubkey } = decoded.data;
return nostr.query([{ kinds: [0], authors: [pubkey], limit: 1 }]);
}
case 'note': {
const id = decoded.data;
return nostr.query([{ ids: [id], kinds: [1], limit: 1 }]);
}
case 'nevent': {
const { id } = decoded.data;
return nostr.query([{ ids: [id], limit: 1 }]);
}
case 'naddr': {
const { kind, pubkey, identifier } = decoded.data;
return nostr.query([{
kinds: [kind],
authors: [pubkey],
'#d': [identifier],
limit: 1,
}]);
}
default:
throw new Error('Unsupported Nostr identifier');
}
Common mistakes
nostr.query([{ ids: [naddr] }]);
nostr.query([{ kinds: [30023], '#d': [slug] }]);
const { kind, pubkey, identifier } = nip19.decode(naddr).data;
nostr.query([{ kinds: [kind], authors: [pubkey], '#d': [identifier] }]);
Populating NIP19Page
src/pages/NIP19Page.tsx already:
- Decodes
params.nip19 with nip19.decode.
- Branches on
decoded.type with a section for each supported identifier.
- Redirects invalid / unsupported identifiers to the 404 page.
- Provides a responsive container wrapper.
To turn it into a real router, replace each placeholder section with a concrete component:
decoded.type | Typical view |
|---|
npub / nprofile | Profile page: header from kind 0, feed of the user's events |
note | Single kind:1 text note with thread + replies |
nevent | Generic event renderer; branch on kind for specialized UIs |
naddr | Addressable-event view (article, product, community, etc.) |
Inside each branch, pass the decoded payload (not the raw bech32 string) to a child component. That keeps filter construction colocated with the fetching hook and removes any chance of a re-decode mismatch.
Linking to NIP-19 Routes
When building links elsewhere in the app:
import { nip19 } from 'nostr-tools';
import { Link } from 'react-router-dom';
<Link to={`/${nip19.npubEncode(pubkey)}`}>Profile</Link>
<Link to={`/${nip19.naddrEncode({ kind, pubkey, identifier, relays })}`}>
Open
</Link>
<Link to={`/${nip19.neventEncode({ id, relays, author, kind })}`}>Open</Link>
Always encode with the most specific identifier you have context for (nprofile > npub, nevent > note, naddr for addressable). The extra metadata makes links more robust across relays.
Security Recap
- Decode before querying.
- For addressable events, always include
authors: [pubkey] in the filter — the d tag alone is not a trust boundary.
- Treat
nsec1 and any unknown/invalid identifier as 404. Never render, log, or echo a decoded nsec.