MBTA Transit Dashboard

Overview

real-time MBTA dashboard that shows when your train is actually coming. also weather and some news headlines I guess but the departures are the whole point. built it as a single static page backed by a proxy that smashes a few APIs into one response so no API key ever touches a browser, that part is kind of important honestly. it runs in two places — publicly at mbtadash.nbkelley.com and as a 24/7 kiosk on a Raspberry Pi at work. same codebase for both just totally different ways of getting it onto a screen

Why I Built It

I take the T to work and got real tired of opening an app every time I wanted to know if I should leave now or could finish what I was doing. the posted schedule is basically fiction so you cant trust that. wanted something I could glance at and immediately know. and I wanted it to look decent on a screen all day, not like some ugly utility panel from 2006

the two channel thing wasnt planned at all, it just kind of happened. mbtadash.nbkelley.com was the original, built it for myself to pull up on my phone at home and honestly thats still the one I actually use. then at some point I realized the same page would work as a permanent display at the office, so I threw it on a Raspberry Pi running Anthias pointed at an internal nginx server. same dashboard two totally different lives and I didnt have to build anything twice

How It Works

the whole thing is a static HTML page with some CSS and vanilla JS. no framework no build step no nothing. one request to /api/data gets the browser everything it needs, all the departures and weather and news in a single JSON blob. the heavy lifting is a tiny Node/Express proxy that goes out and fetches from three APIs in parallel, then normalizes whatever comes back and caches it

ComponentRole
Static frontendSingle HTML page, vanilla CSS/JS, portrait-first layout
Node/Express proxyMerges MBTA v3, OpenWeatherMap, and RSS feeds into one /api/data endpoint
NginxServes static files and reverse-proxies /api/ to Express
pm2 + systemdKeeps the proxy alive across reboots

the proxy

the proxy is basically the only moving part. fires off three fetches at the same time so the slowest one sets the pace instead of adding them all up

SourceWhat I get from itCache TTL
MBTA v3 APIReal-time departure predictions and route patterns30s
OpenWeatherMapCurrent conditions and daily forecast10 min
RSS feedsMassLive Boston and State House News, first few items each5 min

API keys live in a .env file outside the git repo, loaded when the process starts. a git pull cant touch them. took me one bad draft with the key sitting right in server.js to figure out why that mattered

the responses are cached in memory. if the RSS feed is slow whatever, departures and weather still come through. the client just gets one clean object, no chained fetches no nothing

mbtadash.nbkelley.com — the home channel

this is the one I actually use every day. deployed through Cloudflare and styled to match the rest of nbkelley.com so it doesnt look like some separate project. when Im at home wondering if I should leave now or finish my coffee I pull it up on my phone. its public but the API keys are all server-side so theres nothing sensitive happening in the browser at all

the work kiosk

at work the same dashboard runs as a permanent display on a Raspberry Pi 3B+ with Anthias, which is that digital signage thing that used to be called Screenly OSE. portrait 1080x1920 mounted on the wall always on. this one is completely internal, served from an nginx box at the office. not on my home network and definitely not public. just transit.intra.plgt.com on the work LAN

the Pi 3B+ has 788MB of RAM total. thats not a lot. Anthias runs in Docker and eats most of it so youre really working with nothing. every optimization decision in the frontend was basically me staring at htop and trying to make numbers go down

Anthias uses Qt WebEngine for rendering instead of actual Chrome and it has its own weird little quirks. fonts render wider so everything is set to Arial. WebP support is shaky, had to just try it and see. CSS animations are choppier than youd think. and emoji rendering basically doesnt work at all so all the icons are Bootstrap Icons instead. Qt WebEngine debugging was honestly just me going “why does it look like that” over and over until I fixed enough things

theres a cron job that restarts the Anthias viewer container every 6 hours to clear out whatever memory has built up. not elegant. works fine.

station grouping was wrong the first time

the first version grouped everything by line. all Red Line together all Orange Line together. looked nice on a diagram and made absolutely zero sense when you actually use it. when youre standing at Park Street you dont care what color the train is, you care what time the next one leaves from the station youre at

the rewrite groups by physical station. each card gets a header with the station name. departures inside sort by time with a little colored line pill, RL OL BL GL-B all that. the pills use the official MBTA colors and theyre the only color on an otherwise monochrome card, your eye just goes straight to them

departures are filtered to a 10 minute walking radius from wherever Im starting. not showing me trains I cant walk to in time

layout

portrait 1080x1920. 2-column CSS Grid for the station cards with a full-width static T map underneath. cards flex their height to content so a shorter card doesnt leave dead space

design iterations

the thing went through four visual designs:

  1. Line-based cards with big MBTA color backgrounds — horizontal layout meant for a TV. colors were kinda loud and grouping by line just wasnt useful
  2. Minimalist white with grey dividers — MBTA colors only on the small line pills. cleaner but still had the line grouping problem
  3. Station-based cards — finally fixed the grouping. cards represent physical stations with all routes listed per station. way more useful immediately
  4. Portrait 2-column grid — where it landed. flex columns, time-sorted departures, line pills, full-width map at the bottom

Challenges

Result

the dashboard pulls live departures weather and news through the proxy and renders everything in one paint. I check mbtadash.nbkelley.com on my phone at home and the Pi at work runs it 24/7 on the wall with a cron job doing a docker restart on the viewer container every 6 hours to flush memory. same static page and same Express proxy serving both, theres a Cloudflare tunnel for the public one and an internal nginx instance on the work VLAN for the kiosk. someday Ill probably replace the Pi but the 3B+ has been doing this for months and its fine