7 min de lecture

Construire une PWA navigation GPS avec Next.js + Mapbox (case study Wayline)

PWANext.jsMapboxTurf.jsGPS
Construire une PWA navigation GPS avec Next.js + Mapbox (case study Wayline)

Le problème : naviguer en évitant certaines zones

Tous les GPS te donnent l'itinéraire le plus rapide. Mais parfois tu veux éviter certaines zones - un chantier qui bloque la rue chaque matin, un quartier que tu préfères contourner, une route où tu sais qu'il y a un radar.

C'est ce besoin qui a donné Wayline : une PWA de navigation GPS où tu définis tes zones d'évitement, et l'application recalcule des itinéraires qui les contournent automatiquement.

Voici les choix techniques que j'ai faits, et pourquoi.

La stack : pourquoi pas une app native ?

Pour de la nav GPS, le réflexe c'est React Native ou Flutter. J'ai choisi PWA Next.js + Mapbox pour plusieurs raisons :

  • Pas de validation App Store : déploiement instantané, mises à jour sans review
  • Géolocalisation native via l'API Web : navigator.geolocation fonctionne aussi bien qu'une app native sur iOS et Android récents
  • Mapbox GL JS : moteur de rendu vectoriel ultra-rapide, mêmes capacités que les SDK natifs
  • Service Worker : je peux cacher la carte et les routes pour un usage offline
  • Pas de friction d'install : l'utilisateur ouvre l'URL, ajoute à l'écran d'accueil, c'est plié

Le seul vrai trade-off : pas d'accès aux notifications push iOS hors PWA installée. Pour Wayline ce n'est pas critique - le GPS s'utilise en sortant, pas en arrière-plan.

Calculer un itinéraire avec évitement de zones

C'est le cœur du projet. Mapbox propose une API de routing (Directions API) qui prend des waypoints et retourne le trajet optimal. Mais elle ne sait pas "éviter telle polygone".

L'astuce : on calcule un trajet de base, on regarde s'il traverse une zone d'évitement, et si oui, on insère un waypoint qui force le détour.

Pour vérifier l'intersection entre la ligne du trajet et la polygone, j'utilise Turf.js :

import * as turf from '@turf/turf';

function routeIntersectsZone(routeGeoJSON, zonePolygon) {
  return turf.booleanIntersects(routeGeoJSON, zonePolygon);
}

function findDetourWaypoint(zonePolygon) {
  const center = turf.centroid(zonePolygon);
  const bbox = turf.bbox(zonePolygon);
  // Place le waypoint légèrement à l'extérieur du nord de la zone
  return [bbox[0] - 0.01, bbox[3] + 0.01];
}

L'algo simplifié :

  1. Appel Mapbox Directions avec origine + destination
  2. Pour chaque zone définie : check si le trajet la traverse
  3. Si oui, calcule un waypoint de détour
  4. Re-appel Directions avec le waypoint inséré
  5. Retourne le trajet final

Ça reste 1-2 appels API par calcul d'itinéraire, mais l'expérience est fluide (<300ms en moyenne).

Stockage local avec SQLite

Pour les zones d'évitement et l'historique des trajets, j'ai choisi SQLite côté serveur plutôt que Postgres ou un service managé. Pourquoi :

  • Wayline est un projet perso, pas besoin de scaler à des milliers d'users
  • SQLite tourne dans le même process Node.js (better-sqlite3), zero connection overhead
  • Backups simples : un seul fichier .db, je peux le rsync sur le NAS
  • Lecture quasi-instantanée pour les zones de l'utilisateur (typiquement 5-20 zones)

Pour les requêtes spatiales (est-ce que cette zone contient ce point ?), Turf.js fait le job côté serveur. Pas besoin de PostGIS.

const Database = require('better-sqlite3');
const db = new Database('wayline.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS zones (
    id INTEGER PRIMARY KEY,
    user_id TEXT NOT NULL,
    name TEXT,
    geojson TEXT NOT NULL,
    created_at INTEGER
  );
`);

const getUserZones = db.prepare('SELECT * FROM zones WHERE user_id = ?');

Mode offline avec Service Worker

C'est le vrai différenciateur PWA vs app native classique. Avec un service worker, je peux cacher :

  • Le shell de l'app (HTML/CSS/JS)
  • Les tiles Mapbox déjà visités (Mapbox GL gère ça nativement avec un cache configuré)
  • Les zones de l'utilisateur (stockées en localStorage)
  • Le dernier itinéraire calculé

Résultat : l'utilisateur peut continuer à voir sa carte et son trajet même en perte de réseau, ce qui arrive régulièrement en zone rurale ou sous-sol.

Setup minimal du service worker (avec next-pwa) :

// next.config.js
const withPWA = require('next-pwa')({
  dest: 'public',
  runtimeCaching: [
    {
      urlPattern: /^https:\/\/api\.mapbox\.com\/styles\//,
      handler: 'CacheFirst',
      options: { cacheName: 'mapbox-tiles', expiration: { maxAgeSeconds: 7 * 24 * 60 * 60 } }
    }
  ]
});

Les pièges rencontrés

1. navigator.geolocation peut mentir. Sur certains navigateurs mobiles, la précision varie de 5m à 500m d'une frame à l'autre. J'ai dû implémenter un Kalman filter simplifié pour lisser le tracking.

2. Les tiles Mapbox sont chères au-delà du free tier. Si Wayline avait du trafic massif, je passerais à un fournisseur de tiles open-source (Stadia Maps, Protomaps). Pour le moment, le free tier suffit largement.

3. iOS Safari et le service worker. Apple a ses propres règles : les service workers se déchargent agressivement, le cache n'est pas garanti. Pour les usages critiques (un cycliste qui doit pouvoir consulter son itinéraire), je recommande de doubler avec du localStorage.

Le bilan

MétriqueValeur
Bundle JS initial~140 KB gzip
Time to Interactive<2s sur 4G
Recalcul d'itinéraire avec évitement250-350 ms
Fonctionnement offline✅ Oui (carte + dernier trajet)
Stockage côté user~3 MB après plusieurs trajets

Ce que j'en retiens

Pour 95% des cas d'usage GPS, une PWA bien faite remplace une app native. Le seul vrai intérêt d'une app native pour un projet de navigation, c'est l'usage en arrière-plan continu (genre Waze qui te parle en conduite). Si tu n'as pas besoin de ça, la PWA est plus rapide à développer, plus facile à déployer, et offre une expérience comparable.

Mapbox + Turf.js, c'est une combinaison puissante : Mapbox pour le rendu et le routing standard, Turf.js pour tout ce qui sort de leur API (intersections, buffers, distances custom). Avec ces deux briques, tu peux construire à peu près n'importe quelle logique géospatiale dans le navigateur.