Second-screen webapp voor langebaan schaatsfans met live rondetijden, virtuele klassering, eindtijdvoorspellingen en race-analyse.
LapEdge is een webapp die schaatsfans realtime inzicht geeft in wedstrijden door:
- Live rondetijden - Zie elke ronde zodra deze gereden wordt
- Virtuele klassering - Vergelijk rijders die nog bezig zijn met al gefinishte ritten
- Eindtijdvoorspellingen - Geschatte eindtijd op basis van huidige pace (voor afstanden >1500m)
- Referentievergelijkingen - Vergelijk met wereldrecords, baanrecords, Olympische records, en persoonlijke records
- Race-grafieken - Visuele weergave van rondetijden en cumulatieve tijden
- Gender-selectie - Bekijk zowel mannen- als vrouwenwedstrijden
- Countdown-timer - Aftellen naar de start van een wedstrijd
Ideaal voor gebruik als tweede scherm naast een livestream of tv-uitzending.
- Real-time rondetijden van ISU Results en Schaatsen.nl
- Automatische detectie van actieve races
- Ondersteuning voor alle gangbare afstanden (500m - 10000m)
- Status-weergave: "Nog niet gestart", "Live", "Afgelopen"
- SkaterCard: Gedetailleerde rijderinfo met rondetijden, cumulatieve tijden, en voorspellingen
- RaceChart: Grafische weergave van tijdsverschillen per ronde
- RecordTimesPanel: Overzicht van WR, OR (bij Olympische events), TR, en PR
- StandingsPanel: Live klassement met virtuele posities
- Eindtijdvoorspelling: Machine learning-achtige voorspelling o.b.v. huidige pace
- Pace-analyse: Vergelijk rondetijden met referentieronden
- Event-detectie: Automatische detectie van Olympische events voor OR-weergave
- Countdown: Tot op de seconde aftellen naar de start
- Frontend: React 18 + Vite + Chart.js + React Chart.js 2
- Backend: Express.js + Node.js
- Data Sources:
- ISU Results (live.isuresults.eu) - Live race data
- Schaatsen.nl (liveresults.schaatsen.nl) - Backup live data
- World/Olympic/Track records database
- Caching: In-memory caching voor performance
- Node.js 18+
- npm 8+
# Clone de repository
git clone <repository-url>
cd lapedge
# Installeer dependencies
npm install
# (Optioneel) Environment configuratie
cp .env.example .env# Start zowel frontend als backend (aanbevolen)
npm run dev
# Of draai ze apart:
npm run dev:client # Vite dev server (poort 3000)
npm run dev:server # Express API server (poort 3001)Applicatie URLs:
- Frontend: http://localhost:3000
- Backend API: http://localhost:3001/api
De ontwikkelomgeving ondersteunt:
- Hot Module Replacement (HMR) voor instant updates
- Auto-reload bij backend wijzigingen
- CORS voor cross-origin requests
# Maak een productie build
npm run build
# Preview de productie build lokaal
npm run previewDe build wordt geplaatst in de dist/ directory en is geoptimaliseerd voor productie.
Nieuwe wedstrijden voeg je toe in server/config/events.js, in de array EventsConfig.events. Elk event is een object met de volgende velden.
| Veld | Beschrijving |
|---|---|
id |
Unieke string (bijv. wc-berlin-2026), geen spaties |
name |
Weergavenaam (bijv. "World Cup Berlin") |
location |
Locatie/baan (bijv. "Sportforum Hohenschönhausen, Berlin") |
country |
Drielettercode (NED, GER, CAN, …) |
startDate |
Eerste dag, ISO-datum YYYY-MM-DD |
endDate |
Laatste dag, ISO-datum YYYY-MM-DD |
source |
Data-bron: 'isuresults' of 'schaatsen' |
sourceUrl |
Base-URL van de live-uitslagen |
isuEventId(verplicht voor ISU): het event-ID zoals op live.isuresults.eu.- Ga naar de wedstrijd op live.isuresults.eu.
- Het ID staat in de URL:
https://live.isuresults.eu/events/2026_ITA_0003→isuEventId: '2026_ITA_0003'.
sourceUrl: zet op'https://live.isuresults.eu'.
Voorbeeld:
{
id: 'wc-berlin-2026',
name: 'World Cup Berlin',
location: 'Sportforum Hohenschönhausen, Berlin',
country: 'GER',
startDate: '2026-02-06',
endDate: '2026-02-08',
timezone: 'Europe/Berlin',
source: 'isuresults',
sourceUrl: 'https://live.isuresults.eu',
isuEventId: '2026_GER_0001' // uit de URL op live.isuresults.eu
}sourceUrl: base-URL van de live-uitslagen, bijv.'https://liveresults.schaatsen.nl'(of een eventspecifieke URL als die wordt gebruikt).
Voorbeeld:
{
id: 'nk-afstanden-2026',
name: 'NK Afstanden',
location: 'Thialf, Heerenveen',
country: 'NED',
startDate: '2025-12-26',
endDate: '2025-12-30',
timezone: 'Europe/Amsterdam',
source: 'schaatsen',
sourceUrl: 'https://liveresults.schaatsen.nl'
}| Veld | Beschrijving |
|---|---|
startTime |
Starttijd eerste dag (bijv. '14:00') voor countdown |
timezone |
IANA timezone (bijv. 'Europe/Amsterdam') |
isOlympic |
true als het een Olympische wedstrijd is (toont OR in referentietijden) |
Na het toevoegen verschijnt de wedstrijd automatisch in de sidebar (als de einddatum nog niet verstreken is). Herstart de server niet per se; bij npm run dev wordt de config bij een volgende request gelezen.
Haal alle beschikbare wedstrijden op: eerst de events uit de config, daarna komende evenementen van de ISU API (huidige en volgende seizoen). De lijst wordt op startdatum gesorteerd. In de app kies je een wedstrijd via de dropdown.
Response:
[
{
"id": "wcup-heerenveen-2025",
"name": "World Cup Heerenveen",
"location": "Heerenveen",
"startDate": "2025-02-07",
"endDate": "2025-02-09",
"isOlympic": false
}
]Haal beschikbare afstanden op voor een wedstrijd.
Response:
{
"men": [500, 1000, 1500, 5000, 10000],
"women": [500, 1000, 1500, 3000, 5000]
}Haal live race data op voor een specifieke afstand.
Query Parameters:
gender(optioneel):menofwomen(default:women)
Response:
{
"status": "live",
"currentPair": 5,
"skaters": [
{
"name": "Kjeld Nuis",
"country": "NED",
"laps": [
{ "lap": 1, "time": 25.12, "cumulative": 25.12 },
{ "lap": 2, "time": 24.98, "cumulative": 50.10 }
],
"pr": 67.55,
"seasonBest": 67.89,
"prediction": 67.72
}
]
}Haal het huidige klassement op.
Response:
[
{
"position": 1,
"name": "Kjeld Nuis",
"country": "NED",
"time": 67.55,
"diff": 0.00
},
{
"position": 2,
"name": "Laurent Dubreuil",
"country": "CAN",
"time": 67.89,
"diff": 0.34
}
]Haal wereldrecords, baanrecords en Olympische records op.
Query Parameters:
gender(optioneel):menofwomen(default:men)
Response:
{
"distance": 1000,
"gender": "men",
"venue": "Heerenveen",
"isOlympicEvent": false,
"worldRecord": {
"time": 66.53,
"holder": "Pavel Kulizhnikov",
"date": "2020-02-09"
},
"trackRecord": {
"time": 67.01,
"holder": "Kjeld Nuis",
"date": "2023-03-11"
},
"olympicRecord": null
}Haal persoonlijke records (PR) en seizoensbeste (SB) op voor een rijder.
Query Parameters:
distance(optioneel): Specifieke afstand
Response:
{
"skaterId": "kjeld-nuis",
"records": {
"1000": {
"pr": 67.55,
"seasonBest": 67.89
}
}
}Haal rijder informatie op inclusief alle records.
Response:
{
"id": "kjeld-nuis",
"name": "Kjeld Nuis",
"country": "NED",
"records": {
"500": { "pr": 34.32, "seasonBest": 34.55 },
"1000": { "pr": 67.55, "seasonBest": 67.89 },
"1500": { "pr": 103.67, "seasonBest": 104.12 }
}
}Ververs de cache voor live data en/of records.
Body:
{
"type": "records" // of "live", of laat leeg voor beide
}Response:
{
"success": true,
"message": "Cache cleared",
"clearedAt": "2025-02-20T10:30:00.000Z"
}Haal systeem status op.
Response:
{
"recordsCacheSize": 42,
"liveCacheSize": 8,
"uptime": 3600,
"timestamp": "2025-02-20T10:30:00.000Z"
}lapedge/
├── server/
│ ├── adapters/ # Data source adapters
│ │ ├── isuresults.js # ISU Results scraper
│ │ └── schaatsenNL.js # Schaatsen.nl scraper
│ ├── config/
│ │ ├── events.js # Wedstrijd configuratie
│ │ └── records.js # WR/OR/TR database
│ ├── services/
│ │ ├── liveData.js # Live data aggregatie
│ │ └── records.js # Records ophalen & caching
│ └── index.js # Express server + API routes
│
├── src/
│ ├── components/
│ │ ├── Countdown.jsx # Countdown timer naar start
│ │ ├── DistanceSelector.jsx # Afstandskiezer + gender toggle
│ │ ├── EventSelector.jsx # Wedstrijdkiezer
│ │ ├── Header.jsx # App header
│ │ ├── RaceChart.jsx # Tijdsverschil grafiek
│ │ ├── RaceView.jsx # Hoofd race view
│ │ ├── RecordTimesPanel.jsx # WR/OR/TR referenties
│ │ ├── SettingsPanel.jsx # Update interval settings
│ │ ├── SkaterCard.jsx # Rijder details + rondetijden
│ │ └── StandingsPanel.jsx # Klassement sidebar
│ ├── hooks/
│ │ ├── useLiveData.js # Live data polling hook
│ │ └── useSettings.js # Settings persistence hook
│ ├── utils/
│ │ ├── calculations.js # Tijdsverschillen, voorspellingen
│ │ └── timeFormat.js # Tijd formatting (mm:ss.dd)
│ ├── styles/
│ │ └── index.css # Globale styling
│ ├── App.jsx # Main app component
│ └── main.jsx # App entry point
│
├── package.json
├── vite.config.js
├── prd.md # Product Requirements Document
└── README.md # Deze file
- React 18: UI framework met hooks
- Vite: Snelle build tool en dev server
- Chart.js: Canvas-based charting
- React-chartjs-2: React wrapper voor Chart.js
- Express: Web framework
- Cheerio: HTML parsing voor web scraping
- Node-fetch: HTTP requests naar data sources
- CORS: Cross-origin resource sharing
- Concurrently: Parallel scripts draaien
- @vitejs/plugin-react: React support in Vite
- Live rondetijden van ISU Results
- Virtuele klassering
- Eindtijdvoorspellingen
- WR/OR/TR referenties
- Gender selectie
- Race grafieken
- Countdown timer
- 1000m ondersteuning
- Ondersteuning voor meerdere simultane races
- Historische race replay
- Verbeterde voorspellingsalgoritmes
- Push notificaties voor records
- Favoriet rijders tracking
- Persoonlijke instellingen opslaan
- CSV/JSON export van race data
- Multi-language support (EN/NL)
- Dark mode
- Mobile app (React Native)
Contributie is welkom! Open een issue of pull request.
- Gebruik ESM modules (
import/export) - Volg bestaande code stijl
- Test nieuwe features grondig
- Update documentatie bij nieuwe features
MIT
LapEdge gebruikt publiek beschikbare data van:
- ISU Results (live.isuresults.eu) - Live race data
- Schaatsen.nl (liveresults.schaatsen.nl) - Backup live data
- Speedskatingresults.com - Historische records
Dit is een non-commercieel, fan-made project voor persoonlijk gebruik. De applicatie respecteert de robots.txt en rate limits van deze platforms. Alle data blijft eigendom van de respectievelijke organisaties.
Disclaimer: LapEdge is niet geaffilieerd met ISU, Schaatsen.nl of andere schaatsorganisaties. Dit is een onafhankelijk project gemaakt door en voor schaatsfans.
Gebouwd met ❤️ voor schaatsfans wereldwijd.
Data bronnen:
- ISU (International Skating Union)
- Schaatsen.nl
- Speedskatingresults.com