How we built Table Online — a multiplayer backgammon game in Flutter for web, iOS and Android
Engineering lessons from building a real-time game in Flutter, with the same code running in the browser and natively on mobile. State management, server sync, 60fps performance.
A few months ago we shipped Table Online — a classic multiplayer backgammon game you can play directly in your browser at table-online.ro, or as a native app on iPhone and Android. All of it is built in Flutter, with a single source codebase for all three platforms. This article is an engineering look behind the scenes.
Why Flutter for a multiplayer game
The choice wasn’t obvious. For games, many people reflexively recommend Unity or a custom engine. We picked Flutter for three concrete reasons:
- Animations are first-class in Flutter.
AnimationController,AnimatedBuilder,Hero,TweenAnimationBuilder— all motion data for backgammon pieces and dice is already solved by the framework. We didn’t have to write a physics engine. - The web lets you adopt without downloading. The biggest friction in mobile game adoption is installation. With Flutter web, the user lands, taps twice, plays a game. If they like it, they install the app later.
- One codebase, three platforms — and this isn’t just a marketing line. Game logic, move rules and server communication are shared by all three apps and behave identically everywhere.
Architecture
In short:
- Flutter frontend — UI, animations, local input. State management with Riverpod.
- Dart backend on Serverpod — auth, profiles, friends, tournaments, chat, in-app purchases and admin, with a type-safe API shared with the Flutter client.
- Go realtime service — the game authority for live matches: WebSocket connections, one goroutine per game room, every move validated on the server. The client only proposes.
- PostgreSQL and Redis — profiles, match history and the leaderboard in PostgreSQL; sessions, the matchmaking queue and live match state in Redis.
Update (October 2026): at launch the whole stack was Dart, and Serverpod also handled the matches. Later we moved live match traffic into a small Go service that runs next to Serverpod, on the same PostgreSQL and Redis. Everything else is still Dart.
The decision that mattered most: the server is source-of-truth. The client proposes a move, the server decides if it’s valid, then broadcasts the result to both players. This eliminates an entire class of cheating and makes debugging predictable.
Performance: holding 60fps in the browser
Flutter web is harder than native mobile on the performance front. A few things made the difference:
RepaintBoundaryaround the board — we isolate board repaints from the rest of the UI. Moving a piece doesn’t repaint the score.- HTML renderer for text-heavy pages, CanvasKit for the game page — Flutter web supports two renderers and you can pick per build. For the game page, CanvasKit wins clearly.
- Lazy loading assets — piece textures, sounds, victory animations only load when needed.
The most useful lesson
If I were starting over, I’d start with the web variant before mobile. It’s harder (Flutter web has fewer tools and more edge cases), but it forces correct architectural decisions early — bundle size, lazy loading, image optimization. The same decisions make the mobile build better later.
Want to discuss a Flutter project? Get in touch — we reply within one business day. And if you want to see the result in action, hop on table-online.ro and play a round.