an app to share curated trails
sidetrail.app
1# Sidetrail
2
3[Sidetrail](https://sidetrail.app/) is an app to create and share "trails". Create sequential paths with 2-24 stops, walk through them one step at a time, and share them with others.
4
5Built on [AT](https://atproto.com/) protocol: trails, walks, and completions are stored in users' repositories and synced via Jetstream.
6
7## Architecture
8
9Three services:
10
11- **Next.js app** — Web frontend + API. Reads from PostgreSQL, writes to users' ATProto repos via OAuth.
12- **Ingester** — Subscribes to [Jetstream](https://docs.bsky.app/blog/jetstream) and indexes `app.sidetrail.*` records into PostgreSQL.
13- **Realtime** — Rebroadcasts Jetstream events to browser clients for real-time updates on some pages.
14
15## Local Development
16
17### Prerequisites
18
19- Node.js 22.16.0+
20- PostgreSQL
21- Redis
22
23### Setup
24
25```bash
26# Install dependencies
27npm install
28
29# Start Redis
30brew services start redis
31
32# Create the database
33createdb sidetrail
34
35# Copy environment file and configure DATABASE_URL and REDIS_URL
36cp .env.example .env
37
38# Push database schema
39npm run db:push
40
41# Populate the database from the live network (see Syncing below)
42npm run sync
43
44# Start all services (run in separate terminals)
45npm run dev # Next.js app on :3000
46npm run dev:ingester # Jetstream ingester
47npm run dev:realtime # Realtime server
48```
49
50Then open `127.0.0.1:3000` (it has to be `127.0.0.1`, not `localhost`).
51
52### Syncing
53
54The ingester only indexes records as they stream past on Jetstream, and
55Jetstream's replay window is only a few days. Any fresh database — or one
56whose ingester was down for longer than the window — has drifted from the
57network.
58
59`npm run sync` reconciles: it asks the relay which repos contain
60`app.sidetrail.*` records, fetches them from each user's PDS, and makes the
61index mirror the lexicon-valid records currently on the network — adding
62what's missing and pruning what was deleted or doesn't validate. Repos it
63fails to reach are left untouched. Idempotent, safe to rerun anytime
64(including after changing a lexicon).
65
66```bash
67npm run sync # local DB (.env)
68DATABASE_URL=postgres://... npm run sync # any other DB
69```
70
71### Environment Variables
72
73See `.env.example`. For local development, `DATABASE_URL` and `REDIS_URL` are required.
74
75Production requires:
76
77- `PUBLIC_URL` - Your app's public URL
78- `PRIVATE_KEY_ES256` - OAuth signing key (generate with `node scripts/gen-jwk.mjs`)
79- `COOKIE_SECRET` - Session encryption (32+ chars)
80
81## Lexicons
82
83Sidetrail uses three ATProto record types:
84
85- `app.sidetrail.trail` - Trail with embedded stops
86- `app.sidetrail.walk` - User's current walk state (deleted on completion)
87- `app.sidetrail.completion` - Permanent completion record
88
89See [lexicons/LEXICONS.md](lexicons/LEXICONS.md) for details.
90
91## Testing
92
93```bash
94# Create test database (one-time)
95psql -c "CREATE DATABASE sidetrail_test"
96
97npm test
98```
99
100## Scripts
101
102```bash
103npm run dev # Start Next.js dev server
104npm run dev:ingester # Start Jetstream ingester
105npm run dev:realtime # Start realtime server
106
107npm run build # Production build
108npm run db:push # Push schema to database
109npm run db:studio # Open Drizzle Studio
110
111npm run test # Run tests
112npm run check # Lint + typecheck
113npm run sync # Sync the index with the live network
114```
115
116## Deployment
117
118Uses [Railway](https://railway.app/) with three services. One-time setup:
119
120```bash
121railway link --project sidetrail
122```
123
124Then deploy with:
125
126```bash
127npm run deploy:app
128npm run deploy:ingester
129npm run deploy:realtime
130npm run deploy:all # all three, sequentially
131```
132
133## License
134
135MIT