- TypeScript 91%
- CSS 9%
- Dockerfile <1%
Later #
A small self-hosted read-later web app. Save links, extract readable article content with defuddle.md, cache article snapshots, and read them in a quiet responsive UI.
Stack #
- React Router full-stack framework
- TypeScript
- Bun
- SQLite via Bun's native
bun:sqlite - Drizzle ORM/migrations
- shadcn/ui components
- AT Protocol OAuth
Development #
bun install
cp .env.example .env
bun run db:migrate
bun run dev
Open the local dev server printed by React Router, usually http://localhost:5173.
AT Protocol OAuth requires APP_URL/client_id metadata to be HTTPS and reachable by the OAuth server. For local sign-in testing, point APP_URL at an HTTPS tunnel or deployed preview URL.
Environment #
See .env.example.
APP_URL: public app URL. AT Protocol OAuth uses this as the client metadata base URL.DATABASE_URL: SQLite database path, e.g.file:./data/read-later.db.SESSION_SECRET: long random secret for signing local session cookies.
Database #
Generate migrations after schema changes:
bun run db:generate
Apply migrations:
bun run db:migrate
Back up the app by backing up the SQLite file configured by DATABASE_URL.
Production notes #
AT Protocol OAuth requires APP_URL to be reachable by OAuth servers because the app exposes:
/client-metadata.json/jwks.json/oauth/callback
For production, set a real HTTPS APP_URL and a strong SESSION_SECRET. A plain localhost APP_URL is not accepted by AT Protocol OAuth client metadata validation.
Docker #
docker build -t later .
docker run --rm -p 3000:3000 \
-e APP_URL=https://later.example.com \
-e DATABASE_URL=file:/app/data/read-later.db \
-e SESSION_SECRET=replace-me \
-v later-data:/app/data \
later