<?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: Abdullah Al Jahid</title>
    <description>The latest articles on DEV Community by Abdullah Al Jahid (@jahid1030).</description>
    <link>https://dev.to/jahid1030</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%2F647332%2Fc8ab3898-c2ad-4496-b2dc-e40c9566295c.jpeg</url>
      <title>DEV Community: Abdullah Al Jahid</title>
      <link>https://dev.to/jahid1030</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jahid1030"/>
    <language>en</language>
    <item>
      <title>Online multiplayer with no game server: WebRTC invite links and a browser as referee</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Thu, 05 Nov 2026 14:40:41 +0000</pubDate>
      <link>https://dev.to/playtrova/online-multiplayer-with-no-game-server-webrtc-invite-links-and-a-browser-as-referee-316b</link>
      <guid>https://dev.to/playtrova/online-multiplayer-with-no-game-server-webrtc-invite-links-and-a-browser-as-referee-316b</guid>
      <description>&lt;p&gt;PlayTrova is a free games site I run as a fully static Next.js site, with no backend at all. I still wanted the board games to have the one feature families actually ask for: &lt;em&gt;send a link, play together&lt;/em&gt;. No accounts, no lobby, and no server to pay for or keep alive.&lt;/p&gt;

&lt;p&gt;Today 12 of the turn-based games, including &lt;a href="https://playtrova.com/games/checkers/" rel="noopener noreferrer"&gt;Checkers&lt;/a&gt;, Four in a Row, Reversi and &lt;a href="https://playtrova.com/games/sea-battle/" rel="noopener noreferrer"&gt;Sea Battle&lt;/a&gt;, can be played online that way. Here's how it works, including the parts I'd change if people were playing for prizes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transparency note: PlayTrova is built with an AI coding assistant (Claude Code) doing much of the implementation, and this article was written with AI too. The code excerpts are trimmed from the live site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The shape of it
&lt;/h2&gt;

&lt;p&gt;Two browsers talk directly over a WebRTC data channel. The only outside help they need is &lt;em&gt;signalling&lt;/em&gt;: a way to find each other and swap connection details. &lt;a href="https://peerjs.com/" rel="noopener noreferrer"&gt;PeerJS&lt;/a&gt; provides that through a free hosted server. After the handshake, game messages go straight from one browser to the other.&lt;/p&gt;

&lt;p&gt;The room code &lt;em&gt;is&lt;/em&gt; the host's peer ID:&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;const&lt;/span&gt; &lt;span class="nx"&gt;PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playtrova-v1-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// no 0/O or 1/I/L: codes get read out loud and typed on phones&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALPHABET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ABCDEFGHJKMNPQRSTUVWXYZ23456789&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newRoomCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ALPHABET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ALPHABET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The host opens &lt;code&gt;new Peer(PREFIX + code)&lt;/code&gt; and waits. The invite link is just &lt;code&gt;/games/checkers/?join=K7QX2M&lt;/code&gt;: the friend's page sees &lt;code&gt;join&lt;/code&gt;, creates an anonymous peer and calls &lt;code&gt;peer.connect(PREFIX + code)&lt;/code&gt;. The host also shows the link as a QR code, which is handy when two people are sitting together with two phones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's mostly easy: the games are deterministic
&lt;/h2&gt;

&lt;p&gt;As I described in &lt;a href="https://dev.to/playtrova/how-to-build-a-daily-challenge-thats-the-same-for-every-player-with-no-server-176l"&gt;an earlier post&lt;/a&gt;, every turn-based game here is pure logic: &lt;code&gt;init(config, seed)&lt;/code&gt; plus &lt;code&gt;apply(state, move)&lt;/code&gt;, with all randomness coming from a seed stored in the state. That makes the networking almost boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The host picks the seed and who moves first, and sends them once.&lt;/li&gt;
&lt;li&gt;After that, each side only sends its own moves, as short strings: &lt;code&gt;"3"&lt;/code&gt; for a Four in a Row column, &lt;code&gt;"22-15-8"&lt;/code&gt; for a Checkers double jump.&lt;/li&gt;
&lt;li&gt;Dice don't travel either. In Lucky Lanterns, "roll" is the move, and the face comes from the seed in the state, so both browsers roll the same die.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole protocol is one union type:&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;NetMsg&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;hostSeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;hostSeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;syncreq&lt;/span&gt;&lt;span class="dl"&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emoji&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rematch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hello&lt;/code&gt; checks that both pages opened the same game. &lt;code&gt;start&lt;/code&gt; begins a round, and the host swaps seats every round so the first move alternates. &lt;code&gt;rematch&lt;/code&gt; is the guest asking the host to start another one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never trust the other browser
&lt;/h2&gt;

&lt;p&gt;Anything arriving on the data channel could be anything, so it goes through a strict parser before the game sees it. Wrong shape, too long or unknown type: dropped.&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emoji&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;EMOJIS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emoji&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That emoji line is also the entire chat system: six reactions (👋 👏 😂 😮 😅 🔥) and no free text. On a family site that's a feature, and checking on receive means a modified client can't push arbitrary text onto the other screen.&lt;/p&gt;

&lt;p&gt;Then every move is checked against the rules. Both sides do this, so the guest checks the host's moves too:&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;const&lt;/span&gt; &lt;span class="nx"&gt;applyRemote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// already have it&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// we missed something&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;who&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;who&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isLegal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;illegal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;who&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLegal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sequence number is simply the move's index in the game's move list. That one integer handles duplicates, ordering and gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnecting is "send me the move list again"
&lt;/h2&gt;

&lt;p&gt;Phones lock, Wi-Fi blips, someone switches apps to answer a message. Because state is just seed plus moves, recovery doesn't need snapshots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the guest receives a move whose &lt;code&gt;seq&lt;/code&gt; is ahead of what it has, it replies &lt;code&gt;syncreq&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The host answers with &lt;code&gt;sync&lt;/code&gt;: the round's seed, seats and full move list.&lt;/li&gt;
&lt;li&gt;The guest rebuilds the board by replaying the moves from scratch. In React, the board component is keyed on the round and a sync counter, so a sync simply remounts it with &lt;code&gt;initialMoves&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the host side, a returning friend just connects again with the same room code. The new connection replaces the old one, and its &lt;code&gt;hello&lt;/code&gt; triggers a &lt;code&gt;sync&lt;/code&gt;:&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;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// a reconnecting friend replaces the old connection&lt;/span&gt;
  &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;wire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroyed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// lost the signalling server, not the game&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last handler matters. PeerJS can lose its connection to the signalling server while the peer-to-peer channel carries on, and until it reconnects, nobody can find the host. Without &lt;code&gt;reconnect()&lt;/code&gt;, a friend who drops out can never get back in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small UX things that made it usable
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Friendly errors.&lt;/strong&gt; PeerJS errors come with a type, and each one maps to a sentence a parent understands. &lt;code&gt;peer-unavailable&lt;/code&gt; becomes "That room isn't open any more. Ask your friend for a new link."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A join timeout.&lt;/strong&gt; If nothing connects within 20 seconds, the guest gets a clear message instead of an endless spinner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A way out.&lt;/strong&gt; If connecting fails, the screen offers "Try again" and "Play on this device instead", because some networks simply won't connect peer to peer (more on that below). If the line drops mid-game, the guest gets a Reconnect button and the host waits for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Keep this page open."&lt;/strong&gt; The host sees this while waiting, because the host's tab &lt;em&gt;is&lt;/em&gt; the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trade-offs, honestly
&lt;/h2&gt;

&lt;p&gt;This design is cheap and simple because it trusts the people playing. That's fine for friends and family. It wouldn't be fine for ranked play or prizes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's not cheat-proof.&lt;/strong&gt; Both browsers hold the full game state. In Sea Battle, both fleets are placed from the shared seed, and the board only &lt;em&gt;hides&lt;/em&gt; your friend's ships on screen, so anyone with devtools could find them. The host also picks the seed, so a determined host could hunt for a lucky one. A real fix needs a server that deals each player only their own view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some networks block it.&lt;/strong&gt; Strict school or office networks can stop a direct WebRTC connection. I don't run my own relay (TURN) server, so those players get the "play on this device" fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The host is the referee.&lt;/strong&gt; If the host closes the tab, the game ends. There's no host migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signalling is borrowed.&lt;/strong&gt; The free PeerJS server makes no uptime promise. Self-hosting PeerServer would be a small swap if it ever becomes a problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two players online.&lt;/strong&gt; Some games take up to six players on one device, but online play is one-to-one for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a free site with no accounts, I'm happy with that list. The networking layer is about 150 lines plus a 300-line React panel, and it costs nothing to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Pick any game on the &lt;a href="https://playtrova.com/category/online-games-with-friends/" rel="noopener noreferrer"&gt;online games with friends&lt;/a&gt; page, choose &lt;strong&gt;vs Friend&lt;/strong&gt;, then invite with a link. Or open the link in a second browser window to watch both sides at once.&lt;/p&gt;

&lt;p&gt;I'd love to hear how the connection behaves on your network, especially if you're behind something strict.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to build a daily challenge that's the same for every player, with no server</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:40:54 +0000</pubDate>
      <link>https://dev.to/playtrova/how-to-build-a-daily-challenge-thats-the-same-for-every-player-with-no-server-176l</link>
      <guid>https://dev.to/playtrova/how-to-build-a-daily-challenge-thats-the-same-for-every-player-with-no-server-176l</guid>
      <description>&lt;p&gt;Wordle made the "everyone gets the same puzzle today" idea famous. The nice surprise is that you don't need a backend for it. If your game logic is deterministic, the date itself can be the source of the puzzle.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://playtrova.com/" rel="noopener noreferrer"&gt;PlayTrova&lt;/a&gt;, a free family games site, every game has a daily solo challenge built this way: today's &lt;a href="https://playtrova.com/games/lucky-lanterns/" rel="noopener noreferrer"&gt;Lucky Lanterns&lt;/a&gt; dice rolls, today's &lt;a href="https://playtrova.com/games/mosha-zap/" rel="noopener noreferrer"&gt;Mosha Zap&lt;/a&gt; mosquito swarm and today's &lt;a href="https://playtrova.com/games/checkers/" rel="noopener noreferrer"&gt;Checkers&lt;/a&gt; puzzles are identical for every player in the world. The site is fully static, so there's no database deciding what "today" contains.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: PlayTrova was built with an AI coding assistant, and this article was drafted with its help.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the whole recipe in four small pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Agree on what "today" means
&lt;/h2&gt;

&lt;p&gt;Players live in different time zones, so pick one clock. UTC is the simplest:&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="cm"&gt;/** UTC date key, e.g. "2026-10-26". */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todayKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone's daily switches at the same moment (midnight UTC), and the key doubles as a label for share text and saved results.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Turn the date into a number
&lt;/h2&gt;

&lt;p&gt;A seeded random generator needs a 32-bit seed, so hash the string. Mixing the game id in means two games never share a seed on the same day:&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="cm"&gt;/** Stable 32-bit string hash. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1779033703&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3432918353&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;13&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="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** Seed for a game's daily challenge. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dailySeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;todayKey&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;hashStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:pt1`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;:pt1&lt;/code&gt; suffix is a version tag. If you ever change how puzzles are generated, bump it and nobody gets a half-old, half-new puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use a seedable PRNG instead of Math.random
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; can't be seeded, so every player would get something different. mulberry32 is tiny, fast and good enough for games:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mulberry32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x6d2b79f5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mulberry32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailySeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lucky-lanterns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nf"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// same sequence for everyone today&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Keep the game logic deterministic
&lt;/h2&gt;

&lt;p&gt;This is the part that actually takes discipline. If anything inside the game logic calls &lt;code&gt;Math.random()&lt;/code&gt; or &lt;code&gt;Date.now()&lt;/code&gt;, two players drift apart after a few moves. On PlayTrova the rule is simple: turn-based game logic is pure, and randomness comes only from a seed stored in the game state. A test makes the guarantee explicit:&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gives everyone the same daily puzzles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailyPuzzles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailySeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-22&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailyPuzzles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailySeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-22&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailyPuzzles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailySeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-23&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailyPuzzles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dailySeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-22&lt;/span&gt;&lt;span class="dl"&gt;"&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;For puzzles you also want "solvable" to be guaranteed, so generate candidates from the seed and have a solver check each one, in a test that runs over many dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff: sharing without a backend
&lt;/h2&gt;

&lt;p&gt;Because everyone played the same thing, results are comparable, and a share string is all you need. Lucky Lanterns shares a little grid of 🏮 banked and 💨 gusted turns, Wordle-style. Paste it in a group chat and your friends know exactly what you're bragging about.&lt;/p&gt;

&lt;p&gt;What you give up: there's no global leaderboard (that needs a server) and a determined player can peek at tomorrow by changing their clock. For a family games site, that trade-off is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Today's challenges for every game are on the &lt;a href="https://playtrova.com/daily/" rel="noopener noreferrer"&gt;PlayTrova daily page&lt;/a&gt;. If you've built something similar, I'd love to hear how you handled time zones and puzzle quality.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I built 17 browser games as a fully static Next.js site (no backend)</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Mon, 26 Oct 2026 16:26:04 +0000</pubDate>
      <link>https://dev.to/jahid1030/how-i-built-17-browser-games-as-a-fully-static-nextjs-site-no-backend-22ad</link>
      <guid>https://dev.to/jahid1030/how-i-built-17-browser-games-as-a-fully-static-nextjs-site-no-backend-22ad</guid>
      <description>&lt;p&gt;I wanted a games site my whole family could use: free, no sign-up, no backend to babysit, and every game playable three ways — &lt;strong&gt;solo&lt;/strong&gt;, &lt;strong&gt;against the computer&lt;/strong&gt;, or &lt;strong&gt;with a friend on the same screen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://playtrova.com/" rel="noopener noreferrer"&gt;PlayTrova&lt;/a&gt;: 17 browser games (board games, puzzles, racing and math games) served as a fully static Next.js site on Cloudflare Pages. Here's how it's put together, and a few things I learned along the way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transparency note: I built PlayTrova with an AI coding assistant (Claude Code) doing much of the implementation, and this write-up was drafted with its help too. The architecture choices and the lessons below are real — the site is live, so you can poke at all of it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The core idea: a game is pure logic, a mode is just who fills each seat
&lt;/h2&gt;

&lt;p&gt;Every turn-based game implements the same small contract:&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TurnGame&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Seat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// whose turn&lt;/span&gt;
  &lt;span class="nf"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;             &lt;span class="c1"&gt;// legal moves&lt;/span&gt;
  &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// pure: returns a new state&lt;/span&gt;
  &lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// playing | win | draw&lt;/span&gt;
  &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// moves travel as short strings&lt;/span&gt;
  &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Seat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// hide what a seat must not see (memory cards)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Math.random&lt;/code&gt;, no &lt;code&gt;Date.now&lt;/code&gt; inside game logic — randomness comes from a seed stored in the state. That one rule pays off everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modes are free.&lt;/strong&gt; Solo, vs Computer and vs Friend are the same game with different things sitting in the seats: a local human, an AI, or a second local human.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saves, undo and replays are just &lt;code&gt;{ seed, config, moves[] }&lt;/code&gt;.&lt;/strong&gt; Replay the moves and you get the exact same state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily challenges are the same for everyone.&lt;/strong&gt; The date is hashed into a seed, so every player in the world gets today's identical puzzle, dice rolls or mosquito swarm.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Small, fast, seedable PRNG. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mulberry32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x6d2b79f5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;&lt;span class="p"&gt;;&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;It also makes the logic very testable: the project has 744 Vitest tests, most of them plain "apply these moves, expect this state" checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The computer opponents live in a Web Worker
&lt;/h2&gt;

&lt;p&gt;Board-game AI is a generic iterative-deepening alpha-beta search with a time budget, running in a Web Worker so the page never freezes. Each game only supplies an evaluation function.&lt;/p&gt;

&lt;p&gt;Difficulty isn't just search depth. Every game has the same four characters — Rookie Robo, Clever Cat, Sly Fox and Grandmaster Owl — and each one is a profile: depth, time budget, how often it blunders, and for real-time games a reaction delay and aim error. Kids care far more about "I beat the fox!" than about "Hard mode".&lt;/p&gt;

&lt;h2&gt;
  
  
  Online play without a server
&lt;/h2&gt;

&lt;p&gt;Turn-based games can also be played online with an invite link. There's no game server: two browsers connect peer-to-peer over WebRTC (PeerJS handles the signalling), the &lt;strong&gt;host's browser is the referee&lt;/strong&gt;, and both sides validate every move. Because moves are short strings and the logic is deterministic, reconnecting is just "send me the move list again".&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time games share the same shell
&lt;/h2&gt;

&lt;p&gt;Not everything is turn-based. &lt;a href="https://playtrova.com/games/turbo-trails/" rel="noopener noreferrer"&gt;Turbo Trails&lt;/a&gt; is a pseudo-3D racer, &lt;a href="https://playtrova.com/games/snap-duel/" rel="noopener noreferrer"&gt;Snap Duel&lt;/a&gt; is a reflex party game, and &lt;a href="https://playtrova.com/games/mosha-zap/" rel="noopener noreferrer"&gt;Mosha Zap&lt;/a&gt; has you swinging an electric racket at mosquitoes in a Bangladeshi bedroom (load shedding included). These run their own simulation loop with canvas rendering on top, and their daily solo challenges still come from the date seed, so everyone races today's trail or faces today's swarm. The split-screen modes track each finger by its pointer id, so two kids can play on one tablet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fully static, on purpose
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;output: "export"&lt;/code&gt; means &lt;code&gt;next build&lt;/code&gt; writes plain HTML/JS, and Cloudflare Pages serves it for free. Each game page is a server-rendered page (title, rules, strategy tips, FAQ, JSON-LD) with the playable area loaded lazily on the client. Progress, stars and streaks live in &lt;code&gt;localStorage&lt;/code&gt; — no accounts.&lt;/p&gt;

&lt;p&gt;Two gotchas worth knowing if you do the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Unknown dynamic routes in dev.&lt;/strong&gt; With &lt;code&gt;output: "export"&lt;/code&gt;, &lt;code&gt;next dev&lt;/code&gt; throws a "missing param in generateStaticParams()" error for a mistyped slug instead of rendering your 404 page. The fix is to only export in real builds:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PHASE_DEVELOPMENT_SERVER&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/constants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;PHASE_DEVELOPMENT_SERVER&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;trailingSlash&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="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unoptimized&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="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;With &lt;code&gt;dynamicParams = false&lt;/code&gt; on the route, dev now shows the real 404 page, and production still serves &lt;code&gt;404.html&lt;/code&gt; for unknown paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Don't stamp your sitemap with the build time.&lt;/strong&gt; My first sitemap set &lt;code&gt;lastModified: new Date()&lt;/code&gt; on every URL, so every deploy claimed that every page had changed. Google ignores &lt;code&gt;lastmod&lt;/code&gt; when it isn't trustworthy, so now only pages with a real edit date (the guides) carry one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping the same games to itch.io
&lt;/h2&gt;

&lt;p&gt;I also wanted the original games on &lt;a href="https://playtrova.itch.io/" rel="noopener noreferrer"&gt;itch.io&lt;/a&gt;, which prefers games uploaded as self-contained HTML5 zips. Rather than maintain a second codebase, a small Vite config builds a single game straight out of the Next.js source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;next/link&lt;/code&gt; and &lt;code&gt;next/navigation&lt;/code&gt; are aliased to tiny shims, so links to the rest of the site open in a new tab instead of navigating the iframe.&lt;/li&gt;
&lt;li&gt;A virtual module replaces the "load any game" registry with a loader for just that one game, so each zip only contains what it needs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;base: "./"&lt;/code&gt; keeps every asset path relative, because itch.io serves the game from a sub-folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One &lt;code&gt;npm run itch&lt;/code&gt; builds all six originals, zips them with forward-slash paths (itch.io's servers are Linux), and renders a 630×500 cover for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://playtrova.com/games/" rel="noopener noreferrer"&gt;All games&lt;/a&gt; — no sign-up, works on phones and tablets&lt;/li&gt;
&lt;li&gt;Originals worth a look: &lt;a href="https://playtrova.com/games/tilt-four/" rel="noopener noreferrer"&gt;Tilt Four&lt;/a&gt; (four in a row, but you can tilt the whole board once), &lt;a href="https://playtrova.com/games/lucky-lanterns/" rel="noopener noreferrer"&gt;Lucky Lanterns&lt;/a&gt; (push-your-luck dice race to 100) and &lt;a href="https://playtrova.com/games/math-race/" rel="noopener noreferrer"&gt;Math Race&lt;/a&gt; (first right answer scores)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love feedback — especially on the AI difficulty curve and how the split-screen modes feel on your devices.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>gamedev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Using chmod 777: A Developer's Guide to Linux File Permissions</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Sat, 09 May 2026 16:59:36 +0000</pubDate>
      <link>https://dev.to/jahid1030/stop-using-chmod-777-a-developers-guide-to-linux-file-permissions-36dp</link>
      <guid>https://dev.to/jahid1030/stop-using-chmod-777-a-developers-guide-to-linux-file-permissions-36dp</guid>
      <description>&lt;p&gt;Last month, a friend's side project got hacked. The attacker uploaded a PHP shell through a file upload form and executed it. The root cause? Every directory on the server was set to &lt;code&gt;chmod 777&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;He told me: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I kept getting permission denied errors during deployment, so I just 777'd everything."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sound familiar? Let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What chmod Actually Does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;chmod&lt;/code&gt; (change mode) controls who can &lt;strong&gt;read&lt;/strong&gt;, &lt;strong&gt;write&lt;/strong&gt;, and &lt;strong&gt;execute&lt;/strong&gt; files on Linux. Every file has three permission sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt; — the user who created the file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt; — users in the file's group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others&lt;/strong&gt; — everyone else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each gets a combination of:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;View contents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Modify/delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Run as program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;No access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Add the values&lt;/strong&gt; to get each digit. So &lt;code&gt;7 = 4+2+1 (rwx)&lt;/code&gt;, &lt;code&gt;5 = 4+1 (r-x)&lt;/code&gt;, &lt;code&gt;4 = 4 (r--)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three digits = owner, group, others. That's it. That's the whole system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chmod Cheat Sheet You'll Actually Use
&lt;/h2&gt;

&lt;p&gt;Here's what you need 90% of the time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;755 script.sh        &lt;span class="c"&gt;# Scripts &amp;amp; directories&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 index.html       &lt;span class="c"&gt;# Regular files (HTML, CSS, images)&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env             &lt;span class="c"&gt;# Secrets (SSH keys, credentials)&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh/          &lt;span class="c"&gt;# Private directories&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What each one means:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;755&lt;/strong&gt; (&lt;code&gt;rwxr-xr-x&lt;/code&gt;) - Owner can do everything. Everyone else can read and execute but not modify. Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shell scripts&lt;/li&gt;
&lt;li&gt;Directories&lt;/li&gt;
&lt;li&gt;Application binaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;644&lt;/strong&gt; (&lt;code&gt;rw-r--r--&lt;/code&gt;) - Owner can read/write. Everyone else read-only. Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML, CSS, JS files&lt;/li&gt;
&lt;li&gt;Configuration files (non-secret)&lt;/li&gt;
&lt;li&gt;Images and static assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;600&lt;/strong&gt; (&lt;code&gt;rw-------&lt;/code&gt;) - Owner only. Nobody else can even read the file. Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;SSH private keys (&lt;code&gt;~/.ssh/id_rsa&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Database credentials&lt;/li&gt;
&lt;li&gt;API tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;700&lt;/strong&gt; (&lt;code&gt;rwx------&lt;/code&gt;) - Like 600 but with execute (for directories). Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.ssh/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Home directories&lt;/li&gt;
&lt;li&gt;Private application folders&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I built a free &lt;a href="https://toolzip.xyz/developer-tools/chmod-calculator" rel="noopener noreferrer"&gt;interactive chmod calculator&lt;/a&gt; that lets you toggle permissions visually and see the numeric + symbolic notation in real time. Useful when you can't remember if you need 644 or 755.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why chmod 777 Is Dangerous
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;777&lt;/code&gt; means &lt;strong&gt;everyone can read, write, and execute&lt;/strong&gt;. Here's why that's a problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;777 /var/www/html/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you think happens: "My deployment script stops getting permission errors."&lt;/p&gt;

&lt;p&gt;What actually happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any user on the system can modify your files&lt;/li&gt;
&lt;li&gt;A web server vulnerability lets attackers write to your directories&lt;/li&gt;
&lt;li&gt;Uploaded files can be executed as code&lt;/li&gt;
&lt;li&gt;Attackers can replace your files with malicious versions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Real-world attack flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Attacker finds file upload endpoint
2. Uploads malicious.php (or .sh, .py, etc.)
3. Because directory is 777, the file is writable AND executable
4. Attacker visits malicious.php → full server access
5. Game over
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the directory was &lt;strong&gt;755&lt;/strong&gt;, the uploaded file would be owned by the web server user but &lt;strong&gt;not executable&lt;/strong&gt; by default. The attack chain breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symbolic Mode: The Safer Way to Change Permissions
&lt;/h2&gt;

&lt;p&gt;Instead of setting all permissions at once (numeric), you can add/remove specific ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add execute permission for owner only&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x deploy.sh

&lt;span class="c"&gt;# Remove write from group and others&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;go-w config.yml

&lt;span class="c"&gt;# Add read for everyone&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;a+r README.md

&lt;span class="c"&gt;# Remove all permissions from others&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;o-rwx secret.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is safer&lt;/strong&gt;: &lt;code&gt;chmod u+x script.sh&lt;/code&gt; only adds execute for the owner. It doesn't touch group or others permissions. With numeric mode, you have to know and set ALL permissions at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# These do the same thing, but symbolic is clearer:&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;755 script.sh          &lt;span class="c"&gt;# What was it before? Who knows.&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x script.sh          &lt;span class="c"&gt;# Just adding execute for owner. Clear.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Scenarios with Exact Commands
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deploying a web app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set directory permissions&lt;/span&gt;
find /var/www/myapp &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;755 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;

&lt;span class="c"&gt;# Set file permissions&lt;/span&gt;
find /var/www/myapp &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;644 &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;

&lt;span class="c"&gt;# Make scripts executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;755 /var/www/myapp/scripts/&lt;span class="k"&gt;*&lt;/span&gt;.sh

&lt;span class="c"&gt;# Lock down .env&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /var/www/myapp/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting up SSH keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh/
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/id_rsa          &lt;span class="c"&gt;# Private key (SSH REQUIRES this)&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 ~/.ssh/id_rsa.pub      &lt;span class="c"&gt;# Public key&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/authorized_keys
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 ~/.ssh/known_hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH will &lt;strong&gt;refuse to use your key&lt;/strong&gt; if the private key has permissions looser than 600. This is intentional - it's protecting you.&lt;/p&gt;

&lt;p&gt;Not sure what your current numeric permission translates to? &lt;br&gt;
Paste the number into the &lt;a href="https://toolzip.xyz/developer-tools/chmod-calculator" rel="noopener noreferrer"&gt;ToolZip Chmod Calculator&lt;/a&gt; to see &lt;br&gt;
the exact read/write/execute breakdown.&lt;/p&gt;
&lt;h3&gt;
  
  
  Docker volumes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container needs read/execute on the mounted directory&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 755 ./docker-data/

&lt;span class="c"&gt;# But secrets inside should be locked down&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ./docker-data/certs/&lt;span class="k"&gt;*&lt;/span&gt;.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  CI/CD pipeline
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make all scripts in your pipeline executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./scripts/&lt;span class="k"&gt;*&lt;/span&gt;.sh
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick Security Audit
&lt;/h2&gt;

&lt;p&gt;Run these commands on your server right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find all 777 files (fix every single one)&lt;/span&gt;
find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-777&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f 2&amp;gt;/dev/null

&lt;span class="c"&gt;# Find all 777 directories&lt;/span&gt;
find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-777&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; d 2&amp;gt;/dev/null

&lt;span class="c"&gt;# Find SUID files (programs that run as root)&lt;/span&gt;
find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-4000&lt;/span&gt; &lt;span class="nt"&gt;-ls&lt;/span&gt; 2&amp;gt;/dev/null

&lt;span class="c"&gt;# Check your web root specifically&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/www/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;find / -perm -777&lt;/code&gt; returns anything in your web root, fix it immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Permission Decision Flowchart
&lt;/h2&gt;

&lt;p&gt;When you're not sure what permission to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is it a secret (passwords, keys, tokens)?
  → 600

Is it a directory?
  → 755 (or 700 if private)

Is it an executable script?
  → 755

Is it a regular file?
  → 644

Should nobody but the owner access it?
  → 600 (file) or 700 (directory)

Are you tempted to use 777?
  → Stop. Use 755 and fix the actual problem.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tools That Help
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://toolzip.xyz/developer-tools/chmod-calculator" rel="noopener noreferrer"&gt;ToolZip Chmod Calculator&lt;/a&gt; - Toggle permissions visually, see numeric + symbolic notation, common presets for quick reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://toolzip.xyz/developer-tools/hash-generator" rel="noopener noreferrer"&gt;ToolZip Hash Generator&lt;/a&gt; - Verify file integrity after permission changes with MD5/SHA-256 checksums&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://toolzip.xyz/developer-tools/cron-generator" rel="noopener noreferrer"&gt;ToolZip Cron Expression Generator&lt;/a&gt; - Schedule automated permission audits with cron jobs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ls -la&lt;/code&gt; - Your best friend for checking current permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stat filename&lt;/code&gt; - Detailed file information including permissions in octal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Regular files&lt;/td&gt;
&lt;td&gt;644&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chmod 644 file.html&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripts &amp;amp; directories&lt;/td&gt;
&lt;td&gt;755&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chmod 755 script.sh&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets &amp;amp; credentials&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chmod 600 .env&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private directories&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chmod 700 ~/.ssh/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Never in production&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;777&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;Don't.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What To Do Right Now
&lt;/h2&gt;

&lt;p&gt;If you're running a Linux server, SSH into it and run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-777&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns anything — especially in /var/www/ — you have a &lt;br&gt;
problem. Fix it with the permission values from the cheat sheet &lt;br&gt;
above before someone else finds it first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop chmod 777-ing everything. Use 755 for scripts and directories, 644 for files, 600 for secrets. That covers 95% of cases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to play around with permissions without risk, try the &lt;a href="https://toolzip.xyz/developer-tools/chmod-calculator" rel="noopener noreferrer"&gt;chmod calculator&lt;/a&gt; - toggle checkboxes and see exactly what each number means.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://toolzip.xyz/blog/chmod-permissions-explained" rel="noopener noreferrer"&gt;toolzip.xyz/blog/chmod-permissions-explained&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>MUST USE VSCODE EXTENSIONS</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Tue, 31 May 2022 19:05:36 +0000</pubDate>
      <link>https://dev.to/jahid1030/must-use-vscode-extensions-1lj7</link>
      <guid>https://dev.to/jahid1030/must-use-vscode-extensions-1lj7</guid>
      <description>&lt;h2&gt;
  
  
  What is VS Code Extensions?
&lt;/h2&gt;

&lt;p&gt;Extensions are add-ons that allow you to &lt;strong&gt;customize&lt;/strong&gt; and enhance your &lt;strong&gt;experience&lt;/strong&gt; in Visual Studio by adding new features or integrating existing tools. An extension can &lt;strong&gt;range in all levels of complexity&lt;/strong&gt;, but its main purpose is to &lt;strong&gt;boost your productivity and cater to your workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VS Code extensions let you add languages, debuggers, and tools to your installation to support your &lt;strong&gt;development workflow&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to install extensions in VS Code?
&lt;/h2&gt;

&lt;p&gt;You can browse and install extensions from within VS Code. Bring up the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of VS Code or use the View: Extensions command (Ctrl+Shift+X) to bring up the Extensions view.&lt;br&gt;
This will show you a list of the most popular VS Code extensions on the VS Code Marketplace.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Forsrrmmmzgetwqbptq1o.png" 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.amazonaws.com%2Fuploads%2Farticles%2Forsrrmmmzgetwqbptq1o.png" alt=" " width="799" height="318"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation of Extensions
&lt;/h2&gt;

&lt;p&gt;To install an extension, select the Install button. Once the installation is complete, the Install button will change to the Manage gear button.You can also add the extensions from command line terminal.&lt;/p&gt;

&lt;p&gt;Note: Sometimes you may need to restart your VS Code finish the installation process.&lt;/p&gt;

&lt;p&gt;Let's have a look on some must use VS Code extensions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each extension in the list includes a brief description, the publisher, the download count, and a five star rating. You can select the extension item to display the extension's details page where you can learn more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Prettier
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;Prettier&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; esbenp.prettier-vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ESLint
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;ESLint&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; dbaeumer.vscode-eslint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TSLint
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;TSLint&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-vscode.vscode-typescript-tslint-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code Spell Checker
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;Code Spell Checker&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; streetsidesoftware.code-spell-checker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitLens — Git supercharged
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;GitLens — Git supercharged&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; eamodio.gitlens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TODO Highlight
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;TODO Highlight&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; wayou.vscode-todo-highlight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Color Highlight
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;Color Highlight&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; naumovs.color-highlight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NPM Intellisense
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;npm Intellisense&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; christian-kohler.npm-intellisense
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Path Intellisense
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;Path Intellisense&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; christian-kohler.path-intellisense
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IntelliCode
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;IntelliCode&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; visualstudioexptteam.vscodeintellicode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;Go to extensions view page and search for &lt;code&gt;GitHub Copilot&lt;/code&gt; and install it. &lt;br&gt;
Command for installing it from terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; github.copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Thanks&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Markdown Cheat Sheet</title>
      <dc:creator>Abdullah Al Jahid</dc:creator>
      <pubDate>Sat, 19 Jun 2021 10:15:46 +0000</pubDate>
      <link>https://dev.to/jahid1030/markdown-cheat-sheet-1a17</link>
      <guid>https://dev.to/jahid1030/markdown-cheat-sheet-1a17</guid>
      <description>&lt;h2&gt;
  
  
  What is Markdown?
&lt;/h2&gt;

&lt;p&gt;Markdown is a lightweight markup language with a plain text formatting syntax. It can be converted into HTML/XHTML and others formats. It's main purpose is readability and ease of use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is Used For?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;README files (Github , etc.)&lt;/li&gt;
&lt;li&gt;Forum and Blog Posts (dev.to, Medium etc)&lt;/li&gt;
&lt;li&gt;Many static site generators.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's have a look what can we do using markdown and how can we use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headings
&lt;/h2&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fvd8w0j3nk4tty1x2pb3r.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fvd8w0j3nk4tty1x2pb3r.png" alt="Alt Text" width="676" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  For Heading1
&lt;/h1&gt;

&lt;h2&gt;
  
  
  For Heading2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Heading3
&lt;/h3&gt;

&lt;h4&gt;
  
  
  For Heading4
&lt;/h4&gt;

&lt;h5&gt;
  
  
  For Heading5
&lt;/h5&gt;

&lt;h6&gt;
  
  
  For Heading6
&lt;/h6&gt;

&lt;p&gt;Alternatively, you can use an underline-ish style for H1 and H2:&lt;/p&gt;

&lt;h1&gt;
  
  
  For H1
&lt;/h1&gt;

&lt;h2&gt;
  
  
  For H2
&lt;/h2&gt;




&lt;h2&gt;
  
  
  Italic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use single *asterisks* or _underscores_ for Italic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use single &lt;em&gt;asterisks&lt;/em&gt; or &lt;em&gt;underscores&lt;/em&gt; for Italic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bold/Strong
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use double **asterisks** or __underscores__ for Bold/Strong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use double &lt;strong&gt;asterisks&lt;/strong&gt; or &lt;strong&gt;underscores&lt;/strong&gt; for Bold/Strong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combined
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Bold and *Italic1* &amp;amp; _Italic2_**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bold and &lt;em&gt;Italic1&lt;/em&gt; &amp;amp; &lt;em&gt;Italic2&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strikethrough
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use two tildes ~~to scratch/strikethrough this.~~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use two tildes &lt;del&gt;to scratch/strikethrough this.&lt;/del&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Horizontal Rule
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You can use any of the following...
Using Hyphens

---

Using Asterisks

***

Using Underscores

___

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using three Hyphens&lt;/p&gt;




&lt;p&gt;Using three Asterisks&lt;/p&gt;




&lt;p&gt;Using three Underscores&lt;/p&gt;




&lt;h2&gt;
  
  
  Blockquotes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; This line is quoted.
&amp;gt; This line is part of the same quote.

Paragraph:
&amp;gt; You can write a paragraph inside a blockquote. You can also use different markdown here. Such as *itlic*, **bold**, ~~strike~~ etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This line is quoted.&lt;br&gt;
This line is part of the same quote.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Paragraph:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can write a paragraph inside a blockquote. You can also use different markdown here. Such as &lt;em&gt;itlic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, &lt;del&gt;strike&lt;/del&gt; etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;There are two ways to create links.&lt;br&gt;
&lt;strong&gt;1. Inline Link&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Abdulah Al Jahid](https://username.medium.com/)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://jahid1030.medium.com/" rel="noopener noreferrer"&gt;Abdulah Al Jahid&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Inline Link With a Title&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Abdulah Al Jahid](https://github.com/username "My Github Account")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/Jahid1999" rel="noopener noreferrer"&gt;Abdulah Al Jahid&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tricky way to add links in a button&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a target="_blank" href=""&amp;gt;&amp;lt;img src="https://img.shields.io/badge/-%23.svg?&amp;amp;style=plastic&amp;amp;logo=&amp;amp;logoColor=white" alt=""&amp;gt;&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Github link: &lt;a href="https://github.com/Jahid1999" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FGithub-%2523269539.svg%3F%26style%3Dplastic%26logo%3DGithub%26logoColor%3Dblack" alt="Github" width="65" height="18"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Medium Blog Link
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a target="_blank" href="https://github-readme-medium-recent-article.vercel.app/medium/@username/articleId"&amp;gt;&amp;lt;img src="https://github-readme-medium-recent-article.vercel.app/medium/@username/articleId" alt="Alternative text"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Youtube Video Link
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="http://www.youtube.com/watch?feature=player_embedded&amp;amp;v=YOUTUBE_VIDEO_ID" target="_blank"&amp;gt;&amp;lt;img src="http://img.youtube.com/vi/YOUTUBE_VIDEO_ID/0.jpg" 
alt="Alternative text" width="240" height="180" border="10" /&amp;gt;&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Images
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inline Style:
![Markdown Logo](https://markdown-here.com/img/icon256.png)

Reference Style:
![Markdown logo][logo]
`[logo]: https://markdown-here.com/img/icon256.png`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inline Style:&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F7z3hvwroep0bz6ptwuj1.png" 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.amazonaws.com%2Fuploads%2Farticles%2F7z3hvwroep0bz6ptwuj1.png" alt="Markdown Logo" width="256" height="256"&gt;&lt;/a&gt;&lt;br&gt;
Reference Style:&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F7z3hvwroep0bz6ptwuj1.png" 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.amazonaws.com%2Fuploads%2Farticles%2F7z3hvwroep0bz6ptwuj1.png" alt="Markdown logo" width="256" height="256"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Inline Code Block
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is inline `code block` using `back-ticks`.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is inline &lt;code&gt;code block&lt;/code&gt; using &lt;code&gt;back-ticks&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Blocks and Syntax Highlighting
&lt;/h2&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fijb5tq8uyebw2ywzissc.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fijb5tq8uyebw2ywzissc.png" alt="Alt Text" width="796" height="422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal Code blocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;number2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;number1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;number2&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;Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;number1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;number2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UL&lt;/strong&gt;&lt;br&gt;
You can use */-/+ in unordered list&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* Item 1
* Item 2
    * Item 2.1
    * Item 2.2
        * Item 2.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Item 1&lt;/li&gt;
&lt;li&gt;Item 2

&lt;ul&gt;
&lt;li&gt;Item 2.1&lt;/li&gt;
&lt;li&gt;Item 2.2

&lt;ul&gt;
&lt;li&gt;Item 2.2.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Item 1
2. Item 2
3. Item 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Item 1&lt;/li&gt;
&lt;li&gt;Item 2&lt;/li&gt;
&lt;li&gt;Item 3&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| First Name    | Last Name     | Id    |
| ------------- |---------------| ------|
| Mark          | Down          |   1   |
| Abdullah Al   | Jahid         |   2   |
| Awesome       | Table         |   0   |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;First Name&lt;/th&gt;
&lt;th&gt;Last Name&lt;/th&gt;
&lt;th&gt;Id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mark&lt;/td&gt;
&lt;td&gt;Down&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abdullah Al&lt;/td&gt;
&lt;td&gt;Jahid&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Awesome&lt;/td&gt;
&lt;td&gt;Table&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  &lt;em&gt;Thanks&lt;/em&gt;
&lt;/h3&gt;

</description>
      <category>markdown</category>
      <category>github</category>
      <category>readme</category>
    </item>
  </channel>
</rss>
