A static photography portfolio/gallery built with Astro and TypeScript.
This is the Astro rewrite of the earlier Gatsby-based version.
- 100% static output
- Album index with responsive cards and optimized image rendering
- Album detail pages with masonry layout
- Photo detail pages with:
- full-resolution zoom modal
- downloadable original image
- EXIF summary (camera/lens/settings/location fields)
- Dark/light theme toggle with persistent preference
- Astro
- TypeScript
- React islands (
@astrojs/react) - Bulma + react-bulma-components
astro:assets+ Sharp for image optimizationexifrfor EXIF extraction
Main content location:
src/assets/images/*→ each subfolder is treated as an album
Example:
src
├── images
│ └── input
│ ├── Album1 -> /mnt/real/path/to/album1
│ └── Album2 -> /mnt/real/path/to/album2
Routes:
/albums list/albums/[slug]/album page/albums/[album_slug]/[slug]/photo page/gear/gear page/map/map page
Edit src/config.tsx:
title: site/page titlegears: camera/lens data used by/gearflatten_index: navbar label/count behaviorenable_map_page,enable_gear_page: toggle navbar entriesexifr_filter: choose which EXIF keys are shown on photo detail pages
Site-level config:
- astro.config.mjs (
site,base, output mode, integrations)
- Install dependencies
pnpm install - Add your albums, Put album folders (or symlinks) under
src/assets/images/. - Start development server
pnpm dev - Build static site
pnpm build. Build output goes todist/andpnpm preview.
- Album and photo sorting are based on
DateTimeOriginalwhen available. - If EXIF parsing fails for a photo, it still gets included with fallback metadata.
Sharp's prebuilt binaries bundle libheif with the aom AV1 decoder, which cannot
decode some AVIF streams (e.g. 10-bit / YUV 4:4:4 HDR exports). Such images fail to
decode at build time, so Astro silently falls back to the original file
(Sharp could not optimize image ... Sharp doesn't support this format) and emits the
full-size source for every responsive width — i.e. no resizing/cropping.
The fix is to make Sharp use a system libvips whose libheif has the dav1d decoder:
- Install system libvips (it pulls in libheif + dav1d), e.g. on Arch:
sudo pacman -S libvips node-addon-apiandnode-gypare kept indevDependenciesso Sharp can build from source. Onpnpm install, Sharp auto-detects the global libvips (when>= 8.17.3) and compiles against it instead of downloading the aom-only prebuilt.
Caveat: this ties the build to a system libvips. If libvips is missing at install time, Sharp silently reverts to the bundled aom build and the issue returns — so any other build machine / CI must install libvips first.
Upstream tracking: replacing the bundled aom with dav1d (decoder) + rav1e/svt-av1
(encoder) is proposed but not yet shipped — see
lovell/sharp-libvips#97. Until
that lands, all published @img/sharp-libvips-* prebuilts remain aom-only.
Inspired by Chris Benninger's project fussel, then rewritten in TypeScript for Gatsby, and now migrated to Astro.
MIT — see LICENSE.