TL;DR: A local-first React gallery for exploring a Google Takeout-style photo archive. It indexes photo metadata, generates thumbnails, and turns a large personal collection into filterable galleries, timelines, places, trips, albums, and maps.
Travel Albums is a Vite/React interface for exploring a Google Takeout-style photo archive.
- πΌοΈ Browse all indexed photos in grid, row, scroller, day, or adjustments views.
- π§ Group collections into dashboard sections such as albums, countries, cities, people/pets, trips, timeline, tags, and βnow and thenβ.
- π Use EXIF/GPS metadata for location-aware views and nearby-place suggestions.
- π Filter, sort, search, pin, select, favorite, tag, ignore, or mark photos private.
- β¨οΈ Navigate and select photos with keyboard shortcuts.
- ποΈ Explore indexed thumbnails and normalized metadata from Takeout sidecar JSON files.
- ποΈ Inspect EXIF details, maps, charts, collages, and image-negative conversion and adjustments tools.
- Node.js 18 or higher (a current LTS release is recommended).
- npm
npm install
npm run devThis starts the Vite development client at http://localhost:5173.
| Goal | Where to start |
|---|---|
| Browse everything | Open All Photos |
| Discover groups | Open the Dashboard and select a populated section |
| Change presentation | Use the gallery-type control to switch grid, rows, scroller, per-day, or adjustments mode |
| Compare or curate | Turn on selection mode, select photos, then apply favorite/private/ignore/tag actions |
| Navigate efficiently | Use β / β to move the preview; in selection mode, use Enter to toggle the current photo |
| Refresh source data | Reload the indexed data available to the client |
The UI uses hash routing, so it can be hosted from a relative path and its views can be deep-linked with URLs such as #/allPhotos and #/selectedPhotos/:type_name/:id.
.
βββ src/
β βββ components/ # Reusable UI pieces and route host
β βββ context/ # App-wide state providers and persisted stores
β βββ data/ # Routes and geographic reference data
β βββ hooks/ # Fetching, transforms, pipelines, sections, and workers
β βββ layout/ # Header, sidebar, breadcrumbs, status bar, shell
β βββ lib/ # Shared domain types, i18n, storage, and services
β βββ windows/ # Onboarding, search, and gallery flows
β βββ pages/ # Route-level gallery/dashboard/settings views
β βββ routes.ts # Data-driven route normalization and menu generation
βββ vite.config.ts # Vite, aliases, build options, and bundle analysis
- React 19 + TypeScript + Vite power the client;
@/aliasessrc/. - Material UI and Emotion provide the component system and theme-aware styling.
- React Router maps a JSON route catalog to pages, redirects, and menu items.
- React Query wraps client data fetching, while focused React context stores hold gallery state.
- i18next supplies localization wiring.
AppProviders composes settings, theme, notifications, filters, selection, labels, favorites, privacy, and derived gallery-data providers around the route tree. This keeps page components focused on rendering and interaction.
The clientβs core GalleryPhoto model (src/lib/galleryData.ts) includes:
id, albumName, title, batch, takenAt, takenAtTs, width,
latitude?, longitude?, people[], imageUrl, sourceUrl, tiny,
likes, views, description, social[]
Section hooks transform these records into UI-ready groups. A section contains a type, title, and sets of photos; the dashboard and selected routes consume those derived collections rather than reimplementing grouping logic.
- Create a page under
src/pages/. - Register its component key in
src/routes.ts. - Add a matching entry in
src/data/routes.json. - Control visibility, menu inclusion, label, icon, and order from the route JSON.
Place reusable grouping logic in src/hooks/sections/. Compose existing data-transform hooks and return stable, UI-ready section data. The section provider makes the result available to the dashboard, navigation, and selected-gallery routes.
The gallery visualization selector chooses a renderer based on the persisted albumType setting. Keep a new page-local renderer adjacent to its consuming page in a feature-specific components folder; place shared UI in src/components/. Use the existing renderer contracts that accept GalleryPhoto[].
- Keep reusable state logic in
src/hooks/. - Use a focused context store when state is shared across unrelated views.
- Put request wrappers in
src/hooks/remote/and expose loading, error, data, and refresh/cancellation behavior. - Keep source modules out of
src/gallery, which is reserved for volatile gallery data/assets.
Trip Gallery is designed for photo libraries that do not fit comfortably in a single DOM render:
react-virtuosobacks grid and row gallery renderers to virtualize long lists.- Background workers perform expensive grouping and filtering for sections.
- Derived hooks favor memoized, stable data transformations to limit rerenders.
- Vite ignores archive, thumbnail, and sprite folders during development watch;
npm run analyzeemitsdist/stats.jsonfor bundle inspection.
| Command | Description |
|---|---|
npm run dev |
Start the Vite client |
npm run build |
Create a production client build |
npm run preview |
Build and preview the client |
- Keep components small and place reusable components in
src/components/. - Use one React component per file.
- Prefer Material UI component props, theme tokens, and scoped
sxstyling over raw inline styles. - Extract reusable React behavior into
src/hooks/; name hooks withuseβ¦. - Keep transforms deterministic and memoize expensive derived data.
- If
src/galleryis present in a working copy, treat it as volatile gallery data/assets and do not add source modules there. - Do not commit personal paths, archives, thumbnails, generated metadata, or credentials.
- Run
npm run lintandnpm run buildbefore opening a pull request.
The standard build produces static client assets in dist/ with relative asset paths, which supports desktop/embedded packaging.
Built for exploring local travel memoriesβwithout handing your photo library to a cloud service. βοΈ

