---
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
1. **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`.
2. **Invalid, vacant, or unsupported identifiers** (including `nsec1` and `nrelay1`) render the 404 page. The `NIP19Page` boilerplate already handles this.
3. **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.
```ts
import { nip19 } from 'nostr-tools';
const decoded = nip19.decode(value); // throws on invalid input
switch (decoded.type) {
case 'npub': {
const pubkey = decoded.data; // hex string
return nostr.query([{ kinds: [0], authors: [pubkey], limit: 1 }]);
}
case 'nprofile': {
const { pubkey /*, relays */ } = 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 /*, relays, author, kind */ } = decoded.data;
return nostr.query([{ ids: [id], limit: 1 }]);
}
case 'naddr': {
const { kind, pubkey, identifier } = decoded.data;
return nostr.query([{
kinds: [kind],
authors: [pubkey], // critical: prevents d-tag spoofing
'#d': [identifier],
limit: 1,
}]);
}
default:
// nsec, nrelay, unknown → 404
throw new Error('Unsupported Nostr identifier');
}
```
### Common mistakes
```ts
// ❌ Passing bech32 into a filter
nostr.query([{ ids: [naddr] }]);
// ❌ Addressable lookup without the author — anyone can spoof the d-tag
nostr.query([{ kinds: [30023], '#d': [slug] }]);
// ✅ Decode first, then include author
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:
```tsx
import { nip19 } from 'nostr-tools';
import { Link } from 'react-router-dom';
// To a profile
Profile
// To an addressable event (article, product, …)
Open
// To a specific event of any kind, with relay hints
Open
```
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`.