It was originally vanilla JS on the front-end but I changed to Vue.js because it was really inconvenient without something like that. Vue has its own caveats but they're less annoying than nothing. The backend is a single free-tier EC2 server running a single Node.js process, using Websockets. Everything is custom except a canned websocket lib (and Vue).