Real-time Collaborative Map Application
A real-time interactive map application built with Next.js, MapLibre GL, and Geobase that enables multiple users to collaborate on shared maps in real-time.
Real-time Features
This application leverages Geobase’s real-time capabilities to provide:
- Broadcast: Send ephemeral cursor position updates between clients with low latency
- Presence: Track and synchronize active users and their states
- Database Changes: Listen to Postgres changes for collaborative features
Real-time features are powered by Geobase’s globally distributed cluster of Realtime servers.
MouseTracker Component
The MouseTracker component is the core of our real-time collaborative features. It enables:
- Real-time cursor tracking for all connected users
- Pin placement on map click
- User presence tracking with avatars
- Automatic cleanup of stale cursors
Technical Implementation
type CursorPosition = {
userId: string;
x: number;
y: number;
timestamp: number;
};
type PresenceState = {
user: string;
online_at: string;
avatar: string;
};
Real-time Communication
The component utilizes three Geobase channels for different purposes:
- Cursor Broadcasting:
const channel = Geobase.channel('room_01')
.on('broadcast', { event: 'mouse-move' }, ({ payload }) => {
updateCursorPosition(payload)
})
- User Presence:
const presenceChannel = Geobase.channel('presence_01')
.on('presence', { event: 'sync' }, () => {
const state = presenceChannel.presenceState()
updatePresence(state)
})
- Pin Updates:
const pinChannel = Geobase.channel('public:pins')
.on('postgres_changes', { event: 'INSERT', schema: 'public' },
payload => {
addNewPin(payload.new)
}
)
Ensure proper cleanup of channel subscriptions when components unmount to prevent memory leaks.
Performance Optimizations
The component implements several optimizations:
- Throttled Updates: Mouse movements are throttled to prevent channel overflow
const broadcastPosition = _.throttle((position) => {
channel.broadcast('mouse-move', position)
}, 50)
- Automatic Cleanup: Stale cursors are removed after inactivity
const cleanupInterval = setInterval(() => {
const now = Date.now()
const staleTimeout = 5000 // 5 seconds
Object.entries(cursors).forEach(([userId, cursor]) => {
if (now - cursor.timestamp > staleTimeout) {
removeCursor(userId)
}
})
}, 1000)
The cleanup interval ensures the application remains performant even with many simultaneous users.
Map Integration
The component integrates with MapLibre GL through custom markers:
const addCursorToMap = (position: CursorPosition) => {
const el = document.createElement('div')
el.className = 'cursor-marker'
new maplibregl.Marker(el)
.setLngLat([position.x, position.y])
.addTo(map)
}
User Presence System
User presence is managed through a combination of real-time channels and local state:
const initializePresence = async () => {
const userId = generateUserId()
const userState = {
user: userId,
online_at: new Date().toISOString(),
avatar: getUserAvatar(userId)
}
await presenceChannel.track(userState)
}
Ensure proper cleanup of presence tracking when users leave the application.
What’s Included
This blueprint provides a complete real-time mapping solution with:
- Real-Time Cursor Tracking: See other users’ cursor positions on the map in real-time
- User Presence: Track active users with unique avatars and names
- Pin Dropping: Users can drop permanent pins on the map
- Spatial Database: Utilizes PostGIS for efficient geospatial queries
Ensure you have a Geobase project with PostGIS enabled before proceeding.
Development
Prerequisites
To get started, you’ll need:
- Node.js (v14 or higher)
- npm or yarn
- Geobase account with PostGIS enabled
Environment Variables
To connect your app to Geobase, configure these environment variables. Include them in a .env.local
file for local development.
NEXT_PUBLIC_GEOBASE_URL=https://YOUR_PROJECT_REF.geobase.app
NEXT_PUBLIC_GEOBASE_ANON_KEY=YOUR_GEOBASE_PROJECT_ANON_KEY
You can locate the project reference and anon key in your Geobase project settings.
Local Development
- Set Node.js version: Use Node version 21:
nvm use 21
- Install dependencies: Use your preferred package manager:
npm install # or yarn # or pnpm install
- Start the development server with HTTPS enabled:
npm run dev --experimental-https # or yarn dev --experimental-https # or pnpm dev --experimental-https
Note: Without --experimental-https
, the email verification links might redirect to https://localhost:3000/...
, causing errors. You can navigate manually by removing https
from the URL if needed.
Access the project at https://localhost:3000
.
Local Development
Run this SQL in your Geobase SQL editor:
create table public.pins (
id serial,
user_id uuid not null,
x double precision not null,
y double precision not null,
created_at timestamp without time zone null default now(),
geom geometry(Point, 4326),
constraint pins_pkey primary key (id)
);
-- Add spatial indexing
CREATE INDEX pins_geom_idx ON public.pins USING GIST (geom);
Database Setup
# Install dependencies
npm install
# Start development server
npm run dev
Access the application at http://localhost:3000
Local Development
Core Components
The application tracks user presence in real-time:
type PresenceState = {
user: string;
online_at: string;
};
User Presence System
Avatars are generated deterministically based on user IDs:
const getUserAvatar = (userId: string): string => {
return generateAvatar({
style: AVATAR_STYLES[styleIndex],
backgroundColor: BACKGROUND_COLORS[colorIndex],
seed: userId
});
};
Avatar Generation
The database structure supports real-time collaboration with these key tables:
public.pins
: Stores map pin locations
Database Overview
For additional information, refer to:
For support, join our Discord community.