cameron.stream#
Personal site for cameron.stream. Built with Hono + JSX, server-rendered, deployed to Fly.io. Blog content is stored as site.standard.document records on ATProtocol (PDS), not as local files.
Setup#
pnpm install
Create a .env file in the project root:
ATP_IDENTIFIER=cameron.stream
ATP_PASSWORD=<bluesky-app-password>
The following are set in fly.toml for production but can be overridden locally:
| Variable | Default | Description |
|---|---|---|
CAMERON_DID |
did:plc:gfrmhdmjvxn2sjedzboeudef |
Author DID |
PUBLICATION_URI |
(see fly.toml) | AT-URI of the site.standard.publication record |
PORT |
3002 (dev) / 8080 (prod) |
Server port |
HOST |
0.0.0.0 |
Server bind address |
SITE_URL |
https://cameron.stream |
Canonical site URL (used for OG tags) |
SEMBLE_HANDLE |
cameron.stream |
Bluesky handle for Semble page |
ENABLE_MARGIN |
unset | Set to "true" to enable margin notes |
Development#
pnpm dev
Starts the dev server at http://localhost:3002 with file watching.
Type checking#
pnpm typecheck
Publishing blog posts#
Blog posts are published as site.standard.document records to your PDS:
pnpm publish
This runs scripts/publish.ts, which reads markdown files and pushes them as AT Protocol records.
Deployment#
Deployed to Fly.io as cameron-stream:
fly deploy
Secrets (ATP_IDENTIFIER, ATP_PASSWORD) must be set via fly secrets set.
Stack#
- Hono -- HTTP framework with JSX support
- tsx -- TypeScript execution (no build step)
- marked + shiki -- Markdown rendering with syntax highlighting
- @atproto/api -- ATProtocol client for PDS reads/writes
- ioredis -- Optional caching layer
Project structure#
src/
index.tsx -- Routes and page shell
data.ts -- Data fetching (PDS, Bluesky API)
markdown.ts -- Markdown rendering pipeline
cache.ts -- Redis/memory cache
components/ -- Page components (JSX)
public/
site.css -- Main stylesheet
host-primitives.css
host-theme.css
theme.js -- Dark/light mode toggle