Source of www.freemap.sk, the web map portal maintained by OZ Freemap Slovakia.
Freemap is a free, non-commercial web map application built on OpenStreetMap data. Its focus is a detailed outdoor map for hiking, cycling, cross-country skiing and horse riding across Europe, complemented by many additional base and overlay layers and by tools for search, route planning, drawing and measurement, GPX viewing, live tracking, personal maps, map embedding, and export.
This repository is the frontend — a React single-page application. It talks to:
- the Freemap API server (
freemap-v3-api) for accounts, photos, tracking, saved maps and purchases, and - external tile, routing (GraphHopper) and geocoding (Nominatim / Overpass) services.
- React 19 + Redux Toolkit (application state is reflected in the URL hash)
- Leaflet / react-leaflet with MapLibre GL for vector layers
- TypeScript, type-checked with
tsc - rspack bundler, Biome for linting/formatting, pnpm as the package manager
- Zod schemas for validation (see CLAUDE.md for conventions)
- Node.js 22+
- pnpm
- A running Freemap API server for features that depend on the backend (login, photos, tracking, saved maps, export); the map itself renders without it.
-
Map
local.freemap.skto127.0.0.1in your system hosts file (on Linux and macOS this is/etc/hosts):127.0.0.1 local.freemap.sk -
Install dependencies:
pnpm install
-
Start the dev server (it compiles protobufs, syncs language files, and rebuilds on change):
pnpm start
-
Trust the development CA certificate in your browser (the dev server is served over HTTPS).
-
Open the application at https://local.freemap.sk:9000/.
The build target is selected with the DEPLOYMENT environment variable:
- unset or
dev— development build forlocal.freemap.sk(uses local backend / base URLs). www— production build forwww.freemap.sk(wires up the production backend, GraphHopper, Matomo, Sentry and Facebook app IDs).- any other value (e.g.
next) — a production-optimized build that still uses the local / default URLs.
pnpm install
DEPLOYMENT=www pnpm buildThe static web resources are emitted into the dist/ directory.
Deploying www.freemap.sk is done with the dep script, which runs a www production build, injects and uploads Sentry source maps, and rsyncs dist/ to the server:
pnpm depThe production nginx site configs are checked into this repo under etc/nginx/sites-available/ as a reference for what is actually deployed:
www.freemap.sk— the main site.www.freemap.eu— the.eumirror (additionally allows CORS for the GlitchTip/Sentry origin).
Both serve the static build from /home/freemap/www and share the same cache-header policy, which is critical to get right — mismatched headers pin users to stale hashed asset names and break the layout after a deploy:
- Entry HTML (
index*.html, the/redirect), service workers (sw.js,upload-sw.js) andassets-manifest.jsonare servedCache-Control: no-store, max-age=0— they are unhashed and must never go stale. - Content-addressed assets (rspack emits 16-char
[contenthash]/[chunkhash]names) aremax-age=31536000, immutable. *.png/*.jpgget a one-yearmax-age(withoutimmutable).
The configs also handle: HSTS / Referrer-Policy headers, HTTP→HTTPS redirect and HTTP/3 (QUIC) via Certbot-managed TLS, per-user directory listings under /~<user>/, search-engine sitemap rewrites for bots on /, /mbtiles offline-map downloads, and a reverse proxy for /tiles/parametric-shading.
When changing build-output naming, the service worker, or the asset manifest, update these configs in the same change set.
Server-side scheduled jobs are checked in under etc/systemd/system/ as a reference for what is actually deployed (like the nginx configs above).
-
freemap-wikimedia-import.service/freemap-wikimedia-import.timer— the monthly Wikimedia Commons photo import. It runs on the API server (fm6) as thefreemapuser and executes the backend's built importer (build/wikimedia/importWikimedia.jsinfreemap-v3-nodejs-backend), which streams the monthly Commonsgeo_tags+page+image+ Structured-Data (mediainfo) dumps into thewikimediaPicturetable — filtered to real photographs (a photo-extension whitelist plus title filters that drop orthophoto/DOP survey grids and astronaut/space imagery) — and atomically swaps the fresh table in (zero downtime; the independentwikimediaRating/wikimediaCommenttables are untouched). Alongside each photo's coordinates it imports the metadata the map colorizes by (and the direction markers):capturedAt(EXIFDateTimeOriginal, falling back to the SDCP571inception date, since the image dump externalizes rich EXIF out of reach),uploadedAt,authorId(numeric Commons actor id),azimuth(EXIFGPSImgDirection) andlicense(SDCP275). The uploader's name isn't in any public dump, so it stays client-side via the Commons API.Install / update on the server:
sudo cp etc/systemd/system/freemap-wikimedia-import.{service,timer} /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now freemap-wikimedia-import.timer systemctl list-timers freemap-wikimedia-import.timer # next run sudo systemctl start freemap-wikimedia-import.service # run once now journalctl -u freemap-wikimedia-import.service -f # watchNotes:
TimeoutStartSec=0is required (the import runs several hours — it downloads and streams the ~17 GBimagedump and the ~75 GB Structured-Datamediainfodump on top ofgeo_tags+page— well past the default oneshot timeout); it runsnodedirectly withEnvironmentFile=/etc/freemap.conf(the same env the API service uses, supplyingMARIADB_*) rather than thedotenvx-wrappedpnpm run import:wikimedia, so no decryptable.envis needed on the server;Nice/IOSchedulingClass=idlekeep it from starving the live DB. It runs whateverbuild/is deployed, so the filters stay current with each backend deploy.Faster downloads (recommended):
dumps.wikimedia.orgthrottles to ~4.5 MB/s per connection (per-connection, not per-IP), which makes the 75 GBmediainfodump the multi-hour bottleneck. The your.org mirror serves the same dumps at ~50 MB/s single-connection and carries theother/wikibase/(mediainfo) tree, so point the importer at it by setting these in/etc/freemap.conf(the dump URLs aregetEnv-overridable — see the backend'simportWikimedia.ts). This is a pure config change and stays fully streaming (no temp file, no parallel-segment code); it cuts themediainfophase from ~4.6 h to well under an hour:WIKIMEDIA_GEO_TAGS_DUMP_URL=https://dumps.wikimedia.your.org/commonswiki/latest/commonswiki-latest-geo_tags.sql.gz WIKIMEDIA_PAGE_DUMP_URL=https://dumps.wikimedia.your.org/commonswiki/latest/commonswiki-latest-page.sql.gz WIKIMEDIA_IMAGE_DUMP_URL=https://dumps.wikimedia.your.org/commonswiki/latest/commonswiki-latest-image.sql.gz WIKIMEDIA_MEDIAINFO_DUMP_URL=https://dumps.wikimedia.your.org/other/wikibase/commonswiki/latest-mediainfo.json.gzA mirror's
latestcan lag the origin by a day or two (fine for the monthly import); the code defaults stay ondumps.wikimedia.org(canonical), so this is an opt-in override.
Most deployment-specific values are derived from DEPLOYMENT (see above). The remaining overrides:
FM_MAPSERVER_URL— base URL of the Mapnik tile server, without a trailing slash (defaulthttps://outdoor.tiles.freemap.sk; e.g.http://localhost:4000for local rendering).PREVENT_ADS— if set (to any value), disables the ad banner.
Deeper reference docs live in doc/:
- Drawing feature export/import format mapping — how points, lines and polygons round-trip through GPX and GeoJSON, including the curated icon mappings for Garmin / OsmAnd / Locus.
src/static/llms.txt is a hand-maintained, user-facing description of the application (its functions, modals and map layers) for AI assistants, served at www.freemap.sk/llms.txt per the llmstxt.org convention. Keep it in sync when changing user-visible behavior — see CLAUDE.md.
Contributor notes for Claude Code (workflow, conventions): CLAUDE.md.
The English master is src/translations/en.tsx; other locales are edited via their *.template.tsx files (the plain *.tsx files are generated — see CLAUDE.md).
Files holding translatable text:
- rspack.config.ts — texts in
htmlPluginProps - src/translations/en-shared.json
- src/translations/en.tsx
- src/components/supportUsModal/translations/en.tsx
- src/globalErrorHandler.ts — bottom error message
- src/osm/osmTagToNameMapping-en.ts
- https://github.com/FreemapSlovakia/freemap-mapnik/blob/develop/legend.js