Skip to Content
Architecture

Architecture

System overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ WordPress (bttpirineus.org) β”‚ β”‚ β”‚ β”‚ PΓ gina amb [map gpx="ruta.gpx"] β”‚ β”‚ ↓ β”‚ β”‚ Plugin PHP β†’ iframe src="..." β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ iframe request β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Next.js app (Vercel) β”‚ β”‚ β”‚ β”‚ /embed?gpx=https://...ruta.gpx β”‚ β”‚ ↓ β”‚ β”‚ EmbedClient.tsx (orchestrator) β”‚ β”‚ β†’ fetches /api/gpx?url=... β”‚ β”‚ β†’ parseGpx() β†’ enrichGpxData() β”‚ β”‚ β†’ passes enriched data to: β”‚ β”‚ - LeafletMap (map + hover) β”‚ β”‚ - ElevationChart (SVG) β”‚ β”‚ - SpeedChart (SVG, if times) β”‚ β”‚ β†’ analytics via /api/analytics β”‚ β”‚ β†’ ShareDropdown + download btn β”‚ β”‚ β”‚ β”‚ /api/analytics (Supabase Postgres) β”‚ β”‚ β†’ POST {gpx, event} β†’ INSERT β”‚ β”‚ β†’ GET ?gpx= β†’ COUNT + GROUP BY β”‚ β”‚ β”‚ β”‚ /analytics (dashboard) β”‚ β”‚ β†’ password-protected β”‚ β”‚ β†’ PostHog HogQL queries β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data flow

  1. WordPress renders [map gpx="URL"] β†’ plugin outputs an iframe pointing to https://{VERCEL}/embed?gpx={encoded_URL}&chart=auto
  2. EmbedClient fetches GPX via /api/gpx proxy, parses with parseGpx(), enriches with enrichGpxData() (computes distances, elevation stats, speeds)
  3. LeafletMap receives pre-parsed enrichedData and hoveredIndex β€” renders track + hover marker synced with charts
  4. ElevationChart / SpeedChart β€” custom SVG charts, hover fires onHover(index) back to EmbedClient
  5. Analytics β€” useAnalytics hook fires view event on mount, tracks downloads/shares, fetches counters from /api/analytics

Why a proxy?

GPX files are hosted on bttpirineus.org. Browsers block cross-origin requests (CORS). The /api/gpx proxy fetches the GPX server-side and serves it to the browser. It only allows URLs from bttpirineus.org for security.

Routes

RouteTypeDescription
/PageHome β€” links to helper, analytics, and demo
/embedPageEmbeddable map viewer. Query params: gpx, chart, page, anchor, title
/helperPageShortcode generator (new, legacy reconvert, batch CSV)
/analyticsPagePassword-protected analytics dashboard
/demoPageEmbed preview tool for quick validation
/docsDocsThis documentation site
/api/gpxAPIGPX proxy. ?url= + optional &dl=1
/api/analyticsAPIPOST: record event. GET: ?gpx= β†’ counters
/api/analytics/dashboardAPIDashboard data via PostHog HogQL
/api/analytics/sessionsAPIPostHog session recordings
/api/analytics/ip-checkAPIInternal user detection

Key files

app/ page.tsx Home page (navigation hub) layout.tsx Root layout (lang="ca", favicon) embed/ page.tsx Server component β€” extracts ?gpx from searchParams EmbedClient.tsx Orchestrator β€” fetch/parse/enrich, hover state, charts, analytics helper/ page.tsx Server component HelperClient.tsx Shortcode generator (3 tabs: new/legacy/batch) analytics/ page.tsx Server component AnalyticsClient.tsx Password gate + dashboard UI components/ Tab components (Overview, Visits, Downloads, Actions, Sessions) demo/ page.tsx Server component DemoClient.tsx Embed preview tool api/ gpx/route.ts GPX proxy β€” validates host, fetches .gpx analytics/route.ts Supabase analytics (insert events, count per gpx) analytics/dashboard/ PostHog HogQL aggregation analytics/sessions/ PostHog session recordings analytics/ip-check/ Internal user detection components/ LeafletMap.tsx Map renderer β€” enrichedData, hover marker, layer picker LeafletMapDynamic.tsx Dynamic import wrapper (SSR disabled) ElevationChart.tsx Custom SVG elevation profile with hover sync SpeedChart.tsx Custom SVG speed chart (if timestamps) ShareDropdown.tsx Share popover (Facebook, X, WhatsApp, email, Garmin, QR) lib/ gpx.ts GPX XML parser β†’ { trackPoints, waypoints } gpx-enrich.ts Enrichment: haversine distance, speed, elevation stats analytics-events.ts Event types, share channels, summary logic use-analytics.ts Analytics hook: dual-write Supabase + PostHog use-layer-preference.ts localStorage-backed tile layer preference tile-providers.ts Map tile layer definitions (Hybrid, Topo, IGN) routes.ts Hard-coded route metadata (12 routes) posthog-query.ts PostHog HogQL client (server-side) posthog-server.ts PostHog Node.js client factory wordpress/ bttpirineus-maps/ bttpirineus-maps.php WordPress plugin β€” [map] shortcode β†’ iframe content/ Documentation (MDX, rendered at /docs)

Security headers

Configured in next.config.ts:

  • /embed: Content-Security-Policy: frame-ancestors 'self' https://bttpirineus.org https://www.bttpirineus.org β€” only allows embedding from the BTT Pirineus WordPress site
  • All other routes: X-Frame-Options: DENY β€” prevents embedding anywhere else