A Progressive Web App designed to map action sports locations and user-generated media on a global scale

Designed to fill a gap in the action sports community, Spot Hub enables users to discover spots and street obstacles around the world, and document the history of tricks performed at each location. With a functional MVP version currently in alpha release phase, the app is slated for public release later this year, with additional features and improvements to be rolled out iteratively.

The Spot Hub codebase is currently private for security reasons but code samples are available here and full access to the Github repo can be granted upon request.

Spot Hub spot-hub.app

Demo credentials: spothubdemo@gmail.com / spothub123

Table of Contents

  1. Objectives
  2. Technologies
  3. Performance Optimisations
  4. User Experience
  5. Next Steps

Objectives

  • Optimised Performance: Ensure fast, smooth and lag-free rendering of thousands — potentially tens of thousands — of map pins around the world.
  • Streamlined Content Management: Simplify the process of adding spots, images, and related information.
  • Intuitive Discovery: Enable effortless filtering of spots and search functionality.
  • Cross-Platform Compatibility: Full functionality across Web, Android, and iOS devices.
  • Cost-Efficient Scalability: Scale dynamically while minimising infrastructure expenses.

Technologies

  • Javascript
  • NodeJS
  • React
  • NextJS
  • Jotai
  • MapLibreGL
  • DeckGL
  • Supabase
  • Google OAuth
  • Yup
  • Cloudinary CDN
  • Google Cloud Platform
  • Gemini API
  • React Intersection Observer
  • ShadCN
  • TailwindCSS
  • DexieJS
  • Redis (Upstash)
  • Serwist
  • Netlify
  • Vitest + React Testing Library
  • Sentry
  • ESLint + Prettier
  • Git + GitHub
  • Responsive Design

Performance Optimisations

WebGL

In order for Spot Hub to handle large numbers of map pins while maintaining smooth, lag-free movement and scrolling around the map, the DeckGL library has been implemented. DeckGL is a WebGL-powered library that leverages GPU acceleration for significantly faster rendering and smoother interactivity, resulting in a highly scalable map pin layer.

Images

  • CDN Delivery: Spot Hub uses Cloudinary to store images as their CDN provides low-latency delivery worldwide
  • Image size: Cloudinary is configured to automatically optimise images as they are uploaded (a feature available to admin users only). Images are compressed, converted to WebP format, resized to a maximum dimension of 1000px, as well as watermarked with the Spot Hub logo
  • Thumbnails: Thumbnail versions of spot images are automatically generated by Cloudinary (200x200px) to provide a faster-loading preview image for each each spot
  • Lazy Loading: A combination of the Intersection Observer API and lazy-loading attribute (using NextJS Image component) are used to reduce the number of images loaded on initial page load
  • SVG Icons: SVG's are used for icons throughout Spot Hub to provide light-weight and styleable vector icons that reduce HTTP requests, improve rendering performance, and scale perfectly across all devices
  • Service Worker: Caches images using the browser cache API to reduce HTTP requests

Data Pre-Fetching and Caching Strategy

Spot Hub uses a grid-based system for data fetching in order to simplify data caching. Each grid square has a unique ID based on its coordinates. On initial page load, two concurrent requests for spot data are made to the database:

  1. Fetch a shallow set of spot data for all spots worldwide. Shallow spot data provides enough data to display map pins and their thumbnails but does not contain all the data about each spot. If there are more than 5000 spots worldwide (~1MB data) then multiple paginated requests are made to the database in the background until all spots are fetched. Fetching this shallow spot data up front means that browsing, filtering and displaying map pins worldwide is almost instantaneous.
  2. Pre-fetch complete spot data for grid squares surrounding the visible search area. This could be the users' viewport, or in the example below, is a 25km search radius (pre-fetched grid squares highlighted in green). Grid square ID's along with timestamp and cache depth (shallow or complete) are stored in IndexedDB to prevent future duplicate requests for the same grid squares.
Spot data fetching visualisation

As the user moves their search area (or viewport) around the map, map pins are instantly displayed using the shallow spot data. Complete spot data is pre-fetched in the background after determining which grid squares have not been previously fetched (shown in orange below). These orange grid square ID's are then cross-referenced with the shallow spot data to determine which ones contain spots and only make a database request if any grid squares contain spots, preventing unnecessary calls to the database.

If the user interacts with a map pin before the pre-fetched full spot data has been received then a request is made to the database for that single spot data.

In order to prevent requesting too many grid squares at once when the user zooms out on the map, grid squares are only requested below a certain zoom level.

Spot data fetching visualisation

Local-First Caching Strategy

Spot Hub caching strategy

The flow for fetching spot data:

  1. Check IndexedDB
    1.a. If found, check cache timestamp
    1.b. If data is not stale return data, otherwise continue
  2. Call to Server Action (running on Netlify Edge Server)
  3. Check Redis cache
    3.a. If found, check cache timestamp
    3.b. If data is not stale return data, otherwise continue
  4. HTTP fetch to Supabase
    4.a. Edge Server checks in-memory cache for previous matching request
    4.b. If found, check cache timestamp
    4.c. If data is not stale return data, otherwise continue
  5. Request made to Supabase

User Experience

Image Geolocation

The user experience of adding spots around the globe is streamlined by extracting image geolocation data (if available) to automatically move map pins to their correct location.

Spot upload demonstration

The batch upload features allows up to 50 images to be selected at once. It uses image geolocation data to automatically group images in close proximity together (i.e. photos of the same spot). A drag and drop interface then allows the user to make any corrections to the groups before creating each spot individually.

Batch upload demonstration

AI Image Analysis

To enhance the image tagging process, Google's Gemini AI is used to analyse spot images along with a predefined prompt to generate a set of suggested tags.

AI Tag Suggestions demonstration

Next Steps

Beta Release Goals

  • Instagram API Integration: To display related posts for each map pin
  • Youtube API Integration: To display related Youtube videos for each map pin, with timestamped playback
  • Facebook Auth: Implement Facebook Auth and make it the default login option as it affects Instagram API limits
  • Code Rabbit: For AI powered code reviews
  • PostHog: For Web Analytics, session recording and A/B testing
  • Stripe: For handling a tiered pricing model to advanced app features
  • Vitest/RTL: Improve test coverage across codebase
  • Playwright/Cypress: Add end-to-end test coverage

Speculative Future Enhancements

  • Legend State or Zero Sync for local-first database syncing
  • TypeScript for type safety and improved developer experience as the app grows in complexity
  • Remix or Waku as a more performant alternative to NextJS
  • Svelte, SolidJS or Astro as a more performant alternative to React