Raul CariniFull Stack Developer

Rail Radar

January 14, 2026 (2 months ago)
0 views
Rail Radar home page showing interactive map of European railway stations

Have you ever wondered where your train is right now, or why it's delayed? While flight tracking services like Flightradar24 have made it incredibly easy to track planes in real-time, there wasn't an equivalent solution for trains in Europe. That observation sparked the creation of Rail Radar, an interactive web application that tracks trains in real-time across 7 European countries using official railway data from over 5,700 stations.

What started as an Italy-only project using RFI data has grown into a multi-country platform covering Italy, Switzerland, Finland, Belgium, the Netherlands, Germany, and the United Kingdom, each powered by their respective national railway APIs.

Core Features

Rail Radar is designed to be a comprehensive, real-time railway companion:

Architecture: A Monorepo Approach

Rail Radar is structured as a monorepo managed with pnpm and Turborepo, allowing shared code and types across multiple applications.

rail-radar/
├── apps/
│   ├── api/           # Cloudflare Workers API (Hono)
│   ├── web/           # Next.js 16 frontend (Mapbox GL)
│   └── studio/        # Next.js admin app (MapLibre GL)
└── packages/
    ├── data/          # Shared station data (GeoJSON) and TypeScript types
    ├── ui/            # Shared React component library (Base UI)
    └── typescript-config/  # Shared TypeScript configs

Why a Monorepo?

  1. Shared Types: TypeScript types like Station and Train are defined once in packages/data and shared across the API, web app, and admin studio. No more type mismatches between frontend and backend.
  2. Station Database: Over 5,700 stations stored as GeoJSON features with properties like type (rail, metro, light rail), importance ranking, and country-prefixed IDs (e.g., IT1728, CH06013, UKLIVST).
  3. Shared UI Components: The packages/ui library built on Base UI provides consistent components across both the web app and admin studio, with Recharts for data visualization and Sonner for toast notifications.
  4. Atomic Changes: Updating station data or types propagates across all dependent apps in a single commit.

The API Layer: Cloudflare Workers

The backend is a serverless API running on Cloudflare Workers, chosen for its global distribution, generous free tier, and zero server management.

The API does more than just proxy data. It normalizes responses from 7 different railway APIs into a consistent format, handles caching with fine-tuned TTLs (25 seconds for live train data, 24 hours for station GeoJSON), and tracks analytics via Cloudflare's Analytics Engine.

Built with Hono

The API uses Hono, a lightweight framework optimized for edge environments. It provides Express-like ergonomics with full TypeScript support, middleware for CORS and rate limiting, and seamless Cloudflare Workers integration.

import { Hono } from "hono";
import { cors } from "hono/cors";

const app = new Hono<{ Bindings: Env }>();

app.use("/*", cors());

app.get("/stations", async (c) => {
  const query = c.req.query("q");
  const country = c.req.query("country");
  const stations = await searchStations(query, country, c.env);
  return c.json(stations);
});

app.get("/stations/:id", async (c) => {
  const stationId = c.req.param("id");
  const type = c.req.query("type") || "departures";
  const data = await getStationData(stationId, type, c.env);
  return c.json(data);
});

export default app;

Data Source Integration

Each country required a different integration approach. Italy's RFI data involved reverse-engineering the ViaggiaTreno API. Switzerland's transport.opendata.ch provides a clean REST API. Finland's Digitraffic offers well-documented endpoints. Belgium's iRail is community-maintained. The Netherlands and Germany required working with NS and Deutsche Bahn's respective APIs, while the UK uses the National Rail LDBWS (Live Departure Boards Web Service).

The API normalizes all of these into a consistent Train type with scheduled times, actual times, delays, platforms, and route information, so the frontend doesn't need to know which country's API it's dealing with.

The Frontend: Next.js 16 and Mapbox

The web application is built with Next.js 16 and React 19, using Server Components by default and client components only for interactive parts like the map. The React Compiler is enabled for automatic optimization.

For map rendering, Rail Radar uses Mapbox GL JS via react-map-gl. The admin studio uses MapLibre GL JS for its map editing interface.

Performance at Scale

Rendering 5,700+ stations across 7 countries required aggressive optimization:

  1. Clustering: At lower zoom levels, nearby stations are grouped into clusters. Instead of showing 50 individual markers in Milan, you see one cluster marker.
  2. Importance-Based Visibility: Stations have an importance ranking (1-4) and type classification. Major stations appear at all zoom levels, while smaller stops only appear when zoomed in, controlled by derived minzoom values in the GeoJSON data.
  3. Viewport Culling: Only stations within the current viewport are processed for interactions.
  4. Lazy Loading: Detailed station and train information is fetched on-demand via SWR with 30-second auto-refresh.
  5. URL-Based State: Map position, zoom, and selected station are all encoded in the URL via nuqs, making every view shareable and bookmarkable.

With over 5,700 stations across multiple languages, search needed to be robust. Instead of relying on a library like Fuse.js, I built a custom fuzzy search engine using Damerau-Levenshtein distance for typo tolerance:

The Admin Studio

The admin studio (apps/studio) is a separate Next.js application for managing station data. It provides a MapLibre-based interface for editing station properties, verifying coordinates, and managing the GeoJSON database. It integrates with Wikipedia for enriching station metadata and supports the GeoJSON migration workflow.

What's Next

Rail Radar continues to grow. Some planned improvements:

If you have ideas for other features, I'm always open to suggestions on the GitHub repository.

Conclusion

What began as a tool for tracking Italian trains has evolved into a multi-country European railway platform. The expansion from 2,400 Italian stations to over 5,700 stations across 7 countries brought unique challenges: normalizing data from vastly different APIs, building a search engine that works across multiple languages, and keeping the map performant as the dataset more than doubled.

The key architectural decisions that made this growth possible:

If you're interested in European rail travel or just want to explore the railway network, visit railradar24.com and start tracking trains. The project is open source on GitHub, where contributions and feedback are always welcome.