Wisp.place#
Decentralized static site hosting on the AT Protocol. https://wisp.place
What is this?#
Host static sites in your AT Protocol repo, served with CDN distribution. Your PDS holds the cryptographically signed manifest and files - the source of truth. Hosting services index and serve them fast.
Quick Start#
# Using the web interface
Visit https://wisp.place and sign in
# Or use the CLI
npm install -g wispctl
# or
npm create wisp
Your site appears at https://sites.wisp.place/{your-did}/{site-name} or your custom domain.
Architecture#
/apps/main-app- Main backend (OAuth, site management, custom domains)/apps/hosting-service- Microservice that serves cached sites from disk/cli- CLI for direct PDS uploads as well as serving with firehose updates/rust-cli- Deprecated Rust CLI for direct PDS uploads with firehose updates/apps/main-app/public- React frontend/packages- Shared packages
How it works#
- Sites stored as
place.wisp.fsrecords in your AT Protocol repo - Files compressed (gzip) and base64-encoded as blobs
- Hosting service watches firehose, caches sites locally
- Sites served via custom domains or
*.wisp.placesubdomains
Development#
# Backend
# bun install will install packages across the monorepo
bun install
bun run dev
.env file for local dev
DATABASE_URL=postgres://postgres:postgres@localhost:5432/wisp
NODE_ENV=development
DOMAIN=https://wisp.place
CLIENT_NAME=Wisp.Place
LOCAL_DEV=true
Local Domain XRPC (ServiceAuth via PDS Proxy)#
apps/main-app exposes domain claim/status XRPC endpoints:
place.wisp.v2.domain.claim(procedure / POST)place.wisp.v2.domain.getStatus(query / GET)
The server validates serviceAuth JWTs (not cookie auth, not direct end-user access JWTs) on /xrpc/*.
Set these env vars in your active .env:
LOCAL_DEV=true
SERVICE_DID=did:web:regentsmacbookair
SERVICE_IDS="#wisp_xrpc"
SERVICE_ENDPOINT=https://regentsmacbookair
Notes:
/.well-known/atproto-didreturnsSERVICE_DID./.well-known/did.jsonpublishes the DID doc, includingserviceentries fromSERVICE_IDS.SERVICE_IDSmust be quoted when it starts with#(otherwise dotenv treats it as a comment).- Service identity keys are stored in
service_identity_keysin Postgres.
IfSERVICE_PUBLIC_KEY_MULTIBASEandSERVICE_PRIVATE_KEY_MULTIBASEare not set, a keypair is generated once and persisted.
Local TLS Requirement (No Auto Cert Generation)#
Some PDS proxy flows require HTTPS on :443 for the proxied service endpoint.
Cert generation is intentionally manual so SANs are explicit and correct for your environment.
Example with mkcert:
mkcert -cert-file certs/dev-cert.pem -key-file certs/dev-key.pem regentsmacbookair localhost 100.64.0.2
Use SANs that match exactly what your PDS will call (hostname and/or IP).
apps/main-app can terminate TLS directly in local dev with:
PORT=443
LOCAL_DEV_TLS=true
LOCAL_TLS_CERT_PATH=./certs/dev-cert.pem
LOCAL_TLS_KEY_PATH=./certs/dev-key.pem
# Hosting service
bun run hosting:dev
# CLI
cd cli
bun install
bun run index.ts
bun build
node dist/index.js
Features#
File Filtering and .wispignore#
Wisp automatically excludes common files that shouldn't be uploaded to your site (like .git, node_modules, .env files, etc.). You can customize this behavior by creating a .wispignore file in your site root.
The .wispignore file uses the same syntax as .gitignore:
# Custom ignore patterns
*.log
temp/
build/
.secret
Default patterns include: .git, .github, .gitlab, .DS_Store, node_modules, .env, cache directories, Python virtual environments, editor swap files, .tangled, and more.
See File Filtering Documentation for details.
URL Redirects and Rewrites#
The hosting service supports Netlify-style _redirects files for managing URLs. Place a _redirects file in your site root to enable:
- 301/302 Redirects: Permanent and temporary URL redirects
- 200 Rewrites: Serve different content without changing the URL
- 404 Custom Pages: Custom error pages for specific paths
- Splats & Placeholders: Dynamic path matching (
/blog/:year/:month/:day,/news/*) - Query Parameter Matching: Redirect based on URL parameters
- Conditional Redirects: Route by country, language, or cookie presence
- Force Redirects: Override existing files with redirects
Example _redirects:
# Single-page app routing (React, Vue, etc.)
/* /index.html 200
# Simple redirects
/home /
/old-blog/* /blog/:splat
# API proxy
/api/* https://api.example.com/:splat 200
# Country-based routing
/ /us/ 302 Country=us
/ /uk/ 302 Country=gb
Limits#
- Max file size: 100MB (PDS limit)
- Max files: 2000
Tech Stack#
- Backend: Bun + Elysia + PostgreSQL
- Frontend: React 19 + Tailwind 4 + Radix UI
- Hosting: Node microservice using Hono
- CLI: Rust + Jacquard (AT Protocol library)
- Protocol: AT Protocol OAuth + custom lexicons
License#
MIT