01 / 03
Bus Times
Live bus, Luas and rail times for Ireland
2026 · Expo · React Native · TypeScript · Cloudflare Workers · R2 · SwiftUI · WidgetKit · GitHub Actions
About a week after moving to Ireland, I noticed something that kept bothering me: there was already a public transport app, but I still found myself at the stop wondering if the bus was actually coming. So I built my own. It lists the departures from the stops around you, keeps live times apart from scheduled ones and puts the vehicles on a map, using official TFI data.

Departures

Widgets

Map

§01
Turning a 250 MB feed into something a phone can use
The national GTFS feed is a 250 MB file, far too big to send to a phone. A Node script splits it into about 10,200 small JSON files, one per stop, stored in Cloudflare R2. A Worker serves them, checks GTFS-Realtime on a cron and takes care of caching, kill switches and per-IP limits.
§02
The 503 errors in Cork
While testing in Cork, 26% to 33% of departure requests came back as 503 and the app showed “Couldn't reach server”. It turned out to be CPU: a batch of twenty stops made the Worker parse 3.8 MB of JSON on a cold start, past its 10 ms limit. I split the batch into chunks of eight, each with its own budget, and the errors went to zero.
§03
Live times for 4.3% of departures
At first only 4.3% of departures showed a live time, even though the official feed was working. The Irish feed only reports a delay at some stops of each trip, so I carry that delay forward along the rest of the trip on the server. Live coverage went to 67.9% of departures, with no extra cost per user.
§04
Keeping the widget right while the app is closed
iOS decides when a widget refreshes, so polling isn't an option. A scheduled time never changes, only the delay does. The app gives the widget a timeline of 12 entries covering the next hour, each counting down from the absolute due time, and the minutes stay right with the app closed.
§05
An Apple Watch app in SwiftUI
The watch app is native SwiftUI and never calls the backend. It shows the departures the phone already fetched, sent over WatchConnectivity. Complications can only be updated about fifty times a day, which is why the phone only sends something when the data has actually changed.
§06
Live times breaking without an error
Every so often the national feed changes its trip IDs, and when that happened live times stopped matching the schedule without any warning. Now a GitHub Actions workflow runs twice a week, compares a hash of trips.txt and rebuilds the data only when it changed. A Worker cron couldn't do it: parsing 250 MB takes far more than the 128 MB of memory a Worker gets.








