I will pick a better name later.

README.md

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