Serverless, client-side URL shortener for Nostr identifiers. Encoding is deterministic — the same input always produces the same short URL. No server, no database, no tracking — the short code is the data.
Live: https://njmp.to
Nostr identifiers (npub, nevent, naddr, note) are bech32-encoded bytes. nshrt strips the bech32 wrapper and re-encodes the raw bytes in base62 (characters 0–9, a–z, A–Z).
A 32-byte pubkey shrinks from npub1… (63 chars) to ~43 chars. When someone visits a short link, the JS router decodes the code back to the full identifier and redirects to njump.me.
URL scheme:
| Path | Resolves to |
|---|---|
/p/<base62> |
npub — public key |
/n/<base62> |
note — event (simple) |
/e/<base62> |
nevent — event |
/a/<base62> |
naddr — parameterized replaceable event |
Note: relay hints embedded in
neventandnaddridentifiers are dropped during encoding — only the event ID, pubkey, kind, and d-tag are preserved. Links may not resolve if the target event is only available on obscure relays.
Share buttons don't need to call any API. The encoding is pure math — you can construct the short URL entirely client-side using the raw values you already have.
All link types reduce to: encode a byte array as base62. Here's the reference implementation:
const B62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
function bytesToBase62(bytes) {
let digits = [0];
for (const byte of bytes) {
let carry = byte;
for (let i = 0; i < digits.length; i++) {
carry += digits[i] * 256;
digits[i] = carry % 62;
carry = Math.floor(carry / 62);
}
while (carry > 0) {
digits.push(carry % 62);
carry = Math.floor(carry / 62);
}
}
let result = "";
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) result += "0";
return result + digits.reverse().map(d => B62[d]).join("");
}
function hexToBytes(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++)
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
return bytes;
}Input: hex pubkey (32 bytes)
function profileLink(pubkeyHex) {
return `https://njmp.to/p/${bytesToBase62(hexToBytes(pubkeyHex))}`;
}Input: hex event id (32 bytes)
function noteLink(eventIdHex) {
return `https://njmp.to/n/${bytesToBase62(hexToBytes(eventIdHex))}`;
}Same payload as /n/, but resolves to a nevent identifier. Use /e/ when the viewer might need relay context; use /n/ for simplicity.
function eventLink(eventIdHex) {
return `https://njmp.to/e/${bytesToBase62(hexToBytes(eventIdHex))}`;
}Input: kind (integer), hex pubkey (32 bytes), d tag (string)
The payload is kind (4 bytes big-endian) ‖ pubkey (32 bytes) ‖ d-tag (UTF-8 bytes).
function naddrLink(kind, pubkeyHex, dTag) {
const kindBytes = [
(kind >>> 24) & 0xff,
(kind >>> 16) & 0xff,
(kind >>> 8) & 0xff,
kind & 0xff,
];
const pubkeyBytes = Array.from(hexToBytes(pubkeyHex));
const dTagBytes = Array.from(new TextEncoder().encode(dTag));
const payload = new Uint8Array([...kindBytes, ...pubkeyBytes, ...dTagBytes]);
return `https://njmp.to/a/${bytesToBase62(payload)}`;
}// Share a profile
const shareUrl = profileLink("7b3b8e..."); // → https://njmp.to/p/7Xk9mQr…
// Share a note
const shareUrl = noteLink("3a1f9c..."); // → https://njmp.to/n/1dKpRqZ…
// Share a long-form article (kind 30023)
const shareUrl = naddrLink(30023, "7b3b8e...", "my-article-slug");nshrt is a single index.html. Deploy it on any static host and configure your server to serve index.html for all routes:
Netlify
# _redirects
/* /index.html 200
Caddy
# Caddyfile
try_files {path} /index.html
Vercel
// vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}nginx
# nginx.conf
location / {
try_files $uri /index.html;
}The BASE_URL is derived from window.location.origin at runtime, so links will automatically use your domain.
Issues and pull requests are welcome. Since nshrt is a single index.html with no build step, contributions are straightforward — just edit the file and open a PR.
Things that would be useful:
- Support for additional Nostr identifier types
- Improvements to the encoding/decoding logic
- UI or accessibility improvements
If nshrt is useful to you ⚡ Zap this note or just send some sats to sats@liberspace.org