We use cookies for analytics and advertising to understand traffic and improve EarthGuessr. You can accept or reject — essential cookies always stay on. Privacy & cookies

All posts
Behind the ScenesJanuary 15, 20263 min readEarthGuessr Team

How EarthGuessr Works: Frontend, Maps, and Game State

A tour of the implementation: a React app, map renderers, Supabase game functions, and HTML generated during the build.

EarthGuessr runs as a React application built with Vite and TypeScript. React Router selects the page, and game screens keep their display state in React. The database stores sessions and results so a score can be checked independently of what the browser displays.

Two kinds of map rendering

The landing-page globe uses globe.gl and Three.js. The game uses a map component to display satellite tiles and a separate view for choosing a location. These parts have different jobs: displaying the place to inspect and letting the player submit a geographic guess.

The repository includes both MapLibre GL and Mapbox GL dependencies. The satellite raster source in the map component points to Google's tile endpoint. Describing all the imagery as Mapbox imagery would be inaccurate.

A guess goes to a database function

For a standard solo game, the client calls the Supabase start_game function with the selected mode, time limit, and optional location filters. The response supplies a session identifier and the first round's location information.

When a player guesses, the browser sends the session, round number, and guessed coordinates to submit_guess. The function calculates and stores the result. The browser uses the response to update the score and decide whether to advance or show the completed game.

Daily, streak, and knockout play have their own paths. Multiplayer adds shared lobby state and messages about round progress; it is not simply several independent solo games displayed together.

Accounts and multiplayer

Supabase handles authentication and the database connection. The multiplayer entry page requires authentication to create a lobby. The initial lobby request specifies eight players; the lobby settings and database validation govern the final capacity.

Realtime channels notify connected clients about changes and submitted rounds. They also need to reconnect after interruptions. A WebSocket connection alone does not guarantee that every client has the latest state.

The blog is rendered during the build

Vercel runs the prerender command, which compiles the app and launches a local preview. A Playwright browser visits the public routes, and the script writes the rendered HTML into the build output. Blog pages therefore have HTML available before a visitor runs the app's JavaScript.

The script also produces social cards, a sitemap, and a feed. Gameplay routes use the application entry point. The deployment configuration has specific route rewrites, rather than one rule for every URL.

Analytics and checks

PostHog records product events. The authentication code also identifies signed-in users with account properties, so the implementation should not be described as collecting only anonymous session activity. Vercel Speed Insights is included for performance measurement.

TypeScript checks the application before the production bundle is built. Playwright tests exercise browser flows. These checks cover different failure modes; passing a build does not establish that multiplayer works under every network condition.

More in Behind the Scenes

Related reading

Ready to explore?

See the world from above and test your geography skills on a 3D globe.