Photographs, video, and the IPFS question
Two different kinds of file, handled two different ways, for one reason: git is
excellent at versioning things you might edit and terrible at storing things
you never will.
Photographs — in git
Live beside the entry that uses them:
sites/blog/src/content/photos/2026/harbour.mdx
sites/blog/src/content/photos/2026/images/harbour-01.jpg
referenced as ./images/harbour-01.jpg.
Astro takes it from there: AVIF and WebP at 480/800/1200/1600 px wide, a
srcset so a phone downloads a phone-sized file, and all metadata stripped
from the output. A 22 kB source becomes a 3 kB file at phone size.
Commit the full-resolution export. A few hundred photographs is a repository of
a few hundred megabytes, which is fine for years. If it ever stops being fine,
move older sets to the server the same way video works — the components do not
care where a file came from.
Before committing, make sure there is no GPS in them. npm run build
enforces this and will refuse to proceed. See docs/06-privacy.md.
Video — on the server
Video does not go in git. A single two-minute clip can outweigh every word
you will write in a year, and unlike a photo it is never edited in place.
# upload
rsync -av --progress -e "ssh -i ~/.ssh/id_ed25519_eratic" \
harbour.mp4 harbour.jpg \
root@178.104.116.235:/opt/web/sites/eratic/media/video/2026/
then reference it by path:
<Video
src="video/2026/harbour.mp4"
poster="video/2026/harbour.jpg"
alt="Boats moving in a harbour at dusk"
caption="Ten minutes before the light arrived."
/>
Served from media.eratic.ro, same server, no third party. preload="metadata"
means a page with five clips downloads five headers, not five videos.
Use ambient for a short silent loop that should behave like a moving
photograph — no controls, autoplay, muted.
Encoding
H.264 in MP4 plays everywhere. Sensible defaults:
ffmpeg -i source.mov -vf "scale='min(1600,iw)':-2" \
-c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart harbour.mp4
ffmpeg -i harbour.mp4 -vframes 1 -q:v 3 harbour.jpg # poster frame
+faststart matters — without it the browser downloads the whole file before
the first frame appears.
/opt/web/sites/eratic/media/ is the only copy of these files. Keep your
originals somewhere else too.
IPFS, when ird ships it
The media layer was built with this in mind, so the migration is a
configuration change rather than a rewrite.
Every media reference goes through mediaUrl() in
packages/core/src/config.ts, which understands three forms:
video/2026/harbour.mp4 → https://media.eratic.ro/video/2026/harbour.mp4
ipfs://bafy… → {MEDIA.ipfsGateway}/bafy…
https://… → unchanged
To move to IPFS:
- Set the gateway in
packages/core/src/config.ts:
ts
export const MEDIA = {
base: 'https://media.eratic.ro',
ipfsGateway: 'https://ipfs.eratic.ro/ipfs',
};
- Pin a file, then change that one reference from
video/2026/harbour.mp4 to
ipfs://bafy….
- Add the gateway origin to
media-src and img-src in the CSP in
deploy/eratic.caddy, or the browser will block it — which is the CSP doing
its job.
Files migrate one at a time. Both forms resolve through the same function,
so there is no flag day and no moment where half the site is broken. If a
gateway turns out to be slow, change references back.
The one thing to think about before committing to it: a public gateway sees
which content-addressed blocks a reader requests, which is a different privacy
posture from a server that keeps no logs. Running your own gateway on the same
box keeps the current guarantees.