<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham Saurabh</title>
    <description>The latest articles on DEV Community by Shubham Saurabh (@shubhsaur).</description>
    <link>https://dev.to/shubhsaur</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F851789%2F677de9de-8c4c-4cad-b5bc-1e1c14dcc5b6.jpeg</url>
      <title>DEV Community: Shubham Saurabh</title>
      <link>https://dev.to/shubhsaur</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubhsaur"/>
    <language>en</language>
    <item>
      <title>What I Learned From Building a Multiplayer Card Game</title>
      <dc:creator>Shubham Saurabh</dc:creator>
      <pubDate>Fri, 18 Sep 2026 18:25:43 +0000</pubDate>
      <link>https://dev.to/shubhsaur/what-i-learned-from-building-a-multiplayer-card-game-4had</link>
      <guid>https://dev.to/shubhsaur/what-i-learned-from-building-a-multiplayer-card-game-4had</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7h38sj9ostt3hdfigwez.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7h38sj9ostt3hdfigwez.jpg" alt="Dealopoly card shuffler" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first version of Dealopoly sounded simple.&lt;/p&gt;

&lt;p&gt;A player gets some cards, plays them, collects rent, steals a property or two, and eventually someone wins.&lt;/p&gt;

&lt;p&gt;Then I tried to make it work for multiple people playing from different browsers.&lt;/p&gt;

&lt;p&gt;That changed the problem completely.&lt;/p&gt;

&lt;p&gt;What looked like a card-game UI became a distributed-systems problem with a UI attached to it.&lt;/p&gt;

&lt;p&gt;Dealopoly is a real-time multiplayer card gaming platform built around games such as Monodeal and Lowdeck. The current architecture separates the web application, an authoritative Fastify WebSocket game server, a deterministic game engine, persistent storage, and Redis-based realtime infrastructure.&lt;/p&gt;

&lt;p&gt;The most useful things I learned weren't really about cards.&lt;/p&gt;

&lt;p&gt;They were about &lt;strong&gt;state, authority, failure, and boundaries&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. A Multiplayer Game Is Mostly a State Problem
&lt;/h2&gt;

&lt;p&gt;For a single-player game, the browser can get away with being the center of the universe.&lt;/p&gt;

&lt;p&gt;The UI knows the current state.&lt;/p&gt;

&lt;p&gt;The player clicks a button.&lt;/p&gt;

&lt;p&gt;The state changes.&lt;/p&gt;

&lt;p&gt;The screen updates.&lt;/p&gt;

&lt;p&gt;Multiplayer breaks that assumption immediately.&lt;/p&gt;

&lt;p&gt;Imagine this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Player A Browser
       │
       │ "play this card"
       ▼
   Game Server
       │
       ▼
   Game Engine
       │
       ▼
   New Game State
       │
   ┌───┴────┐
   ▼        ▼
Player A  Player B
Browser   Browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there is one important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who gets to decide whether the move is valid?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer in Dealopoly is the &lt;strong&gt;server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The browser requests an action.&lt;/p&gt;

&lt;p&gt;The server decides whether that action is legal.&lt;/p&gt;

&lt;p&gt;The game engine applies the rules.&lt;/p&gt;

&lt;p&gt;The resulting state is then delivered back to the players.&lt;/p&gt;

&lt;p&gt;That distinction turns out to be one of the most important architectural decisions in the whole project.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Browser Should Ask, Not Decide
&lt;/h2&gt;

&lt;p&gt;Consider a button like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;playCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be tempting to let the client perform some validation first and then update its local state optimistically.&lt;/p&gt;

&lt;p&gt;For a normal application, that can be a perfectly reasonable pattern.&lt;/p&gt;

&lt;p&gt;For a competitive multiplayer game, it creates a dangerous boundary.&lt;/p&gt;

&lt;p&gt;A modified browser could simply send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"playCard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cardId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deal-breaker"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it isn't that player's turn,&lt;/li&gt;
&lt;li&gt;the card isn't in their hand,&lt;/li&gt;
&lt;li&gt;they have already used their available actions,&lt;/li&gt;
&lt;li&gt;the target isn't valid,&lt;/li&gt;
&lt;li&gt;or the current game phase doesn't allow the move.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the server needs to treat every command as untrusted input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  │ command
  ▼
Server
  │
  ├── Is player in this room?
  ├── Is it their turn?
  ├── Is this command legal?
  ├── Is the target valid?
  └── Can the current game state accept it?
             │
             ▼
        Game Engine
             │
       ┌─────┴─────┐
       │            │
    rejected      accepted
       │            │
       ▼            ▼
     error       next state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful general lesson is bigger than games:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The client can request an operation, but the system that owns the state should be responsible for deciding whether it happens.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can apply the same principle to payments, inventory, permissions, workflow systems, and collaborative applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. This Led Me to Separate the Game Engine From the WebSocket Server
&lt;/h2&gt;

&lt;p&gt;One of the decisions I value most in the codebase is keeping the game engine independent from the interface.&lt;/p&gt;

&lt;p&gt;The engine doesn't need to know whether the request came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a browser,&lt;/li&gt;
&lt;li&gt;a WebSocket,&lt;/li&gt;
&lt;li&gt;a bot,&lt;/li&gt;
&lt;li&gt;a test,&lt;/li&gt;
&lt;li&gt;or something else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It receives state and a command.&lt;/p&gt;

&lt;p&gt;Then it produces either a rejection or the next state and associated events.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CommandResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameEvent&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the rules can be tested without running a web server.&lt;/p&gt;

&lt;p&gt;For example, a rule test can exercise a payment scenario without needing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → WebSocket → Server → Redis → Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can simply test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Game State
    +
Command
    ↓
Game Engine
    ↓
Next State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the most important part of the system much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Deterministic Rules Are More Valuable Than Clever UI
&lt;/h2&gt;

&lt;p&gt;A card game has a surprisingly large number of interacting rules.&lt;/p&gt;

&lt;p&gt;A player might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;play a property,&lt;/li&gt;
&lt;li&gt;add money to a bank,&lt;/li&gt;
&lt;li&gt;charge rent,&lt;/li&gt;
&lt;li&gt;trigger a reaction,&lt;/li&gt;
&lt;li&gt;respond with "Just Say No",&lt;/li&gt;
&lt;li&gt;choose a target,&lt;/li&gt;
&lt;li&gt;make a payment,&lt;/li&gt;
&lt;li&gt;finish a property set,&lt;/li&gt;
&lt;li&gt;and potentially win.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI can make all of this &lt;em&gt;look&lt;/em&gt; simple.&lt;/p&gt;

&lt;p&gt;The engine cannot.&lt;/p&gt;

&lt;p&gt;The Dealopoly game engine therefore has explicit rule modules for areas such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setup
draw
property
rent
payment
reactions
win condition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation matters because rules tend to grow faster than the UI suggests.&lt;/p&gt;

&lt;p&gt;A seemingly harmless change such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Allow another card type to affect rent."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can ripple through payment resolution, reactions, turn state, and win conditions.&lt;/p&gt;

&lt;p&gt;Putting those rules into explicit domain code gives each change somewhere obvious to live.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Reactions Are Where a "Simple Turn" Stops Being Simple
&lt;/h2&gt;

&lt;p&gt;One of the more interesting parts of the game is reaction handling.&lt;/p&gt;

&lt;p&gt;Suppose Player A charges rent.&lt;/p&gt;

&lt;p&gt;Player B can respond.&lt;/p&gt;

&lt;p&gt;Then Player A may have another response.&lt;/p&gt;

&lt;p&gt;Suddenly a single action isn't really:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;play card
↓
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Player A action
      ↓
pending resolution
      ↓
Player B response window
      ↓
response accepted?
   ┌──┴──┐
   │     │
  yes    no
   │     │
   ▼     ▼
counter  resolve
   │
   ▼
new response window
   │
   ▼
final resolution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is effectively a small state machine.&lt;/p&gt;

&lt;p&gt;And that is a useful lesson beyond games:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whenever an operation can pause, wait for another actor, be rejected, or be countered, model that as state instead of trying to hide everything inside one function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It makes the system much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Hidden Information Changes How You Broadcast State
&lt;/h2&gt;

&lt;p&gt;Another problem appears once the server knows more than a player should know.&lt;/p&gt;

&lt;p&gt;The server might know every player's hand.&lt;/p&gt;

&lt;p&gt;Player A shouldn't.&lt;/p&gt;

&lt;p&gt;That means broadcasting the complete game state to every client would be a security problem.&lt;/p&gt;

&lt;p&gt;Dealopoly therefore creates a &lt;strong&gt;player-specific masked view&lt;/strong&gt; of the game state.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Full Game State
                       │
            ┌──────────┼──────────┐
            ▼          ▼          ▼
         Player A    Player B   Player C
          View        View        View
            │          │          │
            ▼          ▼          ▼
        own hand    own hand    own hand
        visible     visible     visible
        info        info        info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is that masking isn't only about hiding cards.&lt;/p&gt;

&lt;p&gt;Certain cards can contain information that should be represented differently depending on who is looking at them.&lt;/p&gt;

&lt;p&gt;That is a good reminder that authorization isn't always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allowed / forbidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The same underlying state must produce different representations for different viewers.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That pattern appears in dashboards, admin systems, collaborative tools, financial applications, and many other products.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Realtime Doesn't Mean "Just Use WebSockets"
&lt;/h2&gt;

&lt;p&gt;Adding WebSockets makes messages realtime.&lt;/p&gt;

&lt;p&gt;It does not automatically make the system reliable.&lt;/p&gt;

&lt;p&gt;A player can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refresh the browser,&lt;/li&gt;
&lt;li&gt;lose Wi-Fi,&lt;/li&gt;
&lt;li&gt;switch networks,&lt;/li&gt;
&lt;li&gt;close a laptop,&lt;/li&gt;
&lt;li&gt;background a mobile browser,&lt;/li&gt;
&lt;li&gt;or disappear completely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The game still exists.&lt;/p&gt;

&lt;p&gt;So reconnect behavior becomes part of the product.&lt;/p&gt;

&lt;p&gt;Dealopoly tracks room membership and disconnect state, including a grace period before a disconnected player is replaced by a bot.&lt;/p&gt;

&lt;p&gt;The important mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECTED
    │
    │ disconnect
    ▼
DISCONNECTED
    │
    │ reconnect in time
    ├──────────────────► CONNECTED
    │
    │ timeout
    ▼
BOT / LEAVE FLOW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small state transition prevents a temporary network problem from instantly becoming a game-ending event.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Redis Became Useful for More Than Caching
&lt;/h2&gt;

&lt;p&gt;When I initially thought about Redis, it was easy to put it into the "cache" bucket.&lt;/p&gt;

&lt;p&gt;The actual architecture made it more interesting.&lt;/p&gt;

&lt;p&gt;Dealopoly uses Redis for things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;room state,&lt;/li&gt;
&lt;li&gt;active-room indexing,&lt;/li&gt;
&lt;li&gt;disconnect timers,&lt;/li&gt;
&lt;li&gt;and Pub/Sub for propagating room events between server instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Game Server A
      │
      │ publish
      ▼
 Redis Pub/Sub
      │
      ├───────────────┐
      ▼               ▼
Game Server B     Game Server C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters when the game server isn't a single process anymore.&lt;/p&gt;

&lt;p&gt;A room doesn't necessarily belong to one permanently fixed machine.&lt;/p&gt;

&lt;p&gt;The application needs a way for instances to communicate about the same room.&lt;/p&gt;

&lt;p&gt;The broader lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once an application becomes distributed, in-memory state starts becoming a coordination problem.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is when systems such as Redis become much more than a simple key-value cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Bots Should Use the Same Rules as Humans
&lt;/h2&gt;

&lt;p&gt;One architectural choice that pays off quickly is making bots interact with the same game engine.&lt;/p&gt;

&lt;p&gt;A bot should not have a secret shortcut like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWinner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should have to make legal moves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Game State
              │
       ┌──────┴──────┐
       ▼             ▼
    Human          Bot
       │             │
       │ command     │ decision
       └──────┬──────┘
              ▼
         Game Engine
              │
              ▼
       Legal / Invalid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the bot a useful constraint:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the game engine remains the authority for everyone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The bot can become smarter without changing the rules.&lt;/p&gt;

&lt;p&gt;That separation also makes bot simulations possible.&lt;/p&gt;

&lt;p&gt;The repository contains tests around bot behavior and game simulations, which is useful because multiplayer bugs aren't always reproducible by clicking around manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Simulation Is One of the Best Tools for Game Logic
&lt;/h2&gt;

&lt;p&gt;A UI test might tell me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I clicked this and the card moved."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simulation can ask a much more interesting question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can thousands of legal games complete without producing an impossible state?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is especially useful for systems with many interacting rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seed
 ↓
Create Game
 ↓
Choose Legal Move
 ↓
Apply Move
 ↓
Validate State
 ↓
Repeat
 ↓
Game Over
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deterministic seeds make the process reproducible.&lt;/p&gt;

&lt;p&gt;When a simulation fails, the useful thing isn't simply that something broke.&lt;/p&gt;

&lt;p&gt;You can potentially replay the same state and investigate the exact sequence that led there.&lt;/p&gt;

&lt;p&gt;That is a powerful debugging pattern for any state-heavy system.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Persistence and Realtime State Have Different Jobs
&lt;/h2&gt;

&lt;p&gt;Another lesson was not to treat every piece of state as having the same lifecycle.&lt;/p&gt;

&lt;p&gt;Some state needs to be extremely fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current room
current turn
pending reaction
active connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some state needs to survive much longer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;players
completed games
history
statistics
leaderboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That naturally leads to different responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Application
                     │
          ┌──────────┴──────────┐
          ▼                     ▼
       Redis                 PostgreSQL
    fast / ephemeral       durable / persistent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact split will vary by product, but the underlying question is useful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How long does this state need to live, and who needs it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question often tells you where it belongs.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. A Good Monorepo Is About Boundaries, Not Folders
&lt;/h2&gt;

&lt;p&gt;Dealopoly is organized as a monorepo with separate applications and shared packages.&lt;/p&gt;

&lt;p&gt;At a high level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dealopoly/
├── apps/
│   ├── web/
│   └── game-server/
│
└── packages/
    ├── game-engine/
    ├── db/
    ├── redis/
    ├── shared/
    └── ui/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful part isn't the directory tree by itself.&lt;/p&gt;

&lt;p&gt;It's the ownership behind it.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apps/web&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realtime server&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apps/game-server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game rules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;packages/game-engine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;packages/db&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis infrastructure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;packages/redis&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared domain types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;packages/shared&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared UI primitives&lt;/td&gt;
&lt;td&gt;&lt;code&gt;packages/ui&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This prevents a common problem in growing projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Everything imports everything.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, each layer gets a reason to exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. The Most Difficult Bugs Aren't Always the Most Complicated Code
&lt;/h2&gt;

&lt;p&gt;Some of the hardest problems came from interactions between otherwise reasonable systems.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI state
   +
server state
   +
reconnect
   +
timers
   +
pending reaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each individual piece can look fine.&lt;/p&gt;

&lt;p&gt;The difficult part is asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when all five occur at the same time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A player can disconnect during a pending reaction.&lt;/p&gt;

&lt;p&gt;A timer can expire while the browser is reconnecting.&lt;/p&gt;

&lt;p&gt;A stale client can send a command after another player has already changed the state.&lt;/p&gt;

&lt;p&gt;This is why state transitions and server authority matter so much.&lt;/p&gt;

&lt;p&gt;The happy path is easy.&lt;/p&gt;

&lt;p&gt;The edges are the real product.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. What I Would Keep in Mind When Building Another Realtime Product
&lt;/h2&gt;

&lt;p&gt;After working through Dealopoly, these are the principles I would carry into the next realtime application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the domain logic pure
&lt;/h3&gt;

&lt;p&gt;The game engine shouldn't care about React, WebSockets, Redis, or PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Domain
  ↓
Infrastructure
  ↓
Interface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI
  ↕
Game Rules
  ↕
Redis
  ↕
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Treat clients as untrusted
&lt;/h3&gt;

&lt;p&gt;A browser is a user interface, not an authority.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model intermediate states explicitly
&lt;/h3&gt;

&lt;p&gt;Especially when operations can be paused, rejected, countered, or resumed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design for disconnects early
&lt;/h3&gt;

&lt;p&gt;Realtime systems aren't really realtime if they only work on perfect networks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test state transitions, not only screens
&lt;/h3&gt;

&lt;p&gt;A beautiful game can still have a broken engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make infrastructure replaceable
&lt;/h3&gt;

&lt;p&gt;The less your domain model knows about Redis, WebSockets, or a specific database client, the easier it is to evolve the system later.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. The Biggest Thing I Learned
&lt;/h2&gt;

&lt;p&gt;I started thinking about Dealopoly as a game.&lt;/p&gt;

&lt;p&gt;At some point, I realized I was actually building a small distributed system.&lt;/p&gt;

&lt;p&gt;The cards were just the domain.&lt;/p&gt;

&lt;p&gt;The difficult parts were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authority
state transitions
concurrency
reconnection
hidden information
timers
persistence
failure recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that changed how I think about realtime products in general.&lt;/p&gt;

&lt;p&gt;The UI is what players see.&lt;/p&gt;

&lt;p&gt;The interesting engineering is what keeps the UI honest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The easiest way to understand some of these decisions is to play the game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play Dealopoly live:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://dealopoly.vercel.app/" rel="noopener noreferrer"&gt;https://dealopoly.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete source code is also available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/shubhsaur/dealopoly" rel="noopener noreferrer"&gt;https://github.com/shubhsaur/dealopoly&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building something real has a funny way of exposing the parts of software engineering tutorials tend to skip.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a Modern React Application in 2026</title>
      <dc:creator>Shubham Saurabh</dc:creator>
      <pubDate>Thu, 17 Sep 2026 20:49:04 +0000</pubDate>
      <link>https://dev.to/shubhsaur/building-a-modern-react-application-in-2026-1c64</link>
      <guid>https://dev.to/shubhsaur/building-a-modern-react-application-in-2026-1c64</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c" alt="Modern React application architecture" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modern frontend applications are no longer just collections of components.&lt;/p&gt;

&lt;p&gt;As applications grow, developers need to think about &lt;strong&gt;architecture, performance, state management, accessibility, testing, and developer experience&lt;/strong&gt; from the beginning.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through some practical principles for building a modern React application that can scale without becoming difficult to maintain.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Good frontend architecture isn't about adding more abstractions. It's about making the right things easy to change.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Start With a Clear Architecture
&lt;/h2&gt;

&lt;p&gt;Before writing components, define the major responsibilities of your application.&lt;/p&gt;

&lt;p&gt;A typical application might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/
├── components/
├── features/
├── hooks/
├── lib/
├── services/
├── types/
└── styles/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Each directory should have a clear responsibility.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;components/&lt;/code&gt; — reusable UI components&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/&lt;/code&gt; — business-specific functionality&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hooks/&lt;/code&gt; — reusable React hooks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services/&lt;/code&gt; — API and external service communication&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;types/&lt;/code&gt; — shared TypeScript types&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/&lt;/code&gt; — utilities and infrastructure helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact structure isn't important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The important part is having boundaries.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Type Your Application With TypeScript
&lt;/h2&gt;

&lt;p&gt;TypeScript becomes increasingly valuable as an application grows.&lt;/p&gt;

&lt;p&gt;Instead of passing loosely structured objects around your application:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
const user = {&lt;br&gt;
  name: "Shubham",&lt;br&gt;
  email: "shubham@example.com",&lt;br&gt;
};&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;define explicit types:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
interface User {&lt;br&gt;
  id: string;&lt;br&gt;
  name: string;&lt;br&gt;
  email: string;&lt;br&gt;
  avatarUrl?: string;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now your editor and compiler can help catch mistakes before they reach production.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small example
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
function getUserDisplayName(user: User) {&lt;br&gt;
  return user.name || "Anonymous";&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This may look unnecessary for a small project, but strong types become extremely useful when multiple developers and features interact with the same data.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Keep Server State Separate From UI State
&lt;/h2&gt;

&lt;p&gt;One common mistake is treating every piece of state as React component state.&lt;/p&gt;

&lt;p&gt;Consider an application that needs to manage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;API data&lt;/li&gt;
&lt;li&gt;Filters&lt;/li&gt;
&lt;li&gt;Modal visibility&lt;/li&gt;
&lt;li&gt;Form state&lt;/li&gt;
&lt;li&gt;Temporary UI interactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These states have different lifecycles.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Typical Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server state&lt;/td&gt;
&lt;td&gt;User profile&lt;/td&gt;
&lt;td&gt;Query/cache layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL state&lt;/td&gt;
&lt;td&gt;Search/filter&lt;/td&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form state&lt;/td&gt;
&lt;td&gt;Email input&lt;/td&gt;
&lt;td&gt;Form library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI state&lt;/td&gt;
&lt;td&gt;Modal open&lt;/td&gt;
&lt;td&gt;React state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global client state&lt;/td&gt;
&lt;td&gt;Shopping cart&lt;/td&gt;
&lt;td&gt;Store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keeping these responsibilities separate can dramatically reduce unnecessary complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Performance Is About Reducing Work
&lt;/h2&gt;

&lt;p&gt;Performance optimization doesn't always mean using &lt;code&gt;useMemo()&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;Start by asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why is this component doing work again?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;tsx&lt;br&gt;
function ProductList({ products }: Props) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;div&amp;gt;&lt;br&gt;
      {products.map((product) =&amp;gt; (&lt;br&gt;
        &amp;lt;ProductCard&lt;br&gt;
          key={product.id}&lt;br&gt;
          product={product}&lt;br&gt;
        /&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;ProductCard&lt;/code&gt; is expensive and receives stable props, memoization may help:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;tsx&lt;br&gt;
const ProductCard = React.memo(function ProductCard({&lt;br&gt;
  product,&lt;br&gt;
}: Props) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;article&amp;gt;&lt;br&gt;
      &amp;lt;h2&amp;gt;{product.name}&amp;lt;/h2&amp;gt;&lt;br&gt;
      &amp;lt;p&amp;gt;{product.description}&amp;lt;/p&amp;gt;&lt;br&gt;
    &amp;lt;/article&amp;gt;&lt;br&gt;
  );&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But optimization should follow measurement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Measure → identify the bottleneck → optimize → measure again.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Design Systems Create Consistency
&lt;/h2&gt;

&lt;p&gt;A growing application eventually develops repeated patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buttons&lt;/li&gt;
&lt;li&gt;Inputs&lt;/li&gt;
&lt;li&gt;Modals&lt;/li&gt;
&lt;li&gt;Dropdowns&lt;/li&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Toasts&lt;/li&gt;
&lt;li&gt;Cards&lt;/li&gt;
&lt;li&gt;Typography&lt;/li&gt;
&lt;li&gt;Spacing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of implementing these independently, create reusable primitives.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;tsx&lt;br&gt;
&amp;lt;Button variant="primary"&amp;gt;&lt;br&gt;
  Publish Article&lt;br&gt;
&amp;lt;/Button&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A good design system isn't only about visual consistency.&lt;/p&gt;

&lt;p&gt;It also creates &lt;strong&gt;behavioral consistency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, every primary button should ideally have the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loading behavior&lt;/li&gt;
&lt;li&gt;disabled behavior&lt;/li&gt;
&lt;li&gt;keyboard interaction&lt;/li&gt;
&lt;li&gt;focus treatment&lt;/li&gt;
&lt;li&gt;accessibility semantics&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Build for Accessibility From the Beginning
&lt;/h2&gt;

&lt;p&gt;Accessibility shouldn't be a final checklist item.&lt;/p&gt;

&lt;p&gt;Use semantic HTML wherever possible:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;p&gt;&lt;a href="/articles"&gt;Articles&lt;/a&gt;&lt;br&gt;
  &lt;a href="/projects"&gt;Projects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;p&gt;Articles&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard navigation&lt;/li&gt;
&lt;li&gt;Focus states&lt;/li&gt;
&lt;li&gt;Screen readers&lt;/li&gt;
&lt;li&gt;Color contrast&lt;/li&gt;
&lt;li&gt;Reduced motion&lt;/li&gt;
&lt;li&gt;Form labels&lt;/li&gt;
&lt;li&gt;Error messages&lt;/li&gt;
&lt;li&gt;Touch target sizes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A visually impressive interface that cannot be comfortably navigated with a keyboard is still an incomplete interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Testing Gives You Confidence
&lt;/h2&gt;

&lt;p&gt;A modern frontend application should have multiple levels of testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit tests
&lt;/h3&gt;

&lt;p&gt;Test small pieces of logic:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
describe("formatPrice", () =&amp;gt; {&lt;br&gt;
  it("formats a number as currency", () =&amp;gt; {&lt;br&gt;
    expect(formatPrice(1299)).toBe("$12.99");&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Component tests
&lt;/h3&gt;

&lt;p&gt;Test how components behave when users interact with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-end tests
&lt;/h3&gt;

&lt;p&gt;Test complete workflows:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Open application&lt;br&gt;
      ↓&lt;br&gt;
Login&lt;br&gt;
      ↓&lt;br&gt;
Create article&lt;br&gt;
      ↓&lt;br&gt;
Add content&lt;br&gt;
      ↓&lt;br&gt;
Preview article&lt;br&gt;
      ↓&lt;br&gt;
Publish&lt;br&gt;
      ↓&lt;br&gt;
Verify publication&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This type of test is particularly valuable for critical user journeys.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Keep External Integrations Behind Boundaries
&lt;/h2&gt;

&lt;p&gt;When your application communicates with external services, avoid spreading provider-specific logic throughout your codebase.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
if (provider === "devto") {&lt;br&gt;
  // DEV.to API logic&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;if (provider === "hashnode") {&lt;br&gt;
  // Hashnode API logic&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;create an abstraction:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
interface PublishingAdapter {&lt;br&gt;
  publish(input: PublishInput): Promise&amp;lt;PublishResult&amp;gt;;&lt;br&gt;
  update(input: UpdateInput): Promise&amp;lt;PublishResult&amp;gt;;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then each provider implements the same contract:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Publishing Service&lt;br&gt;
       │&lt;br&gt;
       ├── DEV.to Adapter&lt;br&gt;
       ├── Hashnode Adapter&lt;br&gt;
       ├── ArtXFlow Adapter&lt;br&gt;
       └── Future Provider&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This makes integrations easier to add and maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Automate Repetitive Work
&lt;/h2&gt;

&lt;p&gt;The best automation is often the work developers don't have to think about.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;text&lt;br&gt;
Article Published&lt;br&gt;
       ↓&lt;br&gt;
Create Publication&lt;br&gt;
       ↓&lt;br&gt;
Queue Workflow&lt;br&gt;
       ↓&lt;br&gt;
Publish to Destination&lt;br&gt;
       ↓&lt;br&gt;
Capture Result&lt;br&gt;
       ↓&lt;br&gt;
Update Publication Status&lt;br&gt;
       ↓&lt;br&gt;
Collect Analytics&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Instead of making the browser wait for every operation, long-running work can happen asynchronously.&lt;/p&gt;

&lt;p&gt;This becomes especially useful when publishing to multiple destinations.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. What Should You Optimize First?
&lt;/h2&gt;

&lt;p&gt;When working on a new application, it's tempting to optimize everything.&lt;/p&gt;

&lt;p&gt;Don't.&lt;/p&gt;

&lt;p&gt;A better approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish clear boundaries.&lt;/li&gt;
&lt;li&gt;Build the simplest working implementation.&lt;/li&gt;
&lt;li&gt;Measure real usage.&lt;/li&gt;
&lt;li&gt;Identify bottlenecks.&lt;/li&gt;
&lt;li&gt;Optimize the parts that actually matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Premature abstraction can be just as harmful as premature optimization.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Before calling a frontend application production-ready, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is the application accessible?&lt;/li&gt;
&lt;li&gt;[ ] Are important workflows tested?&lt;/li&gt;
&lt;li&gt;[ ] Are API errors handled?&lt;/li&gt;
&lt;li&gt;[ ] Are loading states handled?&lt;/li&gt;
&lt;li&gt;[ ] Are empty states handled?&lt;/li&gt;
&lt;li&gt;[ ] Are authentication boundaries clear?&lt;/li&gt;
&lt;li&gt;[ ] Is sensitive data kept server-side?&lt;/li&gt;
&lt;li&gt;[ ] Are external integrations isolated?&lt;/li&gt;
&lt;li&gt;[ ] Is the application observable?&lt;/li&gt;
&lt;li&gt;[ ] Can new features be added without modifying unrelated areas?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Modern frontend development is less about knowing every library and more about understanding &lt;strong&gt;boundaries and trade-offs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;React gives us the tools to build interfaces.&lt;/p&gt;

&lt;p&gt;TypeScript gives us confidence in our code.&lt;/p&gt;

&lt;p&gt;Testing gives us confidence in our changes.&lt;/p&gt;

&lt;p&gt;Design systems give us consistency.&lt;/p&gt;

&lt;p&gt;Good architecture gives us the ability to keep changing the product without constantly fighting the codebase.&lt;/p&gt;

&lt;p&gt;And perhaps the most important principle is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build the simplest architecture that can support the next stage of the product.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't build for a hypothetical million users.&lt;/p&gt;

&lt;p&gt;Build for the product you have today, while leaving clear paths for tomorrow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In a future article, we can take this architecture and turn it into a production-ready application using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drizzle ORM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TanStack Query&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to use more tools.&lt;/p&gt;

&lt;p&gt;The goal is to make the system &lt;strong&gt;easier to build, understand, test, and evolve&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What principles do you follow when designing frontend architecture?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Share your approach in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Modern software development is ultimately about managing complexity.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#typescript&lt;/code&gt; &lt;code&gt;#frontend&lt;/code&gt; &lt;code&gt;#webdevelopment&lt;/code&gt; &lt;code&gt;#architecture&lt;/code&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>frontend</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
