Architecture
This page describes the high-level architecture of the Maskr platform — how the codebase is structured, how clients interact with the Nostr network, and the key technical decisions that shape the system.
Overview
Maskr is organised as a monorepo. The two primary applications are:
- apps/web — the main Next.js application that users interact with at maskr.space.
- apps/docs — this documentation site, also built with Next.js.
- apps/admin — an internal admin dashboard for monitoring and managing the platform.
Shared code (utilities, types, Nostr helpers) lives in packages/shared and is consumed by the apps.
Client–relay architecture
Maskr follows the standard Nostr client–relay model. There are three layers:
- Client (apps/web) — creates Nostr events, validates their structure, signs them with the user's private key, and publishes them to one or more relays. The client also subscribes to relays to receive events from followed accounts and trending content.
- Maskr Hub (hub.maskr.space) — a custom Rust-based Nostr relay hub that aggregates events from the wider network. The hub supports 15+ NIPs, provides full-text search (NIP-50), and acts as the primary relay for all client connections. It bridges content to and from the broader Nostr ecosystem.
- Maskr server (Next.js API routes) — handles authentication (passkeys, OAuth), media uploads via Blossom (blossom.maskr.space), NIP-05 identity verification, AI features (image generation, translation, summarisation), caching of expensive queries, and server-side rendering.
Key architectural decisions
- Next.js App Router — all pages use the App Router for server components, streaming, and co-located layouts. API routes live in
app/api/. - Nostr-first — social data (posts, profiles, follows, reactions) is stored on Nostr relays, not in Maskr's own database. The database is used only for application-layer concerns (auth sessions, drafts, scheduled posts, NIP-05 registrations).
- Multi-auth — three authentication paths are supported (NIP-07 extension, passkeys, OAuth) with a unified session abstraction. Each path results in a Nostr key pair being available for signing; the key source differs per path.
- Effect.js signing pipeline — event creation, validation, and signing are modelled as an Effect.js pipeline. This gives type-safe error handling and makes it easy to swap signing backends (extension, server-side decryption, in-memory) without changing the rest of the publish flow.
Data flow: publishing a post
When a user submits a post, the following steps occur in sequence:
- Compose — the client builds a Nostr event object (kind 1 for a short note) with the appropriate content, tags, and timestamp.
- Validate — the event structure is validated against the NIP specification using the Effect.js pipeline.
- Sign — the event is signed with the user's private key. Depending on the auth method, signing happens in the browser extension, via a server-side decryption call (passkeys), or with an in-memory key (OAuth).
- Publish — the signed event is broadcast to the user's configured relay set via WebSocket connections. A success or failure status is reported back to the UI for each relay.