A self-hosted travel archive and publishing application built with React, TypeScript, and MapLibre. Travel Map combines an interactive public website with a local visual editor for creating trips, managing places, attaching photos, validating data, and preparing a static site for the web.
The live demo shows one completed map. A fresh clone starts empty and is ready for your own trips, places, and media. Read the user guide for the complete setup and authoring workflow.
- Public app: React 19, React Router, MapLibre GL, React Map GL, ApexCharts, React Image Gallery, and React Photo Album.
- Editor: React 19, DnD Kit, Downshift, Fuse.js, Framer Motion, MapLibre GL, and continuous dataset validation.
- Shared model: TypeScript domain classes, schemas, parsing, and validation
in the
@travelmap/coreworkspace package. - Data: Portable JSON documents for settings, countries, cities, trips, photo manifests, and transport companies.
- Styling: SCSS, PostCSS, shared design tokens, responsive layouts, and light/dark themes.
- Internationalization: i18next and react-i18next with English and Italian interfaces and support for additional authored locales.
- Media tools: Python, Pillow, optional ffmpeg video thumbnails, local media output, and optional BunnyCDN uploads.
- Build system: Vite, TypeScript, pnpm workspaces, and Node.js 22.
- Hosting: Static files, GitHub Pages, or Docker with Nginx.
- Quality: ESLint, Prettier, Vitest, Knip, React Doctor, and repository-level type checking.
- An interactive world map with visited, lived-in, future, and home cities.
- Trip routes, transport legs, city tooltips, and detailed itineraries.
- Browsable trips and places, grouped into useful views.
- Photo galleries and a full-screen lightbox.
- A chronological timeline and travel statistics for distance, transport, countries, continents, currencies, time zones, companies, UNESCO sites, and media.
- Responsive mobile and desktop layouts, light and dark themes, and English and Italian interfaces.
- Create and edit trips through a visual itinerary workspace.
- Search a worldwide city database or add a place from a Google Maps link.
- Add stops, layovers, dates, transport modes, airlines, ferries, and logos.
- Import GPX, KML, GeoJSON, CSV, JSON, and plain-text itineraries.
- Validate the complete dataset and apply guided fixes before publishing.
- Configure branding, locales, map defaults, media paths, and travel categories.
- Autosave changes to disk and create or restore local backups.
The editor is an authoring tool for your machine; it is not part of the published site. Visitors receive only the generated static application and the media you choose to host.
The optional Python uploader prepares photos and video thumbnails for the web.
Media can be served from the included local media/ directory or from a CDN
such as BunnyCDN. See Adding photos for the
complete workflow.
- Run the editor locally and describe your trips, places, and site settings.
- The editor stores your content as portable JSON documents under
data/. - Travel Map validates and compiles those documents into the public app.
pnpm buildproduces a static site underapps/travel-map/dist/.- Serve that directory from a static host, GitHub Pages, or Docker.
There is no production database, account system, or application server. Your content stays in files you control.
You need Node.js 22.22 or newer and pnpm 11.18 or newer.
git clone https://github.com/thisispivi/TravelMap.git
cd TravelMap
pnpm install
cp apps/travel-map/env/.env.example apps/travel-map/env/.env
pnpm devOpen the two local applications:
| Address | Application |
|---|---|
| http://localhost:5173 | Your public Travel Map |
| http://localhost:5174 | The local editor used to create and manage data |
Start in the editor, open Settings to name the site, then create your first
trip. Changes autosave into data/ and appear in the public app after a reload.
For the complete first-run walkthrough, read the Travel Map user guide.
Run the repository-wide quality gate before pushing:
pnpm check
pnpm build
python -m compileall -q scripts/uploader
pnpm --filter travel-map security:auditpnpm check runs type checking, linting, formatting, unit tests, and dead-code
analysis across every workspace project, plus React Doctor on the public app.
Git hooks run staged-file checks before commits and the full suite before
pushes; CI repeats the same checks.
Authored JSON is untrusted. @travelmap/core owns the Zod schemas for
countries, cities, trips, photo manifests, and site configuration, and
buildWorld() is the one place the dataset is parsed β a malformed document
fails the build with the field named, rather than surfacing as a blank panel.
The other validated boundaries are the editor's local write endpoints, the
responses the editor reads back, localStorage, and build-time environment
variables.
Parse new external data at its entry point, infer the TypeScript type from the owning schema instead of declaring it twice, and keep authorization separate from shape validation. Values that never leave the application do not need another layer.
CODING_GUIDELINES.md is the canonical standard for anything else β architecture, naming, state, styling, accessibility, testing, and what the tooling enforces for you.
Build the production application:
pnpm buildThe deployable output is written to apps/travel-map/dist/. Upload that folder
to any static hosting service. Travel Map uses hash-based routing, so it does
not require server-side route handling.
Photos are hosted separately from the application bundle. Copy
apps/travel-map/env/.env.example to apps/travel-map/env/.env, then set
VITE_CDN_PATH to the URL or path that serves your media before building. See
Choosing where photos are served from
for local and CDN examples.
Publish the site directly to the repository's gh-pages branch:
pnpm --filter travel-map deployghRead Publishing to GitHub Pages before deploying, especially if your map includes photos.
The included Compose project builds Travel Map and serves it through Nginx:
docker compose -f docker/compose.yml up --buildOpen http://localhost:8080. The local media/ directory is mounted read-only
inside the container, so adding or replacing a photo does not require a rebuild.
Changes to trips, cities, or settings do require rebuilding because data/ is
compiled into the static site.
See Running the finished site with Docker for details.
Personal content is deliberately separate from the application source:
data/contains site settings, places, trips, photo manifests, and logos.media/contains self-hosted photos and video thumbnails..data-snapshots/contains editor backups.
These directories are excluded from git, so personal travel history and media are not accidentally committed when you update or share the application. Back them up independently; editor snapshots cover authored data but not the media files themselves.
| Guide | Covers |
|---|---|
| User guide | Installation, editor workflow, trips, places, photos, validation, backups, and publishing |
| Create a trip | Stops, transport legs, layovers, and future trips |
| Add photos | Photo processing, manifests, local media, and CDN uploads |
| Site settings | Branding, languages, map defaults, city roles, and statistics |
| Dataset reference | The portable files created by the editor and how they are organized |
| Uploader reference | Image processing, video thumbnails, and BunnyCDN configuration |
| Editor notes | Editor architecture and behavior for contributors |
| Coding guidelines | The canonical coding standard, and what pnpm check enforces automatically |
.
βββ apps/
β βββ travel-map/ Public static site
β βββ travel-map-editor/ Local visual editor
βββ packages/core/ Shared travel model and validation
βββ data/ Your authored content (gitignored)
βββ media/ Your self-hosted media (gitignored)
βββ scripts/uploader/ Photo and video preparation tools
βββ docker/ Nginx image and Compose configuration
βββ docs/ User documentation