<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AnishDe12020 - Articles on Development and Tech]]></title><description><![CDATA[Blogging is one of the hobbies I enjoy a lot because I am able to tell the world about something interesting, learn, and teach others.]]></description><link>https://blog.anishde.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1639060409122/VDmqG_dY4.png</url><title>AnishDe12020 - Articles on Development and Tech</title><link>https://blog.anishde.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 01:58:24 GMT</lastBuildDate><atom:link href="https://blog.anishde.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Creating a Custom Solana Connect Wallet UI with React and Chakra UI]]></title><description><![CDATA[If you have worked with the Solana Wallet Adapter before, you will know that it is very easy to set up a Connect Wallet button with a decent modal.
However, customization is pretty limited. We can only add some custom CSS hence changing the styles bu...]]></description><link>https://blog.anishde.dev/creating-a-custom-solana-connect-wallet-ui-with-react-and-chakra-ui</link><guid isPermaLink="true">https://blog.anishde.dev/creating-a-custom-solana-connect-wallet-ui-with-react-and-chakra-ui</guid><category><![CDATA[Solana]]></category><category><![CDATA[React]]></category><category><![CDATA[Chakra-ui]]></category><category><![CDATA[Web3]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Wed, 15 Feb 2023 09:39:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676453097992/f9e02a6e-4264-4e92-9eac-b8e4eea8eb2a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have worked with the <a target="_blank" href="https://github.com/solana-labs/wallet-adapter">Solana Wallet Adapter</a> before, you will know that it is very easy to set up a Connect Wallet button with a decent modal.</p>
<p>However, customization is pretty limited. We can only add some custom CSS hence changing the styles but that is about it. In this article, we go over the following:</p>
<ol>
<li><p>Understanding how the <code>@solana/wallet-adapter-react-ui</code> package works</p>
</li>
<li><p>Using the <code>@solana/wallet-adapter-react</code> package to create a custom Connect Wallet page</p>
</li>
</ol>
<p>You can check out the deployed version of what we are going to build <a target="_blank" href="https://solana-react-wallet-adapter-custom-ui-example.vercel.app/">here</a></p>
<p>Let's get started!</p>
<h2 id="heading-how-does-solanawallet-adapter-react-ui-work">How does <code>@solana/wallet-adapter-react-ui</code> work?</h2>
<p>If you go through the <a target="_blank" href="https://github.com/solana-labs/wallet-adapter/tree/master/packages/ui/react-ui/src">source code for the React UI package</a>, then you will notice that it makes use of the <code>useWallet</code> hook from the <code>@solana/wallet-adapter-react</code> package. It also has the required components (buttons, modals, providers) and styling to be an easy-to-use library to implement a connect wallet feature.</p>
<h3 id="heading-the-usewallet-hook">The <code>useWallet</code> hook</h3>
<p>The <code>useWallet</code> hook returns functions like <code>select</code>, <code>connect</code>, and <code>disconnect</code> to select a wallet, connect to the selected wallet, and disconnect from the connected wallet respectively. It also returns some variables - <code>wallets</code> which is a list of all wallets with their availability status, <code>wallet</code> which is the currently selected wallet, <code>publicKey</code>, <code>connecting</code>, <code>connected</code>, and <code>disconnected</code> telling us about the current connection status.</p>
<h2 id="heading-using-solanawallet-adapter-react-to-create-a-custom-connect-wallet-ui">Using <code>@solana/wallet-adapter-react</code> to create a custom Connect Wallet UI</h2>
<p>Let us start by creating a new <a target="_blank" href="https://nextjs.org/">Next.js</a> project -</p>
<pre><code class="lang-bash">npx create-next-app solana-react-wallet-adapter-custom-ui-example --ts
</code></pre>
<p>It will ask you for some prompts, you can accept the defaults for this guide.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676448249827/8e7db5d0-5749-4d6f-bd2b-241952da9af5.png" alt class="image--center mx-auto" /></p>
<p>Now open up the project in your favorite IDE and open up the terminal. Then enter the following command to install the required dependencies -</p>
<pre><code class="lang-bash">npm i @solana/web3.js @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-wallets
</code></pre>
<p>For this guide, I am going to be using <a target="_blank" href="https://chakra-ui.com/">Chakra UI</a> for styling. To install the required dependencies for Chakra UI, run the following command -</p>
<pre><code class="lang-bash">npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
</code></pre>
<p>Now replace <code>_app.tsx</code> with the following -</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">"@/styles/globals.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AppProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/app"</span>;
<span class="hljs-keyword">import</span> { ChakraProvider, extendTheme } <span class="hljs-keyword">from</span> <span class="hljs-string">"@chakra-ui/react"</span>;
<span class="hljs-keyword">import</span> {
  ConnectionProvider,
  WalletProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@solana/wallet-adapter-react"</span>;
<span class="hljs-keyword">import</span> { useMemo } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {
  GlowWalletAdapter,
  PhantomWalletAdapter,
  SolflareWalletAdapter,
  MathWalletAdapter,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@solana/wallet-adapter-wallets"</span>;
<span class="hljs-keyword">import</span> { clusterApiUrl } <span class="hljs-keyword">from</span> <span class="hljs-string">"@solana/web3.js"</span>;

<span class="hljs-keyword">const</span> theme = extendTheme({
  config: {
    initialColorMode: <span class="hljs-string">"dark"</span>,
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params">{ Component, pageProps }: AppProps</span>) </span>{
  <span class="hljs-keyword">const</span> wallets = useMemo(
    <span class="hljs-function">() =&gt;</span> [
      <span class="hljs-keyword">new</span> PhantomWalletAdapter(),
      <span class="hljs-keyword">new</span> SolflareWalletAdapter(),
      <span class="hljs-keyword">new</span> GlowWalletAdapter(),
      <span class="hljs-keyword">new</span> MathWalletAdapter(),
    ],
    []
  );

  <span class="hljs-keyword">const</span> endpoint = useMemo(<span class="hljs-function">() =&gt;</span> clusterApiUrl(<span class="hljs-string">"mainnet-beta"</span>), []);

  <span class="hljs-keyword">return</span> (
    &lt;ConnectionProvider endpoint={endpoint}&gt;
      &lt;ChakraProvider theme={theme}&gt;
        &lt;WalletProvider wallets={wallets} autoConnect&gt;
          &lt;Component {...pageProps} /&gt;
        &lt;/WalletProvider&gt;
      &lt;/ChakraProvider&gt;
    &lt;/ConnectionProvider&gt;
  );
}
</code></pre>
<p>We are setting up the <code>ConnectionProvider</code> and <code>WalletProvider</code> from <code>@solana/wallet-adapter-react</code> with the default Solana mainnet RPC URL and 4 wallets - Phantom, Solflare, Glow and Math Wallet (this is to demonstrate how the <code>wallets</code> list includes the wallet availability status).</p>
<p>Now replace <code>index.tsx</code> with the following -</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Container, Heading, VStack } <span class="hljs-keyword">from</span> <span class="hljs-string">"@chakra-ui/react"</span>;
<span class="hljs-keyword">import</span> { useWallet } <span class="hljs-keyword">from</span> <span class="hljs-string">"@solana/wallet-adapter-react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { wallets } = useWallet();

  <span class="hljs-built_in">console</span>.log(wallets);

  <span class="hljs-keyword">return</span> (
    &lt;Container <span class="hljs-keyword">as</span>=<span class="hljs-string">"main"</span> mt={<span class="hljs-number">32</span>} maxW=<span class="hljs-string">"3xl"</span>&gt;
      &lt;VStack w=<span class="hljs-string">"full"</span> gap={<span class="hljs-number">8</span>}&gt;
        &lt;Heading textAlign=<span class="hljs-string">"center"</span>&gt;
          Solana React Wallet Adapter Custom UI demo <span class="hljs-keyword">with</span> Chakra UI
        &lt;/Heading&gt;
      &lt;/VStack&gt;
    &lt;/Container&gt;
  );
}
</code></pre>
<p>Run <code>npm run dev</code> and go to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> on your browser. Open up devtools and look at the console -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676449270039/74e4125e-632b-4a3a-bfd2-dc7cc9b57c9f.png" alt class="image--center mx-auto" /></p>
<p>I have Backpack, Glow, Phantom, and Solflare installed so it shows up with a <code>readyState</code> of <code>Installed</code>. Note that Backpack, Glow and Phantom implement the <a target="_blank" href="https://github.com/solana-labs/wallet-standard">Solana Wallet Adapter Standard</a> and hence show up with <code>StandardWalletAdapter</code> for the <code>adapter</code> field. As I don't have Math Wallet, the <code>readyState</code> for Math Wallet is <code>NotDetected</code>.</p>
<p>Now make a <code>components</code> directory under <code>src</code> and make a <code>Wallets.tsx</code> file under that with the following code -</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { VStack, Button, Image, Text } <span class="hljs-keyword">from</span> <span class="hljs-string">"@chakra-ui/react"</span>;
<span class="hljs-keyword">import</span> { useWallet } <span class="hljs-keyword">from</span> <span class="hljs-string">"@solana/wallet-adapter-react"</span>;

<span class="hljs-keyword">const</span> Wallets = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { select, wallets, publicKey, disconnect } = useWallet();

  <span class="hljs-keyword">return</span> !publicKey ? (
    &lt;VStack gap={<span class="hljs-number">4</span>}&gt;
      {wallets.filter(<span class="hljs-function">(<span class="hljs-params">wallet</span>) =&gt;</span> wallet.readyState === <span class="hljs-string">"Installed"</span>).length &gt;
      <span class="hljs-number">0</span> ? (
        wallets
          .filter(<span class="hljs-function">(<span class="hljs-params">wallet</span>) =&gt;</span> wallet.readyState === <span class="hljs-string">"Installed"</span>)
          .map(<span class="hljs-function">(<span class="hljs-params">wallet</span>) =&gt;</span> (
            &lt;Button
              key={wallet.adapter.name}
              onClick={<span class="hljs-function">() =&gt;</span> select(wallet.adapter.name)}
              w=<span class="hljs-string">"64"</span>
              size=<span class="hljs-string">"lg"</span>
              fontSize=<span class="hljs-string">"md"</span>
              leftIcon={
                &lt;Image
                  src={wallet.adapter.icon}
                  alt={wallet.adapter.name}
                  h={<span class="hljs-number">6</span>}
                  w={<span class="hljs-number">6</span>}
                /&gt;
              }
            &gt;
              {wallet.adapter.name}
            &lt;/Button&gt;
          ))
      ) : (
        &lt;Text&gt;No wallet found. Please download a supported Solana wallet&lt;/Text&gt;
      )}
    &lt;/VStack&gt;
  ) : (
    &lt;VStack gap={<span class="hljs-number">4</span>}&gt;
      &lt;Text&gt;{publicKey.toBase58()}&lt;/Text&gt;
      &lt;Button onClick={disconnect}&gt;disconnect wallet&lt;/Button&gt;
    &lt;/VStack&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Wallets;
</code></pre>
<p>Now, replace <code>index.tsx</code> with the following -</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Heading, VStack } <span class="hljs-keyword">from</span> <span class="hljs-string">"@chakra-ui/react"</span>;
<span class="hljs-keyword">import</span> dynamic <span class="hljs-keyword">from</span> <span class="hljs-string">"next/dynamic"</span>;

<span class="hljs-keyword">const</span> Wallets = dynamic(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">"../components/Wallets"</span>), { ssr: <span class="hljs-literal">false</span> });

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">IndexPage</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;VStack gap={<span class="hljs-number">8</span>} mt={<span class="hljs-number">16</span>}&gt;
      &lt;Heading&gt;Solana Custom Wallet UI example (Chakra UI)&lt;/Heading&gt;

      &lt;Wallets /&gt;
    &lt;/VStack&gt;
  );
}
</code></pre>
<p>Notice that we have to import the <code>Wallets</code> component dynamically using <a target="_blank" href="https://nextjs.org/docs/advanced-features/dynamic-import"><code>next/dynamic</code></a> or else you will be faced with a React hydration error.</p>
<p>The webpage should look like this now -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676451170690/f4062bb9-ec1c-4354-929a-bd658baf10ad.png" alt class="image--center mx-auto" /></p>
<p>Note that it shows the wallets I have installed. You may have other wallets installed so it will show a different list. Notice how Math Wallet is not shown although I included the adapter. This is because we are filtering the <code>wallets</code> list making sure that <code>readyState</code> is <code>Installed</code>.</p>
<p>We can connect to a wallet simply by using the <code>select</code> method from <code>useWallet</code>. Note that <code>select</code> calls <code>connect</code> automatically. If the wallet is disconnected but a wallet is selected, we can call <code>connect</code> to connect to the currently selected wallet. The selected wallet state is persisted in the local storage. Note that calling <code>disconnect</code> removes the selected state as well but it is persisted between reloads and wallet locks.</p>
<p>Now if I click on any of these buttons, the wallet will pop up and ask me to connect. After connecting, the page should look like this -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676451476952/2cf367cd-8bfc-42f8-85c3-f0090e417fa2.png" alt class="image--center mx-auto" /></p>
<p>Clicking "disconnect wallet" simply calls <code>disconnect</code> and the wallet is disconnected and the wallet state is removed from local storage.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This guide went through how <code>@solana/wallet-adapter-react-ui</code> and then went over how to create a Connect Wallet feature in your React application simply using <code>@solana/wallet-adapter-react</code> and Chakra UI.</p>
<p>Check out the <a target="_blank" href="https://github.com/AnishDe12020/solana-react-wallet-adapter-custom-ui-example">GitHub Repository</a> if you want to take a look at the final code for this guide. You can also check out the deployed version - <a target="_blank" href="https://solana-react-wallet-adapter-custom-ui-example.vercel.app/">https://solana-react-wallet-adapter-custom-ui-example.vercel.app/</a></p>
<p>Now you can try to extend this by creating a modal or a multi-button with a nice dropdown to disconnect or change the wallet just like how the official React UI package does it, but with your styles of course.</p>
]]></content:encoded></item><item><title><![CDATA[How to quickly create a Gasless NFT Collection on Solana with CandyPay]]></title><description><![CDATA[Let's say you are an NFT creator and want to publish your NFT onto the Solana blockchain quickly. This can be time-consuming as it involves creating the collection, setting up a minting website, etc. It also requires you to be familiar with all the t...]]></description><link>https://blog.anishde.dev/how-to-quickly-create-a-gasless-nft-collection-on-solana-with-candypay</link><guid isPermaLink="true">https://blog.anishde.dev/how-to-quickly-create-a-gasless-nft-collection-on-solana-with-candypay</guid><category><![CDATA[Solana]]></category><category><![CDATA[NFT]]></category><category><![CDATA[Web3]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Mon, 29 Aug 2022 10:22:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661675955811/Oyqc_FemE.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's say you are an NFT creator and want to publish your NFT onto the Solana blockchain quickly. This can be time-consuming as it involves creating the collection, setting up a minting website, etc. It also requires you to be familiar with all the technologies and as a creator, you may not be familiar with all the development stuff. </p>
<p>That is where <a target="_blank" href="https://candypay.fun/">CandyPay</a> comes in. CandyPay lets you create NFTs with a no-code builder and also lets users easily claim them via <a target="_blank" href="https://solanapay.com/">Solana Pay</a> on mobile without worrying about installing any browser extension or connecting their wallet.</p>
<p>Additionally, we are going to be looking at gasless NFTs which do not cost any SOL to the receiver to claim. Instead, we, the creator of the NFT pre-pay the gas. Note that we can get back our SOL if any of the pre-paid SOL remains unused.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>All of these don't require you to pay any real money.</p>
<ul>
<li>A Solana wallet with Solana pay support. You can use <a target="_blank" href="https://phantom.app/">Phantom</a> or <a target="_blank" href="https://solflare.com/">Solflare</a>, both of which are available for Android, iOS, and as a browser extension as well although you will need the mobile app for Solana pay.</li>
<li>An image to upload as an NFT (you can use anything for this as we are going to be doing it on the devnet anyways). I am going to be using this one, feel free to use it for this tutorial - </li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661607325449/5kqGckuOX.png" alt="The NFT.png" /></p>
<ul>
<li>Some devnet SOL. You can get it from <a target="_blank" href="https://solfaucet.com/">a Solana faucet</a> (it is free and no signup is required)</li>
</ul>
<h2 id="heading-creating-a-gasless-nft-collection-with-candypay">Creating a Gasless NFT collection with CandyPay</h2>
<p>First of all, we need to create a CandyPay account. Head over to <a target="_blank" href="https://candypay.fun/">the CandyPay web app</a> and sign up for an account. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661599679040/kIXFWJYte.png" alt="image.png" /></p>
<p>Next, select "Gasless Collection" under the "Create New" dropdown</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661600362928/NwPTXXPiT.png" alt="image.png" /></p>
<p>Here, we have to provide some details about the NFT, and of course, the image itself. Here is an explanation of the various fields - </p>
<ul>
<li>NFT Name (required) - The name of the NFT</li>
<li>NFT Description - You may want to add a description describing what the NFT is about, its importance, etc.</li>
<li>Wallet Address - Your Solana wallet address</li>
<li>Symbol - You can provide a symbol for your NFT. This is usually shown in marketplaces and wallet apps</li>
<li>Royalty Fee - For any subsequent sales after the first one, this much percent of the amount will be given to you as royalty</li>
<li>Collection size (required) - The number of NFTs the collection is going to have</li>
<li>External URL - Any URL which you want to associate with your NFT</li>
<li>Network - The Solana network you want to publish your collection to. When building a production app, you should choose "Mainnet" (and it will cost some real money in the form of real SOL to publish the collection and pre-pay the gas) but for the purpose of this tutorial, we are choosing "Devnet" (we are going to be using the devnet SOL we got from the faucet, which didn't cost us any real money)</li>
<li>NFT Image (required) - The image that will be used for the NFT</li>
</ul>
<p>I have entered the values for these fields as follows - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661669840416/U31BTyY4f.png" alt="image.png" /></p>
<p>Now click on "Continue". In the next page, we can customize our CandyPay QR code. This will be the QR Code that the receiver of the NFT will scan in order to get the NFT, so let us try to make it beautiful :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661609022601/Hf9MSuH1F.png" alt="image.png" /></p>
<p>I have changed some of the shapes, and also changed the black CandyPay logo to one with the CandyPay accent color :)</p>
<p>Now again click "Continue". In the next step, we have to pay some SOL to create the collection as well as the gas for the 10 NFTs. Note that we can redeem any unused funds later on. </p>
<p>Scan this QR Code with your mobile wallet app and you should see a Solana Pay popup. If you see an error in Phantom which looks like this, try using Solflare (in Solflare, it is a 2 step process and the UI is a little different but the result is the same) -  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661669446505/wEIPvotEW.png" alt="image.png" /></p>
<p>After scanning the QR Code, you should see something like this on your mobile wallet app - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661669794663/tpyrLNLTS.png" alt="image.png" /></p>
<p>Pay the devnet SOL and let the transaction succeed (shouldn't take more than 10 seconds). After that, you should see a success message in CandyPay - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661670086416/9OQ4F3BPU.png" alt="image.png" /></p>
<p>On the collection dashboard, you will be able to see the QR code one can scan to redeem the NFT. There is a link as well which one can open on their phone to redeem the NFT. </p>
<p>Oh, by the way, that QR code in the screenshot actually works so you can try to scan the QR code to get this NFT until supply lasts of course.</p>
<h2 id="heading-claiming-an-nft-using-the-candypay-qr-code">Claiming an NFT using the CandyPay QR Code</h2>
<p>If I now scan the QR Code, I will be prompted to confirm a transaction but notice that no SOL is getting deducted from my side, so it is gasless! (I had to do it with Solflare this time as Phantom errored out)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661672602690/d0D4Oq1Mt.png" alt="image.png" /></p>
<p>Now if I go back to the dashboard, I can see that 1 NFT has been redeemed (ignore the cancelled ones as it was just Phantom being buggy). The gas has been deducted as well!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661672708481/kRz7UFp2b.png" alt="image.png" /></p>
<p>For more clarity on if I had to pay gas or not, if I check the <a target="_blank" href="https://solscan.io/tx/3z5U4gmVJUtPVe4YuFKj29A1JJZiqY4MtLy4pokzJ2DKZ8vi7ibCTcuU78jtqqcdfw49qKH7Wxg9WbB8tJ9rEJHh?cluster=devnet">transaction on Solscan</a>, under the "SOL Balance Change" tab, I can see that there was no change in my SOL balance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661673463418/0YgBR6q9q.png" alt="image.png" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now it's time for you to ship your gasless NFT collections! Feel free to share them down below, really excited to see what y'all build :)</p>
<h2 id="heading-important-links">Important Links</h2>
<ul>
<li><a target="_blank" href="https://candypay.fun/">CandyPay</a></li>
<li><a target="_blank" href="https://discord.com/invite/VGjPXWUHGT">CandyPay Discord Server</a> (you can get support here)</li>
<li><a target="_blank" href="https://solanapay.com/">Solana Pay</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[5 Modern CLI tools that help boost your productivity]]></title><description><![CDATA[As developers, most of us use the terminal to interact with our computers for many tasks as we find it more productive. We are familiar with commands like ls, cd, cat, grep, and find. These are primarily pre-installed on our computers and mostly get ...]]></description><link>https://blog.anishde.dev/5-modern-cli-tools-that-help-boost-your-productivity</link><guid isPermaLink="true">https://blog.anishde.dev/5-modern-cli-tools-that-help-boost-your-productivity</guid><category><![CDATA[Productivity]]></category><category><![CDATA[cli]]></category><category><![CDATA[command line]]></category><category><![CDATA[Rust]]></category><category><![CDATA[terminal]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sat, 13 Aug 2022 12:36:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660393311485/olDcPwC6h.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As developers, most of us use the terminal to interact with our computers for many tasks as we find it more productive. We are familiar with commands like <code>ls</code>, <code>cd</code>, <code>cat</code>, <code>grep</code>, and <code>find</code>. These are primarily pre-installed on our computers and mostly get the job done hence, we never consider looking for any alternatives.</p>
<p>But, today we are going to look at 5 alternatives that accomplish the same task but are more feature-rich, faster, and cleaner. Coincidentally, these are all written in the rust programming language.</p>
<h2 id="heading-bat"><code>bat</code></h2>
<p><a target="_blank" href="https://github.com/sharkdp/bat"><code>bat</code></a> is a popular alternative for the <code>cat</code> command, just with a ton of more features. So, what are they?</p>
<h3 id="heading-syntax-highlighting">Syntax highlighting</h3>
<p><code>bat</code> automatically provides syntax highlighting for all major programming languages.</p>
<h3 id="heading-line-numbers">Line numbers</h3>
<p>This might not be a big one but <code>bat</code> shows line numbers, and I have found it extremely useful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660301066725/8saJ0Tcj5.png" alt="image.png" /></p>
<h3 id="heading-search">Search</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660301322679/49XrPp1-X.png" alt="image.png" /></p>
<p>We can use <code>/</code> and then enter a query (can be regex) to perform a search operation. This is similar to how it is done in vim, and yes, it supports vim keybindings like <code>n</code> to go to the next result and <code>N</code> to go to the previous result.</p>
<h2 id="heading-zoxide"><code>zoxide</code></h2>
<p><a target="_blank" href="https://github.com/ajeetdsouza/zoxide"><code>zoxide</code></a> behaves like <code>cd</code> at first glance but it has 1 feature which makes it a game-changer. How cool would it be if you did not have to specify the path to a directory every time you wanted to change into it? Zoxide stores paths in a db, and the next time you use it, you can just specify the directory name instead of the full path. Here it is in action (<code>z</code> is the default alias for zoxide) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660301811431/xHhqVzRNx.png" alt="image.png" /></p>
<p>You can also use the <code>zi</code> command to interactively select previous paths using <a target="_blank" href="https://github.com/junegunn/fzf"><code>fzf</code></a> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660301993213/J_S2ZlpPo.png" alt="image.png" /></p>
<h2 id="heading-exa"><code>exa</code></h2>
<p><a target="_blank" href="https://github.com/ogham/exa"><code>exa</code></a> is a modern replacement for the <code>ls</code> command but with more features. First of all, it supports colors and icons (I have aliased <code>ls</code> to <code>exa --icons --color=always</code>) -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660302192319/TPjECGpvE.png" alt="image.png" /></p>
<p>This makes distinguishing folders and files extremely easy and the icons are just a great touch. Also, the list view (pass in the <code>-l</code> to see it in the list view) is way cleaner.</p>
<p>Exa also comes with a handy tree feature - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660303178064/UQrSBIqSq.png" alt="image.png" /></p>
<p>Here, <code>-T</code> is for displaying it as a tree. The <code>--git-ignore</code> flag ignores files and folders mentioned in the <code>.gitignore</code> ignore file.</p>
<h2 id="heading-fd"><code>fd</code></h2>
<p><a target="_blank" href="https://github.com/sharkdp/fd"><code>fd</code></a> is an alternative to the <code>find</code> command packed with features and is also extremely fast.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660303551209/tmVR0VXyZ.png" alt="image.png" /></p>
<p>The first argument is the term we want to search and any other arguments after that will be directories to search in.</p>
<p>We can also specify an extension with the <code>-e</code> flag - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660303647841/LgRlovka3.png" alt="image.png" /></p>
<h2 id="heading-ripgrep"><code>ripgrep</code></h2>
<p><a target="_blank" href="https://github.com/BurntSushi/ripgrep"><code>ripgrep</code></a> is an alternative to the <code>grep</code> command and the main highlight is its speed. It also automatically ignores files specified in ignore files like <code>.gitignore</code> and <code>.ignore</code>. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660304440208/7FkNjBeCy.png" alt="image.png" /></p>
<p>Yes, that took just 20 milliseconds!</p>
<p>Ripgrep comes with many other features too, like searching in specific file types and searching inside zips. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660304800607/Tj8WyJOgt.png" alt="image.png" /></p>
<p>Here, we can specify the file type using the <code>-t</code> flag.</p>
<h2 id="heading-bonus-tealdeer">BONUS: <code>tealdeer</code></h2>
<p><a target="_blank" href="https://github.com/dbrgn/tealdeer"><code>tealdeer</code></a> is an alternative to the <a target="_blank" href="https://github.com/tldr-pages/tldr"><code>tldr</code></a> tool. Both accomplish the same task, that is, showing community-driven help/man pages which are easier to read and understand than the traditional, detailed ones. Here is an example for <code>exa</code> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660306721508/ncF7d_nJ1.png" alt="image.png" /></p>
<p>Tealdeer installs as <code>tldr</code> and hence <code>tldr</code> is the command and not <code>tealdeer</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you have found this article useful and that it helped boost your productivity. You can leave any suggestions via comments or you can dm them to me on <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a> :)</p>
]]></content:encoded></item><item><title><![CDATA[Forgit and Lazygit. The 2 Git tools to supercharge your git workflow?]]></title><description><![CDATA[Most of us use version control systems (mostly git) for our projects but the git CLI is unproductive. We often need to run multiple commands and got to type more characters. 
Well, what if I told you there are tools that can improve this significantl...]]></description><link>https://blog.anishde.dev/forgit-and-lazygit-the-2-git-tools-to-supercharge-your-git-workflow</link><guid isPermaLink="true">https://blog.anishde.dev/forgit-and-lazygit-the-2-git-tools-to-supercharge-your-git-workflow</guid><category><![CDATA[Git]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[command line]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sat, 23 Jul 2022 08:48:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/842ofHC6MaI/upload/v1658565475883/khowiElqD.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most of us use version control systems (mostly git) for our projects but the <code>git</code> CLI is unproductive. We often need to run multiple commands and got to type more characters. </p>
<p>Well, what if I told you there are tools that can improve this significantly. We are going to be looking at 2 tools today, <a target="_blank" href="https://github.com/wfxr/forgit">forgit</a> and <a target="_blank" href="https://github.com/jesseduffield/lazygit">lazygit</a>. Both of these tools let us do many of our day-to-day git tasks, interactively and come with a LOT of keyboard shortcuts. </p>
<h2 id="heading-forgit">Forgit</h2>
<p><a target="_blank" href="https://github.com/wfxr/forgit">Forgit</a>, a simple and lightweight wrapper around git commands which uses <a target="_blank" href="https://github.com/junegunn/fzf">fzf</a> to provide interactivity to git commands (and some more goodies :D).</p>
<p>For example, this is how the <code>ga</code> (<code>git add</code> equivalent) looks like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563394521/nTOe8gHny.png" alt="ga command in forgit" /></p>
<p>YES, that is a diff view!</p>
<p>Oh and it is not limited to just staging files, there are many more interactive commands (each with its own alias :D )</p>
<p>Want to explore all the previous git commits? Run <code>glo</code> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563446553/5pq7J1rZN.png" alt="glo command in forgit" /></p>
<p>Want to see the list of branches and checkout to the appropriate one? Use <code>gcb</code> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658562681258/9h3PNnkRB.png" alt="gcb command in forgit" /></p>
<p>And there are more! For a full list, you can see the <a target="_blank" href="https://github.com/wfxr/forgit#-features">features section in the forgit README</a></p>
<h2 id="heading-lazygit">Lazygit</h2>
<p><a target="_blank" href="https://github.com/jesseduffield/lazygit">Lazygit</a> on the other hand is a TUI written in Go and is crazy powerful. This is how it looks like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563520539/ZNlT0Uxrw.png" alt="lazygit tui" /></p>
<p>Yeah those are a lot of panes indeed. Files can be staged/unstaged easily by pressing <kbd>Space</kbd> and pressing <kbd>c</kbd> brings up a modal for writing a commit message - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563609546/VozxIJSeg.png" alt="commit modal in lazygit" /></p>
<p>Once you are done writing the commit message, press <kbd>Enter</kbd> and the changes get committed :D</p>
<p>Oh and we can see a log of everything we do as well as any command output - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563645033/zZr1ftsqi.png" alt="command log in lazygit" /></p>
<p>And for all of us who hate using the mouse (although lazygit has mouse support), there are a TON of keyboard shortcuts - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658563733510/XousH_fns.png" alt="keyboard shortcuts in lazygit" /></p>
<p>You can see look at some more things it can do (like resolving merge conflicts and interactive rebasing) in the <a target="_blank" href="https://github.com/jesseduffield/lazygit#cool-features">lazygit README</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So, which one should you use? This depends on your use case and how you want to use the tools.</p>
<p>I personally use the git CLI, forgit, lazygit, and the vscode source control panel depending on what I'm doing. I always use lazygit inside neovim but when I am using vscode, it is mostly forgit and the git CLI (I rarely use the source control pane). </p>
<h2 id="heading-links">Links</h2>
<ul>
<li><a target="_blank" href="https://github.com/wfxr/forgit">Forgit</a></li>
<li><a target="_blank" href="https://github.com/jesseduffield/lazygit">Lazygit</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Make a beautiful Connect Wallet Button with RainbowKit and React]]></title><description><![CDATA[Authentication in Web3 is extremely easy but supporting all the wallets and making a nice UI can be painful and time-consuming. Thankfully, there are many libraries which makes this extremely easy as well. Today we are going to be looking at adding R...]]></description><link>https://blog.anishde.dev/make-a-beautiful-connect-wallet-button-with-rainbowkit-and-react</link><guid isPermaLink="true">https://blog.anishde.dev/make-a-beautiful-connect-wallet-button-with-rainbowkit-and-react</guid><category><![CDATA[THW Web3]]></category><category><![CDATA[Web3]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sat, 14 May 2022 09:47:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652521066137/sJ3tsAZoN.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Authentication in Web3 is extremely easy but supporting all the wallets and making a nice UI can be painful and time-consuming. Thankfully, there are many libraries which makes this extremely easy as well. Today we are going to be looking at adding <a target="_blank" href="https://www.rainbowkit.com/">RainbowKit</a> to a React App.</p>
<h2 id="heading-what-is-rainbowkit">What is RainbowKit?</h2>
<p>RainbowKit is a React library that provides us with components to build a Connect Wallet UI in a few lines of code. It comes with support for many wallets, including Metamask, Rainbow, Coinbase Wallet, WalletConnect, and many more. It is also extremely customizable and comes with an amazing built-in theme.</p>
<p>RainbowKit uses <a target="_blank" href="https://github.com/ethers-io/ethers.js">Ethers.js</a> and <a target="_blank" href="https://github.com/tmm/wagmi">Wagmi</a>, both popular libraries in this space, under the hood.</p>
<p>Also, it is developed by the same team behind the beautiful <a target="_blank" href="https://rainbow.me/">Rainbow Wallet</a></p>
<h2 id="heading-creating-a-new-nextjs-app">Creating a new Next.js App</h2>
<p>Run the following command to create a new Next.js app (note that you can use it on a regular React app too) - </p>
<pre><code class="lang-sh"><span class="hljs-comment"># With NPM</span>
npx create-next-app rainbowkit-demo
<span class="hljs-comment"># With yarn</span>
yarn create next-app rainbowkit-demo
</code></pre>
<p>Now, move into the project directory and open it in your favorite code editor.</p>
<h2 id="heading-adding-rainbowkit-to-our-react-app">Adding RainbowKit to our React app</h2>
<p>Run the following command to install RainbowKit and its peer dependencies - </p>
<pre><code class="lang-sh"><span class="hljs-comment"># With NPM</span>
npm install @rainbow-me/rainbowkit wagmi ethers
<span class="hljs-comment"># With yarn</span>
yarn add @rainbow-me/rainbowkit wagmi ethers
</code></pre>
<p>Now add the following code to <code>pages/_app.js</code> -</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/globals.css"</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">"@rainbow-me/rainbowkit/styles.css"</span>;

<span class="hljs-keyword">import</span> {
  apiProvider,
  configureChains,
  getDefaultWallets,
  RainbowKitProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@rainbow-me/rainbowkit"</span>;
<span class="hljs-keyword">import</span> { chain, createClient, WagmiProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;

<span class="hljs-keyword">const</span> { chains, provider } = configureChains(
  [
    chain.mainnet,
    chain.polygon,
    chain.goerli,
    chain.rinkeby,
    chain.polygonMumbai,
  ],
  [apiProvider.fallback()]
);

<span class="hljs-keyword">const</span> { connectors } = getDefaultWallets({
  <span class="hljs-attr">appName</span>: <span class="hljs-string">"My RainbowKit App"</span>,
  chains,
});

<span class="hljs-keyword">const</span> wagmiClient = createClient({
  <span class="hljs-attr">autoConnect</span>: <span class="hljs-literal">true</span>,
  connectors,
  provider,
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">WagmiProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{wagmiClient}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">RainbowKitProvider</span> <span class="hljs-attr">chains</span>=<span class="hljs-string">{chains}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">RainbowKitProvider</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">WagmiProvider</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp;
</code></pre>
<p>Firstly, we import the RainbowKit styles, the <code>RainbowKitPovider</code> and other functions to configure our chains, and the <code>WagmiProvider</code>.</p>
<p>Next, we configure the chains which we want to support. In this example, I have added the Ethereum Mainnet, Polygon Mainnet, Goerli and Rinkeby (both Ethereum test networks), and the Polygon Mumbai testnet. We are using the public fallback RPC URLs for the purpose of this demo for our API providers. RainbowKit also lets us specify our own JSON RPC URLs or Alchemy or Infura URLs for our API providers. You can see the <a target="_blank" href="https://www.rainbowkit.com/docs/api-providers">API Providers documentation here</a>.</p>
<p>Next, we create our <code>wagmiClient</code>, passing in the <code>autoConnect</code> and setting it to <code>true</code>. Our app will automatically reconnect to the last used connector this way.</p>
<p>At last, we wrap our application with <code>WagmiProvider</code> and <code>RainbowKitProvider</code>. </p>
<p>Next, let us add the Connect Wallet button to our app. Replace the code in <code>pages/index.js</code> with the following - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { ConnectButton } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rainbow-me/rainbowkit"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/Home.module.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>RainbowKit Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
          <span class="hljs-attr">content</span>=<span class="hljs-string">"Demo app part of a tutorial on adding RainbowKit to a React application"</span>
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.main}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.title}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginBottom:</span> "<span class="hljs-attr">4rem</span>" }}&gt;</span>
          Welcome to this demo of{" "}
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://www.rainbowkit.com/"</span>&gt;</span>RainbowKit<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">ConnectButton</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Now run <code>npm run dev</code> or <code>yarn dev</code> and open up <a target="_blank" href="http://localhost:3000">localhost:3000</a> in your browser and you should see this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652518368819/WWI1V_IJQ.gif" alt="Peek 2022-05-14 14-22.gif" /></p>
<h2 id="heading-making-it-dark-mode">Making it dark mode 🌑</h2>
<p>Time to make sure our eyes don't burn anymore. </p>
<p>Head over to <code>pages/_app.js</code> and import the <code>midnightTheme</code> function from RainbowKit. (Alternatively, you can also import the <code>darkTheme</code> function, a dimmer version of midnight)</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {
  apiProvider,
  configureChains,
  getDefaultWallets,
  midnightTheme,
  RainbowKitProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@rainbow-me/rainbowkit"</span>;
</code></pre>
<p>We must also pass in our theme to <code>RainbowKitProvider</code> - </p>
<pre><code class="lang-js">&lt;RainbowKitProvider chains={chains} theme={midnightTheme()}&gt;
</code></pre>
<p>RainbowKit supports more advanced theming, you can see the <a target="_blank" href="https://www.rainbowkit.com/docs/theming">RainbowKit Theming docs here</a> for more information.</p>
<p>Also, add this small piece of code to <code>styles/globals.css</code> to make our app dark mode too - </p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#010101</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#f0f0f0</span>;
}
</code></pre>
<p>Now our app should look like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652518912187/Z5fwbguf9.gif" alt="Peek 2022-05-14 14-31.gif" /></p>
<h2 id="heading-a-tour-of-rainbowkit">A tour of RainbowKit</h2>
<p>After authenticating with a wallet, our connect button will automatically change to a network switcher which also show us our balance and wallet address - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652518983014/PG0nny96e.png" alt="image.png" /></p>
<p>Switching the network is as easy as clicking the network switcher and then selecting the network we want to switch to - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652519079358/IRQKD8BrJ.gif" alt="Peek 2022-05-14 14-33.gif" /></p>
<p>Clicking on our wallet address gives us a modal with the option to copy our address or disconnect our wallet - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652519167948/wZBoGNlNT.gif" alt="Peek 2022-05-14 14-35.gif" /></p>
<h2 id="heading-cool-mode">Cool Mode 😎</h2>
<p>Let's make our application a little cooler :)
Just add the <code>coolMode</code> prop to <code>RainbowKitProvider</code> -</p>
<pre><code class="lang-js">&lt;RainbowKitProvider chains={chains} theme={midnightTheme()} coolMode&gt;
</code></pre>
<p>Now if we click on any of the options in the connect modal, we will get some amazing confetti 🎊</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652519690123/kf66Lk7qK.gif" alt="Peek 2022-05-14 14-44.gif" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That was a basic demo of what RainbowKit can do, but it can do a lot more like show recent transactions. The best place to learn more about it is <a target="_blank" href="https://www.rainbowkit.com/">RainbowKit docs</a>.</p>
<h2 id="heading-important-links">Important Links</h2>
<ul>
<li><a target="_blank" href="https://github.com/AnishDe12020/rainbowkit-demo">Source Code</a></li>
<li><a target="_blank" href="https://rainbowkit-demo.vercel.app/">Deployed Preview</a></li>
<li><a target="_blank" href="https://www.rainbowkit.com/">RainbowKit</a></li>
<li><a target="_blank" href="https://github.com/rainbow-me/rainbowkit">RainbowKit GitHub</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[5 Amazing React Component Libraries to Consider for your Next Project]]></title><description><![CDATA[As web developers, it is often quite hard and time-consuming to make accessible UIs. This gets even worse when we have to make special components like Modals or Popovers from scratch.
Thankfully, the React ecosystem is huge and there are many great p...]]></description><link>https://blog.anishde.dev/5-amazing-react-component-libraries-to-consider-for-your-next-project</link><guid isPermaLink="true">https://blog.anishde.dev/5-amazing-react-component-libraries-to-consider-for-your-next-project</guid><category><![CDATA[THW Web Apps]]></category><category><![CDATA[React]]></category><category><![CDATA[components]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[Material Design]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Mon, 09 May 2022 10:04:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/XmZ4GDAp9G0/upload/v1652090173489/BrJWe16IU.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As web developers, it is often quite hard and time-consuming to make accessible UIs. This gets even worse when we have to make special components like Modals or Popovers from scratch.</p>
<p>Thankfully, the React ecosystem is huge and there are many great people who have made amazing libraries to help us with this problem. Today, we are going to focus on React component libraries that are accessible, have a decent base style, have good docs, and come with components like Modals, Popovers, Tooltips, etc.</p>
<h2 id="heading-1-chakra-uihttpschakra-uicom">1. <a target="_blank" href="https://chakra-ui.com/">Chakra UI</a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652085007803/QbhtHwWIY.png" alt="image.png" /></p>
<p>When I started out with Next.js, Chakra UI was the first component library I ever used, and it was amazing! I was able to make quite complex UIs (with modals and tables and everything) in not much time and that helped me focus on other things like application logic. It is the perfect one to use for Hackathons! It also has a huge community and is extremely popular.</p>
<h2 id="heading-2-next-uihttpsnextuiorg">2. <a target="_blank" href="https://nextui.org/">Next UI</a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652085218540/wVFj0gDd1.png" alt="image.png" /></p>
<p>Next UI is probably the most beautiful out of all 5 in this article. Although it is quite new and still in the beta stage, it comes with all the essentials and looks absolutely amazing out of the box! It also comes with some amazing transitions and animations out of the box, that other component libraries don't come with.</p>
<h2 id="heading-3-muihttpsmuicom">3. <a target="_blank" href="https://mui.com/">MUI</a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652085809008/yXZ8rAykw.png" alt="image.png" /></p>
<p>MUI also has been around for a long time and was called Material UI. It is based on <a target="_blank" href="https://material.io/">Material Design by Google</a> but also comes with an extensive level of customization. Moreover, MUI also provides an <a target="_blank" href="https://mui.com/base/getting-started/installation/">unstyled version</a> and <a target="_blank" href="https://mui.com/system/basics/">a package with some amazing CSS utilities</a>. MUI also provides a set of <a target="_blank" href="https://mui.com/x/advanced-components/">advanced components</a> under MUI X. Some of these components are free but some require a paid license.</p>
<h2 id="heading-4-mantinehttpsmantinedev">4. <a target="_blank" href="https://mantine.dev/">Mantine</a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652087017990/V0lwKr61B.png" alt="image.png" /></p>
<p>Mantine also comes with a lot of components and a decent out-of-the-box UI. It is a lot like Chakra UI but has a smaller community. It, however, also comes with some amazing packages like <a target="_blank" href="https://mantine.dev/others/notifications/">a notification center</a>, <a target="_blank" href="https://mantine.dev/others/spotlight/">a command bar</a>, <a target="_blank" href="https://mantine.dev/others/rte/">a rich text editor</a> and much more!</p>
<h2 id="heading-5-react-daisy-uihttpsreactdaisyuicom">5. <a target="_blank" href="https://react.daisyui.com/">React Daisy UI</a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652088634825/dYUJWf7_n.png" alt="image.png" /></p>
<p><a target="_blank" href="https://daisyui.com/">Daisy UI</a> is an amazing <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS </a> component library. React Daisy UI is React component library for Daisy UI. It comes with a huge number of themes out of the box and a lot of components as well. As it is based on Tailwind CSS and comes with it, it is extremely easy to customize it with Tailwind CSS.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now those were 5 React component libraries that will help you speed up development. Do let me know if you have any other favorites or which one you liked the most of these 5!</p>
]]></content:encoded></item><item><title><![CDATA[Amazing preview images with Next.js and LQIP Modern]]></title><description><![CDATA[Images take a long time to load and can have a disruptive impact on UX. Today we are going to be looking at creating preview images with a library called lqip-modern. 
What is LQIP?
LQIP simply stands for Low Quality Image Placeholders. They have ext...]]></description><link>https://blog.anishde.dev/amazing-preview-images-with-nextjs-and-lqip-modern</link><guid isPermaLink="true">https://blog.anishde.dev/amazing-preview-images-with-nextjs-and-lqip-modern</guid><category><![CDATA[THW Web Apps]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[image processing]]></category><category><![CDATA[optimization]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Mon, 02 May 2022 10:05:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1651485793452/F3aixNLhI.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Images take a long time to load and can have a disruptive impact on UX. Today we are going to be looking at creating preview images with a library called <a target="_blank" href="https://www.npmjs.com/package/lqip-modern"><code>lqip-modern</code></a>. </p>
<h2 id="heading-what-is-lqip">What is LQIP?</h2>
<p><strong>LQIP</strong> simply stands for <strong>L</strong>ow <strong>Q</strong>uality <strong>I</strong>mage <strong>P</strong>laceholders. They have extremely small file sizes and act as placeholders for the actual image while the actual image is still loading. These extremely small file sizes are obtained by blurring the image, resizing it to a smaller size, or reducing the quality in the case of JPEGs.</p>
<h2 id="heading-compatibility">Compatibility</h2>
<p>WebP is supported by all modern browsers. Also, WebP support is present in Safari on macOS only if one is using macOS 11 (Big Sur) or later. <a target="_blank" href="https://caniuse.com/?search=webp">source</a></p>
<p>If 100% compatibility is the goal, we can use JPEG LQIPs as well (they are almost 2-3 times the size of a WebP image).</p>
<p>Let us now look at how we can use <code>lqip-modern</code> with Next.js</p>
<h2 id="heading-using-lqip-modern-with-nextjs">Using LQIP Modern with Next.js</h2>
<p>Next.js has an in-built <a target="_blank" href="https://nextjs.org/docs/api-reference/next/image">next/image</a> component which can provide preview images for local files without the use of an external library, but it cannot for remote images.</p>
<p>Now, there is also a limitation with our approach here, that is, preview images are created at build time. This means, that if the external image changes, then the preview image will not change. </p>
<p>However, this method will be especially useful if you are fetching the image from a CMS. If the image is ever updated, a build can be triggered which will create a new preview image. A better approach would be to use <a target="_blank" href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation-beta">on-demand Incremental Static Regeneration</a> or regular incremental static regeneration, however, that is out of the scope of this article. You can read <a target="_blank" href="https://blog.anishde.dev/making-a-blog-with-directus-mdx-and-nextjs-on-demand-isr">my blog post on implementing on-demand incremental static regeneration with Directus</a> to learn more.</p>
<p>In this example, we are going to look at creating preview images for an image from Unsplash. I am going to be using <a target="_blank" href="https://images.unsplash.com/photo-1642083139428-9ee5fa423c46">this awesome image of a Vercel mug along with some computer peripherals</a> for this tutorial. However, you can choose any picture you like.</p>
<p>Firstly, let us create a new Next.js application - </p>
<pre><code>npx <span class="hljs-keyword">create</span>-<span class="hljs-keyword">next</span>-app <span class="hljs-keyword">next</span>-lqip-demo
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">create</span> <span class="hljs-keyword">next</span>-app <span class="hljs-keyword">next</span>-lqip-demo
</code></pre><p>After it has been created, open up the project in your favorite code editor.</p>
<p>Now, open up the <code>pages/index.js</code> file and replace it with the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/Home.module.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>LQIP demo with Next.js<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Generated by create next app"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.main}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.title}</span>&gt;</span>
          Welcome to{" "}
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://nextjs.org"</span>&gt;</span>this demo of LQIP with Next.js!<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">4rem</span>" }}&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">"https://images.unsplash.com/photo-1642083139428-9ee5fa423c46"</span>
            <span class="hljs-attr">alt</span>=<span class="hljs-string">"Vercel mug with computer peripherals"</span>
            <span class="hljs-attr">height</span>=<span class="hljs-string">{480}</span>
            <span class="hljs-attr">width</span>=<span class="hljs-string">{320}</span>
          /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Also, replace the code inside <code>next.config.js</code> with the following - </p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('next').NextConfig}</span> </span>*/</span>
<span class="hljs-keyword">const</span> nextConfig = {
  <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">images</span>: {
    <span class="hljs-attr">domains</span>: [<span class="hljs-string">"images.unsplash.com"</span>],
  },
};

<span class="hljs-built_in">module</span>.exports = nextConfig;
</code></pre>
<p>We are using the <code>next/image</code> component to show our image from Unsplash. As the image is from a remote URL, we have to also add the domain in <code>next.config.js</code>.</p>
<p>Now run <code>npm run dev</code> or <code>yarn dev</code> to start a development server and then visit <a target="_blank" href="http://localhost:3000/">localhost:3000</a>. You will be able to see the page heading with the image - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651481994200/3UFVupiF_.png" alt="image.png" /></p>
<p>When you first visited the page, you would have noticed the image took a split of a second to load. Depending on your internet connection, it can be fast or slow. If you have a fast internet connection, open up developer tools and go to the network tab. Here you can throttle your internet connection to simulate a slow loading time - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651482154248/ZMGC1ekZo.png" alt="image.png" /></p>
<h3 id="heading-using-lqip-to-optimize-our-remote-image">Using LQIP to optimize our remote image</h3>
<p>Firstly, let us install <code>lqip-modern</code>, and <a target="_blank" href="https://www.npmjs.com/package/sharp"><code>sharp</code></a>. Sharp is an awesome package that helps with image transformations and is used by <code>lqip-modern</code> - </p>
<pre><code>npm <span class="hljs-keyword">install</span> <span class="hljs-comment">--save lqip-modern sharp</span>
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">add</span> lqip-modern sharp
</code></pre><p>Now, replace the code in <code>pages/index.js</code> with the following - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> lqipModern <span class="hljs-keyword">from</span> <span class="hljs-string">"lqip-modern"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/Home.module.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params">{ imageUrl, previewImageUrl }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>LQIP demo with Next.js<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Generated by create next app"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.main}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.title}</span>&gt;</span>
          Welcome to{" "}
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://nextjs.org"</span>&gt;</span>this demo of LQIP with Next.js!<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> "<span class="hljs-attr">4rem</span>" }}&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">{imageUrl}</span>
            <span class="hljs-attr">alt</span>=<span class="hljs-string">"Vercel mug with computer peripherals"</span>
            <span class="hljs-attr">height</span>=<span class="hljs-string">{480}</span>
            <span class="hljs-attr">width</span>=<span class="hljs-string">{320}</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"blur"</span>
            <span class="hljs-attr">blurDataURL</span>=<span class="hljs-string">{previewImageUrl}</span>
          /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> unsplashImageUrl =
    <span class="hljs-string">"https://images.unsplash.com/photo-1642083139428-9ee5fa423c46"</span>;
  <span class="hljs-keyword">const</span> image = <span class="hljs-keyword">await</span> fetch(unsplashImageUrl);
  <span class="hljs-keyword">const</span> imageBuffer = Buffer.from(<span class="hljs-keyword">await</span> image.arrayBuffer());
  <span class="hljs-keyword">const</span> previewImage = <span class="hljs-keyword">await</span> lqipModern(imageBuffer);

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">imageUrl</span>: unsplashImageUrl,
      <span class="hljs-attr">previewImageUrl</span>: previewImage.metadata.dataURIBase64,
    },
  };
};
</code></pre>
<p>In <code>getStaticProps</code>, we first fetch the image and convert it to a <a target="_blank" href="https://nodejs.org/api/buffer.html#buffer">Buffer</a>. We then give <code>lqip-modern</code> our buffer and it returns us an object called <code>previewImage</code> which contains a buffer and some metadata.  Inside the metadata, there is a field called <code>dataURIBase64</code> which is a base64 URL for our preview image. We pass this in via props to our client-side application.</p>
<p>On the client-side, we have added a new <code>placeholder="blur"</code> parameter to our <code>Image</code> component that will show a blur placeholder. As it is a remote image, we are required to pass in the <code>blurDataURL</code> parameter. We pass in the base64 URL for our preview image we obtained from the metadata earlier, here.</p>
<p>Now if you reload the page, while the image is loading, you should see the preview image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651483127819/2h9bDoMQI.gif" alt="Preview Image Demo.gif" /></p>
<p>For those wondering, this is the image <code>lqip-modern</code> made us - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651483336423/yy1ZQSwDk.png" alt="image.png" /></p>
<p>It is tiny at just 11x16 (the <code>next/image</code> component makes it fill the width and height of the original image) and is just 78 bytes!</p>
<h3 id="heading-using-jpeg-instead-of-webp">Using JPEG instead of WebP</h3>
<p>If you want to support all browsers, you can add the <code>outputFormat</code> option when making the preview image to get a JPEG preview image, like this - </p>
<pre><code class="lang-js">  <span class="hljs-keyword">const</span> previewImage = <span class="hljs-keyword">await</span> lqipModern(imageBuffer, { <span class="hljs-attr">outputFormat</span>: <span class="hljs-string">"jpeg"</span> });
</code></pre>
<p>The JPEG image is the same dimensions as our WebP image but significantly larger in size at 303 bytes - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651483539416/b63sHVUS7.png" alt="image.png" /></p>
<p>Note that these file sizes will vary depending on what image you use. The difference in file size between JPEG and WebP can go under double in some cases.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Alright, that is it! Let us go over what we did in this tutorial - </p>
<ul>
<li>Learned about LQIP images</li>
<li>Created a Next.js application and added an image from Unsplash</li>
<li>Used <code>lqip-modern</code> to create preview images</li>
<li>Looked at how we can obtain JPEG preview images</li>
</ul>
<p>Hope you liked this tutorial! Do share it if you have found it useful :)</p>
<h2 id="heading-important-links">Important Links</h2>
<ul>
<li><a target="_blank" href="https://www.npmjs.com/package/lqip-modern">LQIP Modern</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/next-lqip-demo">GitHub Repository with code</a></li>
<li><a target="_blank" href="https://next-lqip-demo.vercel.app/">Deployed example</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Making a blog with Directus, MDX, and Next.js On-Demand ISR]]></title><description><![CDATA[There are many Headless CMSs out there and many other tools that let us make a blog easily and quickly. Today we look at building a blog with Directus and Next.js. We will use MDX to store our blog content in Directus. We are also going to use Next.j...]]></description><link>https://blog.anishde.dev/making-a-blog-with-directus-mdx-and-nextjs-on-demand-isr</link><guid isPermaLink="true">https://blog.anishde.dev/making-a-blog-with-directus-mdx-and-nextjs-on-demand-isr</guid><category><![CDATA[THW Web Apps]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[headless cms]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Fri, 29 Apr 2022 15:57:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1651247682343/a8z6OZ-5D.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are many Headless CMSs out there and many other tools that let us make a blog easily and quickly. Today we look at building a blog with <a target="_blank" href="https://directus.io/">Directus</a> and Next.js. We will use <a target="_blank" href="https://mdxjs.com/">MDX</a> to store our blog content in Directus. We are also going to use <a target="_blank" href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation-beta">Next.js's on-demand incremental static regeneration feature</a> which lets us add or update content on our website without triggering a re-build.</p>
<p>Demo - </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/744f94ee7e74433a98134b3c0db8cac0">https://www.loom.com/share/744f94ee7e74433a98134b3c0db8cac0</a></div>
<p>Note that incremental static regeneration will be referred to as ISR from now on</p>
<h2 id="heading-why-on-demand-isr-over-regular-isr">Why On-Demand ISR over regular ISR?</h2>
<p>Next.js supports ISR for a few years now but on-demand ISR makes it better. Before, we would specify a <code>revalidate</code> property with the number of seconds to cache the data. This had a few disadvantages - </p>
<ul>
<li>The cache would be invalidated regularly even if the data in the server had not changed. This would also result in unnecessary API calls.</li>
<li>Often, the content served would be stale.</li>
</ul>
<p>On-demand ISR solves both the aforementioned problems with regular ISR. This is done by triggering a cache revalidation via webhooks. So whenever the content is updated on the server (or CMS), a webhook event can be fired which will create or update the required static pages</p>
<h2 id="heading-setting-up-a-directus-project">Setting up a Directus project</h2>
<p>Although Directus can be self-hosted, it has a cloud offering with a decent free tier. Go ahead and sign up for an account if you haven't already. Click "Create Project" in the dashboard (you might be prompted to create one during the onboarding process) and give your project a name. Under "Infrastructure", select "Community Cloud" and under "Starting Template", select "Empty Project". Now click "Create Project" and within 2 minutes, it should be created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651223269097/aJVKyy2gR.png" alt="image.png" /></p>
<p>After the project has been created, click on it on the dashboard and then click "Open Project". Here, on the login screen, enter the credentials that have been sent to the email address associated with your Directus account.</p>
<h3 id="heading-setting-up-our-blog-collection">Setting up our Blog collection</h3>
<p>On, Directus, it should say that there are no collections. Let us create a collection called "Blog". Click on the arrow in the top-right corner and check "Status", "Created on" and "Updated On". We will add more fields later on. Now click on the checkmark and we should be able to see our collection's schema - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651223280422/6jk5xyTOb.png" alt="image.png" /></p>
<p>Let us add a few more fields. Create one called with the type "Input" and give it the key "title". Make it required and hit save.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651223394462/vpz-8l4b1.png" alt="image.png" /></p>
<p>Add one more field with the type "Markdown" and give it the key "content". Make this required too and hit save.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651223472104/1BR8ZE5F8.png" alt="image.png" /></p>
<p>We need to also add a slug for our blog posts. Create a new field with the "Input" type with key "slug" and make it required. Now click "Continue in Advanced Field Creation Mode". Under "Interface" check the "Slugify" option and click the checkmark to create the field.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651225354586/VbUHIbeFu.png" alt="image.png" /></p>
<h3 id="heading-adding-some-sample-data">Adding some sample data</h3>
<p>Adding content in Directus is extremely easy! Go to the content tab in the sidebar (the first one with a 3d box icon) and select the Blog collection. Now click "Create Item". Here add a title and some content and a slug. Also, make sure to change the status to "Published". We will later use this status to manage API access. Note that you can use markdown here. After you are done adding the content, click the checkmark to save it.</p>
<h3 id="heading-setting-up-permissions-for-our-api">Setting up permissions for our API</h3>
<p>Go to the settings tab on the sidebar and then click on "Roles &amp; Permissions" in the settings navigation. Next, click on "Public" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651224303551/x1F4OS5JD.png" alt="image.png" /></p>
<p>Here we can see our Blog collection. Click on the red not allowed sign under the eye (this denotes view permission). In the dropdown, select "Use Custom".</p>
<p>Unser "Item Permissions", add the following rule allowing public view access to only published items - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651224523453/mYoGUFWzC.png" alt="image.png" /></p>
<p>Under "Field Permissions", select all and click on the checkmark to save the permissions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651225541073/dFphhgKhV.png" alt="image.png" /></p>
<h2 id="heading-creating-a-blog-with-nextjs-and-directus">Creating a blog with Next.js and Directus</h2>
<p>Let us start by creating a new Next.js app - </p>
<pre><code>npx <span class="hljs-keyword">create</span>-<span class="hljs-keyword">next</span>-app nextjs-directus-<span class="hljs-keyword">on</span>-<span class="hljs-keyword">demand</span>-isr
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">create</span> <span class="hljs-keyword">next</span>-app nextjs-directus-<span class="hljs-keyword">on</span>-<span class="hljs-keyword">demand</span>-isr
</code></pre><p>After it has been created, open the project in your favorite code editor.</p>
<p>Next, install the Directus JS SDK - </p>
<pre><code><span class="hljs-built_in">npm</span> install @directus/sdk
<span class="hljs-comment"># OR</span>
yarn add @directus/sdk
</code></pre><p>Now, make a file called <code>lib/directus.js</code> and add the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Directus } <span class="hljs-keyword">from</span> <span class="hljs-string">"@directus/sdk"</span>;

<span class="hljs-keyword">const</span> directus = <span class="hljs-keyword">new</span> Directus(process.env.DIRECTUS_URL);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> directus;
</code></pre>
<p>Here, we are initializing the SDK with the URL to our Directus backend. We must set this URL as an environment variable. Go ahead and create a new file called <code>.env.local</code> and add the environment variable - </p>
<pre><code class="lang-env">DIRECTUS_URL=&lt;PATH_TO_YOUR_DIRECTUS_INSTANCE_WITHOUT_ANY_OF_THE_PATHS&gt;
</code></pre>
<p>Make sure to appropriately replace the URL with the one for your instance.</p>
<p>Now, open the <code>pages/index.js</code> file and replace it with the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> directus <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/directus"</span>;

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/Home.module.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params">{ posts }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Create Next App<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Generated by create next app"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.main}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.title}</span>&gt;</span>
          Welcome to A demo of Next.js with Directus
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.grid}</span>&gt;</span>
          {posts.map((post) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{post.slug}</span> <span class="hljs-attr">passHref</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.card}</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          ))}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> directus.items(<span class="hljs-string">"blog"</span>).readByQuery({
    <span class="hljs-attr">limit</span>: <span class="hljs-number">-1</span>,
    <span class="hljs-attr">fields</span>: [<span class="hljs-string">"title"</span>, <span class="hljs-string">"slug"</span>, <span class="hljs-string">"id"</span>],
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">posts</span>: res.data,
    },
  };
};
</code></pre>
<p>Here, we are fetching all the blog posts but only the title, slug, and id fields. We do this in <code>getStaticProps</code> and pass the data as a prop to the client. Hence, the fetching is done on the server environment, only at build time. (note that in a development environment, <code>getStaticProps</code> runs on every request)</p>
<p>Later on in the article, we are going to be looking at on-demand ISR that will run <code>getStaticProps</code> when a webhook is triggered.</p>
<p>Once you run <code>yarn dev</code> and open up <a target="_blank">https://localhost:3000</a> on your web browser, you should be able to see your blog posts - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651230716520/vtByp3p3R.png" alt="image.png" /></p>
<p>Now, if we click on the blog post cards, it will lead us to a 404 as we haven't set up our blog post pages yet. Let us do that now.</p>
<p>Create a new file <code>pages/[slug].js</code> and add the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> directus <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/directus"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/BlogPost.module.css"</span>;

<span class="hljs-keyword">const</span> BlogPage = <span class="hljs-function">(<span class="hljs-params">{ post }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{post.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> ({ params }) =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> directus.items(<span class="hljs-string">"blog"</span>).readByQuery({
    <span class="hljs-attr">filter</span>: { <span class="hljs-attr">slug</span>: params.slug },
    <span class="hljs-attr">fields</span>: [<span class="hljs-string">"title"</span>, <span class="hljs-string">"content"</span>],
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">post</span>: res.data[<span class="hljs-number">0</span>],
    },
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticPaths = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> directus.items(<span class="hljs-string">"blog"</span>).readByQuery({
    <span class="hljs-attr">limit</span>: <span class="hljs-number">-1</span>,
    <span class="hljs-attr">fields</span>: [<span class="hljs-string">"slug"</span>],
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">paths</span>: res.data.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> ({
      <span class="hljs-attr">params</span>: {
        <span class="hljs-attr">slug</span>: post.slug,
      },
    })),
    <span class="hljs-attr">fallback</span>: <span class="hljs-literal">false</span>,
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BlogPage;
</code></pre>
<p>Here, in <code>getStaticPaths</code>, we are fetching only the slug for all blog posts. Then we are mapping over it and create an array of params to let Next.js know the paths that exist. Lastly, we set <code>fallback</code> to <code>false</code> so that any path not in the paths array redirects to a 404.</p>
<p>In <code>getStaticProps</code> we do a query where we filter by the <code>slug</code> field. We also specifically ask for only the <code>title</code> and <code>content</code> fields.</p>
<p>Lastly, we pass the data in through props and render it on the client-side.</p>
<p>Also, we can add some CSS for this page. Create a new file called <code>styles/BlogPost.module.css</code> and add the following to it - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">2rem</span>;
}

<span class="hljs-selector-class">.main</span> {
  <span class="hljs-attribute">min-height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">4rem</span> <span class="hljs-number">0</span>;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
}
</code></pre>
<p>Now if we see our blog post page, we see that the content is rendering as a string and is not getting parsed as markdown - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651232707540/m80wn9q-X.png" alt="image.png" /></p>
<p>Let's fix this</p>
<h3 id="heading-rendering-mdx-for-content">Rendering MDX for content</h3>
<p>Although MDX supports more advanced features like the ability to render React components, we are not going to be looking at that in this article. However, let us focus on parsing the markdown and rendering the necessary HTML.</p>
<p>We are going to be using <a target="_blank" href="https://github.com/hashicorp/next-mdx-remote"><code>next-mdx-remote</code></a> for this tutorial. Let us install it -</p>
<pre><code>npm <span class="hljs-keyword">install</span> <span class="hljs-keyword">next</span>-mdx-remote
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">add</span> <span class="hljs-keyword">next</span>-mdx-remote
</code></pre><p>Now, replace the code in <code>pages/[slug].js</code> with the following - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote/serialize"</span>;
<span class="hljs-keyword">import</span> { MDXRemote } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote"</span>;

<span class="hljs-keyword">import</span> directus <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/directus"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"../styles/BlogPost.module.css"</span>;

<span class="hljs-keyword">const</span> BlogPage = <span class="hljs-function">(<span class="hljs-params">{ post }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">MDXRemote</span> {<span class="hljs-attr">...post.content</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> ({ params }) =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> directus.items(<span class="hljs-string">"blog"</span>).readByQuery({
    <span class="hljs-attr">filter</span>: { <span class="hljs-attr">slug</span>: params.slug },
    <span class="hljs-attr">fields</span>: [<span class="hljs-string">"title"</span>, <span class="hljs-string">"content"</span>],
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">post</span>: {
        <span class="hljs-attr">title</span>: res.data[<span class="hljs-number">0</span>].title,
        <span class="hljs-attr">content</span>: <span class="hljs-keyword">await</span> serialize(res.data[<span class="hljs-number">0</span>].content),
      },
    },
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticPaths = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> directus.items(<span class="hljs-string">"blog"</span>).readByQuery({
    <span class="hljs-attr">limit</span>: <span class="hljs-number">-1</span>,
    <span class="hljs-attr">fields</span>: [<span class="hljs-string">"slug"</span>],
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">paths</span>: res.data.map(<span class="hljs-function">(<span class="hljs-params">post</span>) =&gt;</span> ({
      <span class="hljs-attr">params</span>: {
        <span class="hljs-attr">slug</span>: post.slug,
      },
    })),
    <span class="hljs-attr">fallback</span>: <span class="hljs-literal">false</span>,
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BlogPage;
</code></pre>
<p>Now, if we navigate to a blog post, we can see the rendered markdown - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651233312751/MhH0ZHt25.png" alt="image.png" /></p>
<h2 id="heading-using-nextjs-on-demand-isr-with-directus">Using Next.js On-demand ISR with Directus</h2>
<p>If we build the Next.js application and serve a production build and then make changes in Directus, they will not reflect in our website unless we re-build it. Here we got 3 options - </p>
<ul>
<li>Trigger a new build every time content is added/updated/deleted</li>
<li>Use regular ISR with a specified time for which the cache should be retained</li>
<li>Use on-demand ISR to only re-build that specific page only when it is needed</li>
</ul>
<p>With the first option, it can be expensive and time-consuming. Re-building also invalidates the cache for all pages in many cases. This might cause issues when you are dealing with larger websites.</p>
<p>With the second option, there will be unnecessary API calls to our server and data might be stale.</p>
<p>This leaves us with the third option. Although setting it up is slightly more complicated than the other 2 methods, it does not have any of the downsides the other 2 methods have. And, I am going to show you how exactly to set it up so it shouldn't be very hard :)</p>
<p><strong>Note:</strong> Currently the website is hosted locally but Directus needs a publicly accessible URL as a webhook. You can either host it somewhere and then test it out or start a local tunnel using something like <a target="_blank" href="https://ngrok.com/">Ngrok</a></p>
<p>Create a new file under <code>pages/api/revalidate.js</code> and add the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> directus <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/directus"</span>;

<span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { collection } = req.body;
  <span class="hljs-keyword">const</span> headers = req.headers;

  <span class="hljs-keyword">if</span> (!headers[<span class="hljs-string">"x-webhook-secret"</span>]) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).send(<span class="hljs-string">"Forbidden"</span>);
  }

  <span class="hljs-keyword">const</span> receivedSecret = headers[<span class="hljs-string">"x-webhook-secret"</span>];

  <span class="hljs-keyword">const</span> secret = process.env.REVALIDATE_SECRET;

  <span class="hljs-keyword">if</span> (receivedSecret !== secret) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).send(<span class="hljs-string">"Forbidden"</span>);
  }

  <span class="hljs-keyword">if</span> (collection === <span class="hljs-string">"blog"</span>) {
    <span class="hljs-keyword">const</span> { keys } = req.body;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> key <span class="hljs-keyword">of</span> keys) {
      <span class="hljs-keyword">const</span> directusRes = <span class="hljs-keyword">await</span> directus
        .items(collection)
        .readOne(key, { <span class="hljs-attr">fields</span>: [<span class="hljs-string">"slug"</span>] });

      <span class="hljs-keyword">await</span> res.unstable_revalidate(<span class="hljs-string">`/<span class="hljs-subst">${directusRes.slug}</span>`</span>);
      <span class="hljs-keyword">await</span> res.unstable_revalidate(<span class="hljs-string">"/"</span>);
    }
  }

  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">"Success"</span>);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>This is a simple Next.js API route.</p>
<p>First, we check for the <code>x-webhook-secret</code> header and compare it to a preset webhook secret, set as an environment variable. We should always use a webhook secret to prevent spam. It can also be a security risk in some cases (but here it is not as we are not relying on data sent as an event payload for input).</p>
<p>Directus sends us some event payload and we are destructuring the collection field which contains the name of the collection where the change was made. We check if this is the blog collection and then go ahead with revalidating the pages. Although this does not make much sense here, if our pages had multiple sets of pages that could be revalidated, we could specifically just revalidate a set of pages instead of all sets of pages. (for example, if there was a landing and then a blog page, for changes to the landing collection, we could just revalidate the landing page but for changes to the blog page, we could revalidate only the specific blog page and the blog index).</p>
<p>Now, the event payload does not contain the slug for the blog post but it has the id. We use the Directus SDK to get the slug corresponding to that id and then revalidate that slug page and the home page (as it has the blog index).</p>
<p>Lastly, open up the <code>.env.local</code> file and add the <code>REVALIDATE_SECRET</code> environment variable. For the value, it can be a random string. The easiest way would be to use the output of the following command - </p>
<pre><code><span class="hljs-attribute">openssl</span> rand -base<span class="hljs-number">64</span> <span class="hljs-number">32</span>
</code></pre><p>To test this out, we cannot use the development environment as <code>getStaticProps</code> runs every time a request is made on the development environment. Either build the site with <code>npm run build</code> or <code>yarn build</code>, serve it locally with <code>npm run start</code> or <code>yarn start</code>, and then use a local tunneling solution like Ngrok or else, deploy it to a hosting platform like <a target="_blank" href="https://vercel.com/">Vercel</a>.</p>
<p>Now head over to your Directus instance and go to the settings tab. Now click on "Webhooks" in the side navigation and create a new one. Give it a name and for the URL field, add your Ngrok URL or your hosted instance. Make sure that the slug is <code>/api/revalidate</code>. The URL should look like <code>https://&lt;my-domain&gt;/api/revalidate</code>. Make sure the status is set to active and the "Send Event Data" checkbox is checked.</p>
<p>Now, add a header called <code>x-webhook-secret</code> with the value of the secret you created earlier and set it as an environment variable. Under "Triggers", check all the actions and the blog collection. Now click the checkmark to save it. Here is what it looks like for me - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651238918101/cLQGh0Por.png" alt="image.png" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That has been quite a lot! Let us go over the things we did - </p>
<ul>
<li>First, we create a project in Directus and created a schema for our blog posts</li>
<li>We created a Next.js application and added the Directus SDK</li>
<li>We displayed our blog posts on the home page and the post with content on its own page</li>
<li>We used <code>next-mdx-remote</code> to render markdown</li>
<li>We used Next.js on-demand ISR to revalidate the cache whenever required</li>
</ul>
<p>Hope you liked this tutorial! Do share it if you have found it useful and you can follow me on <a target="_blank" href="https://twitter.com/">Twitter</a> too :)</p>
<p>See you on my next blog 🤞</p>
<h2 id="heading-important-links">Important Links</h2>
<ul>
<li><a target="_blank" href="https://github.com/AnishDe12020/nextjs-directus-on-demand-isr">GitHub Repo with all the code</a></li>
<li><a target="_blank" href="https://nextjs-directus-on-demand-isr.vercel.app/">Hosted Demo</a></li>
<li><a target="_blank" href="https://directus.io/">Directus</a></li>
<li><a target="_blank" href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation-beta">Next.js on-demand ISR</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introducing XdoX - Start Challenges, Log your Progress and Show them off to the World]]></title><description><![CDATA[🤔 What is XdoX?
XdoX is a web application that lets you start challenges and log your progress every day. You are also able to show your progress to the world via your unique profile page. These challenges can be anything from 100DaysOfCode to 30Day...]]></description><link>https://blog.anishde.dev/introducing-xdox-start-challenges-log-your-progress-and-show-them-off-to-the-world</link><guid isPermaLink="true">https://blog.anishde.dev/introducing-xdox-start-challenges-log-your-progress-and-show-them-off-to-the-world</guid><category><![CDATA[Hasura Hackathon]]></category><category><![CDATA[Hasura]]></category><category><![CDATA[hackathon]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[Next.js]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Thu, 31 Mar 2022 15:24:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1648739175407/TYb9x3jFw.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-xdox">🤔 What is XdoX?</h2>
<p>XdoX is a web application that lets you start challenges and log your progress every day. You are also able to show your progress to the world via your unique profile page. These challenges can be anything from 100DaysOfCode to 30DaysOfRust to even 60DaysOfCooking!</p>
<p>It is also my submission for the <a target="_blank" href="https://townhall.hashnode.com/hasura-hackathon">Hasura x Hashnode Hackathon</a></p>
<p><a target="_blank" href="https://xdox.me/">Live Demo</a> / <a target="_blank" href="https://github.com/AnishDe12020/xdox">GitHub Repository</a></p>
<h2 id="heading-what-is-hasura">❓ What is Hasura?</h2>
<p>GraphQL is a query language for APIs with a schema. It comes with multiple features like the ability to query specific fields, do pagination, do aggregation queries, and a lot more.</p>
<p>However, making a GraphQL backend is more complicated than making a simple REST backend and that is where Hasura comes in. <a target="_blank" href="https://hasura.io/">Hasura</a> provides us with an easy way to make a GraphQL backend that connects our database with our application without the need to write a single line of code!</p>
<p>Hasura also has a cloud offering with a decent free tier so that we can get started with hosting our GaphQL backend without needing to worry about costs. It is Open Source and Self-Hostable as well.</p>
<h2 id="heading-the-tech-stack">📚 The Tech Stack</h2>
<p>What all technologies did I use for XdoX?</p>
<p>For starters, I used <a target="_blank" href="https://hasura.io/">Hasura</a> for my application's backend.</p>
<p>Other than that, I used the following services - </p>
<ul>
<li><p><a target="_blank" href="https://clerk.dev/">Clerk</a> to add authentication to my application. It also integrated well with Hasura and I was able to secure my backend by using JWT Auth (more on this later on in this article)</p>
</li>
<li><p><a target="_blank" href="https://www.heroku.com/postgres">Heroku Postgres</a> for my database. It also integrated well with Hasura</p>
</li>
<li><p><a target="_blank" href="https://vercel.com/">Vercel</a> to host my frontend</p>
</li>
</ul>
<p>And, here are the libraries and frameworks I used for the application - </p>
<ul>
<li><a target="_blank" href="https://nextjs.org/">Next.js</a> for my application's frontend</li>
<li><a target="_blank" href="https://tailwindcss.com/">TailwindCSS</a> for styling my frontend</li>
<li><a target="_blank" href="https://www.radix-ui.com/">Radix UI</a> for un-styled UI components like Modals and Popovers</li>
<li><a target="_blank" href="https://headlessui.dev/">Headless UI</a> for transitions</li>
<li><a target="_blank" href="https://www.apollographql.com/docs/react/">Apollo React Client</a> for making GraphQL requests from my frontend. It also takes care of caching.</li>
<li><a target="_blank" href="https://tiptap.dev/">Tiptap</a> for the rich-text editor with markdown support that is used to log progress</li>
</ul>
<h2 id="heading-how-does-xdox-work">🧐 How does XdoX work?</h2>
<p>It is a simple 3-step process. One signs up for an account using Google or Email and then starts a challenge (e.g. 100DaysOfCode). Then one logs their progress every day.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648735974435/jGQ7wv8a0.png" alt="image.png" /></p>
<p>Next, one can share their unique profile page to the world to show off their progress.</p>
<p>Also, it is not necessary to log your progress every day. The app is built in such a way that lets you be flexible with your challenges. Gone on a vacation? No problem, XdoX won't bug out for not logging your progress.</p>
<h3 id="heading-securing-the-backend">Securing the backend</h3>
<p>Backends have direct access to the database and it is considered a best practice to secure them. I need to use the GraphQL API from my front-end and hence it has to be a public API. However, I must secure it so that only limited unauthorized requests and authorized requests can be made.</p>
<p>As I was using Clerk for user authentication, it didn't take me long to implement this. Clerk integrated with Hasura using JWT templates. Here is the <a target="_blank" href="https://docs.clerk.dev/integrations/hasura">documentation explaining how to implement this</a>.</p>
<p>Here, we create a JWT template from the Clerk dashboard. Here is what mine looks like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648723772908/ke9jezE3i.png" alt="image.png" /></p>
<p>When making a request to the API, we pass in a header called <code>Authorization</code> with a bearer token as the value. This is verified by Hasura using a signing key (this is set in Hasura via environment variables). </p>
<p>This is the code in the frontend that takes care of passing in the bearer token when making requests - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> {
  ApolloClient,
  ApolloProvider,
  <span class="hljs-keyword">from</span>,
  HttpLink,
  InMemoryCache,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@apollo/client"</span>;
<span class="hljs-keyword">import</span> { setContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"@apollo/client/link/context"</span>;
<span class="hljs-keyword">import</span> { useSession } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/clerk-react"</span>;
<span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> hasuraGraphqlApi = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_API;

<span class="hljs-keyword">interface</span> IApolloProviderWrapperProps {
  children: ReactNode;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ApolloProviderWrapper = <span class="hljs-function">(<span class="hljs-params">{
  children,
}: IApolloProviderWrapperProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { getToken } = useSession();
  <span class="hljs-keyword">const</span> authMiddleware = setContext(<span class="hljs-keyword">async</span> (_, { headers }) =&gt; {
    <span class="hljs-keyword">const</span> token = <span class="hljs-keyword">await</span> getToken({ template: <span class="hljs-string">"hasura"</span> });

    <span class="hljs-keyword">return</span> {
      headers: {
        ...headers,
        authorization: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>,
      },
    };
  });

  <span class="hljs-keyword">const</span> httpLink = <span class="hljs-keyword">new</span> HttpLink({
    uri: hasuraGraphqlApi,
  });

  <span class="hljs-keyword">const</span> apolloClient = <span class="hljs-keyword">new</span> ApolloClient({
    link: <span class="hljs-keyword">from</span>([authMiddleware, httpLink]),
    cache: <span class="hljs-keyword">new</span> InMemoryCache(),
  });

  <span class="hljs-keyword">return</span> &lt;ApolloProvider client={apolloClient}&gt;{children}&lt;/ApolloProvider&gt;;
};
</code></pre>
<p>We simply get a bearer token by using the <code>getToken</code> function made available to us by the Clerk React SDK and pass it in the <code>Authorization</code> header.</p>
<p>Now, if the bearer token is valid, the <code>X-Hasura-User-Id</code> header is added to the request that contains the user id of the user who is making the request. The headers for the <code>user</code> role are passed in as well. Note that this is taken care of on Hasura's side. </p>
<p>I am also making some unauthenticated requests with a <code>viewer</code> role. This has been set as the unauthorized role in my Hasura instance and is used in the public user profile pages. Here is the code that take cares of making an unauthenticated requests - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> {
  ApolloClient,
  ApolloProvider,
  <span class="hljs-keyword">from</span>,
  HttpLink,
  InMemoryCache,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@apollo/client"</span>;
<span class="hljs-keyword">import</span> { setContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"@apollo/client/link/context"</span>;
<span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> hasuraGraphqlApi = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_API;

<span class="hljs-keyword">interface</span> IApolloProviderWrapperProps {
  children: ReactNode;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UnauthenticatedApolloProviderWrapper = <span class="hljs-function">(<span class="hljs-params">{
  children,
}: IApolloProviderWrapperProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> authMiddleware = setContext(<span class="hljs-keyword">async</span> (_, { headers }) =&gt; {
    <span class="hljs-keyword">return</span> {
      headers: {
        ...headers,
        <span class="hljs-string">"X-Hasura-User-Role"</span>: <span class="hljs-string">"viewer"</span>,
      },
    };
  });

  <span class="hljs-keyword">const</span> httpLink = <span class="hljs-keyword">new</span> HttpLink({
    uri: hasuraGraphqlApi,
  });

  <span class="hljs-keyword">const</span> apolloClient = <span class="hljs-keyword">new</span> ApolloClient({
    link: <span class="hljs-keyword">from</span>([authMiddleware, httpLink]),
    cache: <span class="hljs-keyword">new</span> InMemoryCache(),
  });

  <span class="hljs-keyword">return</span> &lt;ApolloProvider client={apolloClient}&gt;{children}&lt;/ApolloProvider&gt;;
};
</code></pre>
<h3 id="heading-setting-row-level-permissions-for-the-data">Setting row-level permissions for the data</h3>
<p>Although the API is now secured, no data is accessible by default. We need to set up permissions and this will also let us limit the data one can access. For example, we will let a user only access their own user data and only access private challenges they have created.</p>
<p>Thankfully, Hasura again makes doing this extremely easy. Let us look at an example - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648727072397/ZET1GtAf8.png" alt="image.png" /></p>
<p>Here, I have setup insert permissions for the <code>user</code> role in such a way that one can insert rows only where the <code>user_id</code> column is equal to the user id of the user making the request (this was passed in as a header).</p>
<p>I am also allowing the <code>user</code> to only update specific columns. Here, the <code>id</code> column is auto-generated with the <code>gen_random_uuid()</code> PostgreSQL function. The <code>created_at</code> and <code>updated_at</code> fields are also being taken care of by the backend.</p>
<p>I am also adding a column preset for the <code>user_id</code> column that will be equal to the <code>X-Hasura-User-Id</code> header. Now, that is crazy powerful!</p>
<p>Similarly, I have set permissions for update, select and delete for the <code>user</code> role where I check that the <code>user_id</code> column matches the <code>X-Hasura-User-Id</code> header.</p>
<p>For the <code>viewer</code> role, I have set it up this way - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648733581527/V6oGYUvmP.png" alt="image.png" /></p>
<p>Here, the viewer is only able to select rows from the database (that is, only read data). I have additionally added a check that makes sure that the challenge is public.</p>
<h2 id="heading-what-i-learned-from-this-hackathon">👓 What I learned from this Hackathon</h2>
<p>Although I have used GraphQL in the past, my experience was quite limited. Also, I had never built a GraphQL backend, I was just using public GraphQL APIs. I had also never used Hasura before, nor did I ever use a SQL database for any production project.</p>
<p>This hackathon gave me a chance to explore the backend side of GraphQL through Hasura and understand the deeper concepts. I also had a great time using a PostgreSQL database, learning more about relational data. It is crazy powerful!</p>
<h2 id="heading-conclusion">✨ Conclusion</h2>
<p>Over the past month, I have worked on XdoX and have been exploring and learning a LOT of new things. I'm quite excited to see how XdoX does in the real world!</p>
<p>Bye, and have a nice day 😁🤞</p>
<h2 id="heading-important-links">🔗 Important Links</h2>
<ul>
<li><a target="_blank" href="https://www.xdox.me/">XdoX</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/xdox">XdoX GitHub Repository</a></li>
<li><a target="_blank" href="https://www.xdox.me/@anishde12020">My profile on XdoX</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[5 Chrome Keyboard Shortcuts to boost your productivity]]></title><description><![CDATA[Browsers have become an integral part of our life and most applications are web-based now. We use web browsers for everything, from asking a question on Stackoverflow to booking a flight ticket. We often end up having a huge number of tabs open and i...]]></description><link>https://blog.anishde.dev/5-chrome-keyboard-shortcuts-to-boost-your-productivity</link><guid isPermaLink="true">https://blog.anishde.dev/5-chrome-keyboard-shortcuts-to-boost-your-productivity</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Google Chrome]]></category><category><![CDATA[Browsers]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Wed, 23 Mar 2022 08:39:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1648010384861/eZTBzzKqf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Browsers have become an integral part of our life and most applications are web-based now. We use web browsers for everything, from asking a question on Stackoverflow to booking a flight ticket. We often end up having a huge number of tabs open and it becomes quite hard to keep a track of them. It gets especially confusing when we try to switch between tabs. Let us look at 5 keyboard shortcuts that can boost our productivity -</p>
<p>Note: Although I am going to be talking about keyboard shortcuts specific to Chrome, they should remain the same across all Chromium-based browsers (Brave, Edge, Vivaldi, etc.)</p>
<h2 id="heading-moving-between-tabs">Moving between tabs</h2>
<p>Clicking tabs can be quite a painful and slow process, especially, if you switch between them frequently. Keyboard shortcuts greatly speed things up here.</p>
<p><kbd>Ctrl</kbd> + <kbd>Tab</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>⌥</kbd> + <kbd>→</kbd> (for macOS) lets you move to the next tab.</p>
<p>Edit: <kbd>Ctrl</kbd> + <kbd>Tab</kbd> works on macOS too, kudos to <a class="user-mention" href="https://hashnode.com/@carrotfarmer">Dhruva Srinivas</a> for pointing it out</p>
<p>Bonus:
We can also move to the previous tab with <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>⌥</kbd> + <kbd>←</kbd> (for macOS)</p>
<h2 id="heading-re-opening-closed-tabs">Re-opening closed tabs</h2>
<p>We tend to close tabs by mistake but there is a simple keyboard shortcut that can re-open the last closed tab - </p>
<p><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>t</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>t</kbd> (for macOS) lets you re-open recently closed tabs in the order they were closed in.</p>
<p>Bonus: If you close a whole window, the above keyboard shortcut will re-open the whole window with all the tabs.</p>
<h2 id="heading-searching-through-open-tabs">Searching through open tabs</h2>
<p>As the number of tabs open gets larger and larger, it gets difficult to find a tab. Fortunately, from Chrome 87 onwards, we can search through all open tabs, as well as recently closed tabs.</p>
<p><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>a</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>a</kbd> (for macOS) opens the tab search dialog.</p>
<h2 id="heading-close-the-current-tab">Close the current tab</h2>
<p>After we are done with using a web page on a tab, it is a good idea to close it so that it doesn't clutter our tab bar and keeps the resource usage low.</p>
<p><kbd>Ctrl</kbd> + <kbd>w</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>w</kbd> (for macOS) closes the current tab.</p>
<p>Bonus: <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>w</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>w</kbd> (for macOS) closes the current window.</p>
<h2 id="heading-open-a-new-tab-or-a-window">Open a new tab or a window</h2>
<p><kbd>Ctrl</kbd> + <kbd>t</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>t</kbd> (for macOS) opens a new tab and jumps to it.</p>
<p>Similarly, <kbd>Ctrl</kbd> + <kbd>n</kbd> (for Windows) and <kbd>⌘</kbd> + <kbd>n</kbd> (for macOS) opens a new window and jumps to it.</p>
<p>All Chrome Keyboard Shortcuts - <a target="_blank" href="https://support.google.com/chrome/answer/157179?hl=en&amp;co=GENIE.Platform%3DDesktop">https://support.google.com/chrome/answer/157179?hl=en&amp;co=GENIE.Platform%3DDesktop</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you have found this small article helpful and now you can be more productive while browsing the web. See you in the next one 🤞</p>
]]></content:encoded></item><item><title><![CDATA[Powerful Code Blocks with Code Hike and MDX]]></title><description><![CDATA[MDX is a format that combines markup in markdown along with JSX code to embed components into markdown documents. It is used in documentation, blog posts, and much more as one can add interactive examples with JSX. You can learn more about it here.
T...]]></description><link>https://blog.anishde.dev/powerful-code-blocks-with-code-hike-and-mdx</link><guid isPermaLink="true">https://blog.anishde.dev/powerful-code-blocks-with-code-hike-and-mdx</guid><category><![CDATA[Next.js]]></category><category><![CDATA[markdown]]></category><category><![CDATA[documentation]]></category><category><![CDATA[tutorials]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sun, 06 Mar 2022 09:22:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646557910712/Kd9HITQXq.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://mdxjs.com/">MDX</a> is a format that combines markup in markdown along with JSX code to embed components into markdown documents. It is used in documentation, blog posts, and much more as one can add interactive examples with JSX. You can learn more about it <a target="_blank" href="https://mdxjs.com/docs/what-is-mdx/">here</a>.</p>
<p>Today, we are going to look at a library called <a target="_blank" href="https://codehike.org/">Code Hike</a> that provides exceptional features on markdown code blocks. These includes <a target="_blank" href="https://codehike.org/demo/code">focussing code</a>, <a target="_blank" href="https://codehike.org/demo/filenames">adding filenames and displaying them as tabs</a>, <a target="_blank" href="https://codehike.org/demo/meta-annotations">annotations</a>, <a target="_blank" href="https://codehike.org/demo/sections">linking text to code</a>, and much more! It also has in-built support for syntax highlighting 🤩</p>
<p>Let us look at adding Code Hike to a Next.js application. Do note that although MDX is supported by a number of frameworks like Vue, Svelte, etc, Code Hike only works with React.</p>
<p><a target="_blank" href="https://code-hike-example.vercel.app/">Live Demo</a> / <a target="_blank" href="https://github.com/AnishDe12020/code-hike-example">GitHub Repository</a></p>
<h2 id="heading-setting-up-code-hike-in-a-nextjs-application">Setting up Code Hike in a Next.js application</h2>
<p>First of all, let us create a new Next.js application - </p>
<pre><code>npx <span class="hljs-keyword">create</span>-<span class="hljs-keyword">next</span>-app code-hike-example
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">create</span> <span class="hljs-keyword">next</span>-app code-hike-example
</code></pre><p>Now, open up the project in your favorite text editor.</p>
<h3 id="heading-setting-up-mdx">Setting up MDX</h3>
<p>Next, we need to add MDX to our Next.js application. For this, we are going to be following the <a target="_blank" href="https://nextjs.org/docs/advanced-features/using-mdx">official guide on adding MDX to a Next.js application</a>.</p>
<p>Do note that Code Hike also works with <a target="_blank" href="https://github.com/hashicorp/next-mdx-remote">Next MDX Remote</a> and <a target="_blank" href="https://github.com/kentcdodds/mdx-bundler">MDX Bundler</a> however, we are going to look at a simple example with the <a target="_blank" href="https://www.npmjs.com/package/@next/mdx">official MDX plugin for Next.js</a>.</p>
<p>First of all, let us install the required packages - </p>
<pre><code><span class="hljs-built_in">npm</span> install @next/mdx @mdx-js/loader
<span class="hljs-comment"># OR</span>
yarn add @next/mdx @mdx-js/loader
</code></pre><p>Now, open up <code>next.config.js</code> and replace it with the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> withMDX = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@next/mdx"</span>)({
  <span class="hljs-attr">extension</span>: <span class="hljs-regexp">/\.mdx?$/</span>,
  options: {
    <span class="hljs-attr">remarkPlugins</span>: [],
    <span class="hljs-attr">rehypePlugins</span>: [],
  },
});

<span class="hljs-built_in">module</span>.exports = withMDX({
  <span class="hljs-attr">pageExtensions</span>: [<span class="hljs-string">"ts"</span>, <span class="hljs-string">"tsx"</span>, <span class="hljs-string">"js"</span>, <span class="hljs-string">"jsx"</span>, <span class="hljs-string">"md"</span>, <span class="hljs-string">"mdx"</span>],
})
</code></pre>
<p>We are simply telling our bundler to treat <code>.md</code> and <code>.mdx</code> files as pages as well when they are in the <code>pages</code> directory. This also takes care of compiling our MDX.</p>
<p>Now, let us setup Code Hike</p>
<h3 id="heading-setting-up-code-hike">Setting up Code Hike</h3>
<p>First of all, let us install the <a target="_blank" href="https://www.npmjs.com/package/@code-hike/mdx">Code Hike Package</a></p>
<pre><code><span class="hljs-built_in">npm</span> install @code-hike/mdx@next
<span class="hljs-comment"># OR</span>
yarn add @code-hike/mdx@next
</code></pre><p>Now, we must add Code Hike as a <a target="_blank" href="https://remark.js.org/">Remark</a> plugin. Remark is a simple markdown processor that has a huge ecosystem of plugins.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { remarkCodeHike } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@code-hike/mdx"</span>);

<span class="hljs-keyword">const</span> withMDX = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@next/mdx"</span>)({
  <span class="hljs-attr">extension</span>: <span class="hljs-regexp">/\.mdx?$/</span>,
  options: {
    <span class="hljs-attr">remarkPlugins</span>: [[remarkCodeHike]],
    <span class="hljs-attr">rehypePlugins</span>: [],
  },
});

<span class="hljs-built_in">module</span>.exports = withMDX({
  <span class="hljs-attr">pageExtensions</span>: [<span class="hljs-string">"ts"</span>, <span class="hljs-string">"tsx"</span>, <span class="hljs-string">"js"</span>, <span class="hljs-string">"jsx"</span>, <span class="hljs-string">"md"</span>, <span class="hljs-string">"mdx"</span>],
});
</code></pre>
<p>Code Hike uses <a target="_blank" href="https://github.com/shikijs/shiki">Shiki</a> under the hood to provide syntax highlighting. We can find a list of all the supported themes <a target="_blank" href="https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes">here</a>. Let us go with Monokai for this tutorial.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { remarkCodeHike } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@code-hike/mdx"</span>);
<span class="hljs-keyword">const</span> theme = <span class="hljs-built_in">require</span>(<span class="hljs-string">"shiki/themes/monokai.json"</span>);

<span class="hljs-keyword">const</span> withMDX = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@next/mdx"</span>)({
  <span class="hljs-attr">extension</span>: <span class="hljs-regexp">/\.mdx?$/</span>,
  options: {
    <span class="hljs-attr">remarkPlugins</span>: [[remarkCodeHike, { theme }]],
    <span class="hljs-attr">rehypePlugins</span>: [],
  },
});

<span class="hljs-built_in">module</span>.exports = withMDX({
  <span class="hljs-attr">pageExtensions</span>: [<span class="hljs-string">"ts"</span>, <span class="hljs-string">"tsx"</span>, <span class="hljs-string">"js"</span>, <span class="hljs-string">"jsx"</span>, <span class="hljs-string">"md"</span>, <span class="hljs-string">"mdx"</span>],
});
</code></pre>
<p>There is one last thing to do. We need to add the Code Hike CSS to our application. Open up <code>_app.js</code> and add this one line of code to the top</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">"@code-hike/mdx/dist/index.css"</span>
</code></pre>
<h2 id="heading-testing-out-code-hike">Testing out Code Hike</h2>
<p>Let us make a new file called <code>code-hike.mdx</code> under the <code>pages</code> directory. Add the following mdx markup in there - </p>
<pre><code class="lang-md"><span class="hljs-section"># Just testing out [<span class="hljs-string">Code Hike</span>](<span class="hljs-link">https://codehike.org/</span>)</span>

Some normal <span class="hljs-code">`markdown`</span>

<span class="hljs-section">## Annotation Example</span>
<span class="hljs-code">```js index.js box=1[25:39]
console.log("Some code. I am annotated!")
```</span>

<span class="hljs-section">## Focus Example</span>
<span class="hljs-code">```js next.config.js focus=1:2,7
const { remarkCodeHike } = require("@code-hike/mdx");
const theme = require("shiki/themes/monokai.json");

const withMDX = require("@next/mdx")({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [[remarkCodeHike, { theme }]],
    rehypePlugins: [],
  },
});

module.exports = withMDX({
  pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
});
```</span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CH.Section</span>&gt;</span></span>

<span class="hljs-section">## Code Links Example</span>

We are looking at the [<span class="hljs-string">console.log</span>](<span class="hljs-link">focus://console.js#2</span>) function today

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CH.Code</span>&gt;</span></span>
<span class="hljs-code">```js console.js
console.table(["Hello", "World"])
console.log("Hello World")
```</span>
<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">CH.Code</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">CH.Section</span>&gt;</span></span>
</code></pre>
<p>Here, we are writing some basic markdown at first and then adding 3 code blocks. In all 3 of them, I have provided a filename just after specifying the language (<code>js</code> in these 3 cases).</p>
<p>In the first code block, we pass in the <code>box</code> attribute after the filename. We set it to <code>1[25:39]</code> where <code>1</code> indicated the line number and <code>25:39</code> indicates the range of characters to annotate on that line.</p>
<p>Similarly, in the second code block, we pass in the <code>focus</code> attribute and set it to <code>1:2,7</code>. Here <code>1:2</code> indicates a range of lines to be put on focus. We also add line 7 by adding it as a comma-separated value.</p>
<p>For the third code block, we have to wrap the content and code block in the <code>CH.Section</code> tag. We must also wrap the code block in the <code>CH.Code</code> tag. This is so that Code Hike knows which file we are going to be referring to through the code links when we specify the filename.</p>
<p>We have <code>console.log</code> as a code link here that points to <code>focus://console.js#2</code>. This indicates that whenever we hover over that code link, it will focus the second line in the  <code>console.js</code> code block.</p>
<p>At last, this is what it should look like when we navigate to <a target="_blank" href="http://localhost:3000/code-hike"><code>/code-hike</code></a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646557043098/Zgwdl-v55.png" alt="image.png" /></p>
<p>Yes, those box shadows are cool 👀</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That was a brief overview of Code Hike. Code Hike supports many more things like <a target="_blank" href="https://codehike.org/demo/scrollycoding">Scrollycoding</a> and previews as well. Code Hike is still in a beta stage and many features are still experimental.</p>
<p>Hope everything went well so far and now you can use Code Hike in your blog or documentation to achieve extremely powerful code blocks!</p>
<p>See you in the next one 😁🤞</p>
<h2 id="heading-important-links">🔗 Important Links</h2>
<ul>
<li><a target="_blank" href="https://codehike.org/">Code Hike</a></li>
<li><a target="_blank" href="https://github.com/code-hike/codehike">Code Hike GitHub Repository</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/code-hike-example">GitHub Repository for this tutorial</a></li>
<li><a target="_blank" href="https://code-hike-example.vercel.app/">Deployed Preview for this tutorial</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Making an accessible Breadcrumb Navigation using TailwindCSS and NextJS]]></title><description><![CDATA[Breadcrumb Navigations are starting to appear in many applications nowadays, mostly in dashboards. Today we are going to look at building a Breadcrumb navigation that is accessible and styled with TailwindCSS. We are going to be using NextJS (specifi...]]></description><link>https://blog.anishde.dev/making-an-accessible-breadcrumb-navigation-using-tailwindcss-and-nextjs</link><guid isPermaLink="true">https://blog.anishde.dev/making-an-accessible-breadcrumb-navigation-using-tailwindcss-and-nextjs</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Accessibility]]></category><category><![CDATA[navigation]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Wed, 02 Mar 2022 12:02:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646221079849/m2I61XWGT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Breadcrumb Navigations are starting to appear in many applications nowadays, mostly in dashboards. Today we are going to look at building a Breadcrumb navigation that is accessible and styled with TailwindCSS. We are going to be using NextJS (specifically the <a target="_blank" href="https://nextjs.org/docs/api-reference/next/router">NextJS Router</a>) for this tutorial. However, you can follow the same steps for other frameworks, it may also not be a React framework. Just make sure to look up and implement the proper router logic and create the components properly, styling will remain the same.</p>
<h2 id="heading-ok-what-is-a-breadcrumb-navigation">🍞 Ok, what is a Breadcrumb Navigation?</h2>
<p>You must have come across navigation bars, something like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646199179447/9_Fiu4HCV.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646199205374/YsJUaSi8p.png" alt="image.png" /></p>
<p>The first example is from the <a target="_blank" href="https://vercel.com/">Vercel</a> dashboard and the second one is from the <a target="_blank" href="https://www.netlify.com/">Netlify</a> dashboard.</p>
<p>We can see that it gives us a brief idea of what page we are on and also lets us navigate back easily (yes, those breadcrumb items are usually links).</p>
<p>At times, these Breadcrumb Links also have an associated dropdown, something like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646199893472/GKcGgeWLz.png" alt="image.png" /></p>
<p>We will not be building this today as it is a more advanced use case, however, in larger projects, it will be a useful thing to have as it makes navigation easier.</p>
<h2 id="heading-initializing-a-nextjs-application-with-tailwindcss">Initializing a NextJS application with TailwindCSS</h2>
<p>First of all, let us create a new NextJS application</p>
<pre><code>npx <span class="hljs-keyword">create</span>-<span class="hljs-keyword">next</span>-app breadcrumb-example
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">create</span> <span class="hljs-keyword">next</span>-app breadcrumb-example
</code></pre><p>Now, let us remove some of the boilerplate code and existing CSS (except the <code>global.css</code> file) as we are going to be adding TailwindCSS.</p>
<p>A cleaned up <code>pages/index.js</code> will look like this - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>NextJS Breadcrumb Example<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
          <span class="hljs-attr">content</span>=<span class="hljs-string">"A simple example application for a NextJS Breadcrumb tutorial"</span>
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Breadcrumb Navigation Example with NextJS<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Also, delete <code>Home.module.css</code> under the <code>styles</code> directory.</p>
<h3 id="heading-adding-tailwindcss">Adding TailwindCSS</h3>
<p>We are going to be simply following the office guide on <a target="_blank" href="https://tailwindcss.com/docs/guides/nextjs">adding TailwindCSS to a NextJS application</a>.</p>
<p>First of all, let us install TailwindCSS, PostCSS, and AutoPrefixer - </p>
<pre><code>npm <span class="hljs-keyword">install</span> -D tailwindcss postcss autoprefixer
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">add</span> -D tailwindcss postcss autoprefixer
</code></pre><p>Also, run the following command to generate the <code>tailwind.config.js</code> and <code>postcss.config.js</code> files - </p>
<pre><code>npx tailwindcss <span class="hljs-keyword">init</span> -p
</code></pre><p>Now, replace the <code>tailwind.config.js</code> file with the following - </p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">content</span>: [
    <span class="hljs-string">"./pages/**/*.{js,ts,jsx,tsx}"</span>,
    <span class="hljs-string">"./components/**/*.{js,ts,jsx,tsx}"</span>,
  ],
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<p>Now, add the following code to the <code>globals.css</code> file under the <code>styles</code> directory - </p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<p>For this example, I am going to go for a dark mode version, just so that the screenshots I take are easy on the eyes. For this, I will add this to my <code>globals.css</code> file - </p>
<pre><code class="lang-css"><span class="hljs-keyword">@layer</span> base {
  <span class="hljs-selector-tag">body</span> {
    @apply bg-black text-white;
  }
}
</code></pre>
<p>I am also going to add some styles to the <code>h1</code> tag we added earlier in the <code>index.js</code> file - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-8 md:mx-16 lg:mx-32 mt-32 text-2xl md:text-3xl lg:text-4xl"</span>&gt;</span>
    Breadcrumb Navigation Example with NextJS
<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
</code></pre>
<p>This is what the website looks like now - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646201361555/zql7yFvkI.png" alt="image.png" /></p>
<h2 id="heading-building-the-breadcrumb-navigation">Building the Breadcrumb Navigation</h2>
<p>Time for the fun part, let us build the breadcrumb navigation!</p>
<p>Let us make a new directory called <code>components</code>. This is where we will be putting in our <code>Breadcrumb</code> and <code>BreadcrumbItem</code> components.</p>
<p>Now, let us make the Breadcrumb. Create a file called <code>Breadcrumb.jsx</code> and add the following code - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { Children } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Fragment } <span class="hljs-keyword">from</span> <span class="hljs-string">"react/cjs/react.production.min"</span>;

<span class="hljs-keyword">const</span> Breadcrumb = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> childrenArray = Children.toArray(children);

  <span class="hljs-built_in">console</span>.log(childrenArray);

  <span class="hljs-keyword">const</span> childrenWtihSeperator = childrenArray.map(<span class="hljs-function">(<span class="hljs-params">child, index</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (index !== childrenArray.length - <span class="hljs-number">1</span>) {
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Fragment</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>
          {child}
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>/<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Fragment</span>&gt;</span></span>
      );
    }
    <span class="hljs-keyword">return</span> child
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-8 md:mx-16 lg:mx-32 mt-8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ol</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center space-x-4"</span>&gt;</span>{childrenWtihSeperator}<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Breadcrumb;
</code></pre>
<p>Let us go over this code step by step. First of all, we make a new component called <code>Breadcrumb</code>. We are accepting one prop, that is the children. These children will include the Breadcrumb Items, that is the links to the pages.</p>
<p>We then convert the children into an array using the <a target="_blank" href="https://reactjs.org/docs/react-api.html#reactchildrentoarray"><code>Children.toArray()</code> function</a>. </p>
<p>If we log the <code>childrenArray</code> variable to the console, we should see something like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646206142465/6mnoTJzFT.png" alt="image.png" /></p>
<p>Here, we can see that it contains all the metadata about the component such as the props, component type, etc.</p>
<p>Next, we take this array and map over it. We check if it is the last element of the array by comparing the <code>index</code> of the element with the length of the array. If it is not the last element, we return it as a <a target="_blank" href="https://reactjs.org/docs/react-api.html#reactfragment">React Fragment</a> with the child element itself, as well as a separator (<code>/</code> in this case as it is the most common one but you can change it as well). If it is the last element of the array, we just return the element, without the separator.</p>
<p>Next, the Breadcrumb component returns a <code>nav</code> element with an order list (<code>ol</code>). The ordered list is a flexbox and the <code>space-x-4</code> class adds a margin of <code>1 rem</code> between all children elements (the breadcrumb items in this case).</p>
<p>Lastly, we export the component to be used later.</p>
<p>Now, let us create another file called <code>BreadcrumbItem.jsx</code> and add the following code to it - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">const</span> BreadcrumbItem = <span class="hljs-function">(<span class="hljs-params">{ children, href, ...props }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> {<span class="hljs-attr">...props</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{href}</span> <span class="hljs-attr">passHref</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BreadcrumbItem;
</code></pre>
<p>Here, we are creating a new component called <code>BreadcrumbItem</code> which takes in a <code>children</code> and a <code>href</code> prop. We also take in additional props which will be passed into the <code>li</code> prop. Do note that each <code>BreadcrumbItem</code> is a list item and hence it fits in the ordered list we created earlier in the <code>Breadcrumb</code> component.</p>
<p>Time to add the Breadcrumb to our application. For the time being, let us add the following code to our <code>index.js</code> file under <code>pages</code> - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">Breadcrumb</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Breadcrumb</span>&gt;</span>
</code></pre>
<p>Also, don't forget the imports</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Breadcrumb <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Breadcrumb"</span>;
<span class="hljs-keyword">import</span> BreadcrumbItem <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/BreadcrumbItem"</span>;
</code></pre>
<p>This is what the home page should look like now - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646210552946/KIeeKQd8g.png" alt="image.png" /></p>
<p>Right now, we are rendering the breadcrumb on the home page and hardcoding the page names. However, this is not what we should be doing, let us look at a better approach where the routes are inferred using the router component.</p>
<h2 id="heading-dynamically-creating-the-breadcrumb-items">Dynamically creating the breadcrumb items</h2>
<p>Till now, we were manually passing in the breadcrumb items into the <code>Breadcrumb</code> component. However, this approach is not suitable for larger applications. </p>
<p>I have made some changes to the <code>_app.js</code> file to dynamically create the breadcrumb items - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/globals.css"</span>;
<span class="hljs-keyword">import</span> Breadcrumb <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Breadcrumb"</span>;
<span class="hljs-keyword">import</span> BreadcrumbItem <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/BreadcrumbItem"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">const</span> [breadcrumbs, setBreadcrumbs] = useState();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> pathWithoutQuery = router.asPath.split(<span class="hljs-string">"?"</span>)[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">let</span> pathArray = pathWithoutQuery.split(<span class="hljs-string">"/"</span>);
    pathArray.shift();

    pathArray = pathArray.filter(<span class="hljs-function">(<span class="hljs-params">path</span>) =&gt;</span> path !== <span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> breadcrumbs = pathArray.map(<span class="hljs-function">(<span class="hljs-params">path, index</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> href = <span class="hljs-string">"/"</span> + pathArray.slice(<span class="hljs-number">0</span>, index + <span class="hljs-number">1</span>).join(<span class="hljs-string">"/"</span>);
      <span class="hljs-keyword">return</span> {
        href,
        <span class="hljs-attr">label</span>: path.charAt(<span class="hljs-number">0</span>).toUpperCase() + path.slice(<span class="hljs-number">1</span>),
      };
    });

    setBreadcrumbs(breadcrumbs);
  }, [router.asPath]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Breadcrumb</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
        {breadcrumbs &amp;&amp;
          breadcrumbs.map((breadcrumb) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{breadcrumb.href}</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{breadcrumb.href}</span>&gt;</span>
              {breadcrumb.label}
            <span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
          ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">Breadcrumb</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp;
</code></pre>
<p>Let us go through this code. First of all, we are importing the NextJS router, <a target="_blank" href="https://reactjs.org/docs/hooks-effect.html">the <code>useEffect</code> hook</a>, <a target="_blank" href="https://reactjs.org/docs/hooks-state.html">the <code>useState</code> hook</a>, the <code>Breadcrumb</code> component and the <code>BreadcrumbItem</code> component. Also, you can remove the Breadcrumb component from the <code>index.js</code> file.</p>
<p>Then, inside the <code>MyApp</code> component, we are initializing the router and creating a state to store out breadcrumbs. Next, we have an <code>useEffect</code> hook which fires when the page loads and whenever <code>router.asPath</code> changes.</p>
<p>In the <code>useEffect</code> hook, firstly, we remove the query parameters from our path (which we get from <code>router.asPath</code>). Next, we split it into an array called <code>pathArray</code> and call the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift"><code>shift()</code></a> function on it to remove the first element (which is the home route). </p>
<p>Next, we also filter it out so that we don't get any other blank path (this is because the NextJS router returns a blank element at the last index if we are on the home route).</p>
<p>After that, we <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> over it and generate a new array called <code>breadcrumbs</code>. The <code>breadcrumbs</code> array is an array of objects that contain the <code>href</code> and <code>label</code> for the breadcrumb items.</p>
<p>Lastly, we save it to the <code>breadcrumbs</code> state so that we can use it outside the <code>useEffect</code> hook.</p>
<p>Next, in the JSX, we add the <code>Breadcrumb</code> component and add a <code>BreadcrumbItem</code> for the home route to it.</p>
<p>Then, we make sure that the <code>breadcrumbs</code> array is not null and map over it, adding the other <code>BreadcrumbItem</code> components.</p>
<p>Now, to test it out, create a directory called <code>dashboard</code> under <code>pages</code> and add 2 files - <code>index.js</code> and <code>[id].js</code>.</p>
<p>Add the following code to <code>index.js</code> - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;

<span class="hljs-keyword">const</span> Dashboard = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Dashboard | NextJS Breadcrumb Example<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
          <span class="hljs-attr">content</span>=<span class="hljs-string">"A simple example application for a NextJS Breadcrumb tutorial"</span>
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-8 md:mx-16 lg:mx-32 mt-32 text-2xl md:text-3xl lg:text-4xl"</span>&gt;</span>
          Breadcrumb Navigation Example with NextJS - Dashboard Page
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Dashboard;
</code></pre>
<p>Now, add the following code to <code>[id].js</code> - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;

<span class="hljs-keyword">const</span> Project = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Dynamic Route | NextJS Breadcrumb Example<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
          <span class="hljs-attr">content</span>=<span class="hljs-string">"A simple example application for a NextJS Breadcrumb tutorial"</span>
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-8 md:mx-16 lg:mx-32 mt-32 text-2xl md:text-3xl lg:text-4xl"</span>&gt;</span>
          Breadcrumb Navigation Example with NextJS - Dynamic Route Page
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Project;
</code></pre>
<p>Now, let us head over to <code>/dashboard</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646217652362/uow8lzmLv.png" alt="image.png" /></p>
<p>We can see that the breadcrumb works properly and the links work too!</p>
<p>Now let us head over to <code>/dashboard/test</code> and we can see that the breadcrumb displays the dynamic route - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646217749339/A4xbuXLHg.png" alt="image.png" /></p>
<h2 id="heading-making-it-accessible">Making it accessible</h2>
<p>First of all, let us add an <code>aria-label</code> of value <code>breadcrumb</code> to our <code>Breadcrumb</code> component - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { Children } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Fragment } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Breadcrumb = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> childrenArray = Children.toArray(children);

  <span class="hljs-keyword">const</span> childrenWtihSeperator = childrenArray.map(<span class="hljs-function">(<span class="hljs-params">child, index</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (index !== childrenArray.length - <span class="hljs-number">1</span>) {
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Fragment</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>
          {child}
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>/<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Fragment</span>&gt;</span></span>
      );
    }
    <span class="hljs-keyword">return</span> child;
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-8 md:mx-16 lg:mx-32 mt-8"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"breadcrumb"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ol</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center space-x-4"</span>&gt;</span>{childrenWtihSeperator}<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Breadcrumb;
</code></pre>
<p>Now, let us make a small change to our <code>_app.js</code> to pass in a prop to the <code>BreadcrumbItem</code> called <code>isCurrent</code> with a boolean value. The value is true for the last breadcrumb item, that is, the current page.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/globals.css"</span>;
<span class="hljs-keyword">import</span> Breadcrumb <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Breadcrumb"</span>;
<span class="hljs-keyword">import</span> BreadcrumbItem <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/BreadcrumbItem"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">const</span> router = useRouter();
  <span class="hljs-keyword">const</span> [breadcrumbs, setBreadcrumbs] = useState();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> pathWithoutQuery = router.asPath.split(<span class="hljs-string">"?"</span>)[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">let</span> pathArray = pathWithoutQuery.split(<span class="hljs-string">"/"</span>);
    pathArray.shift();

    pathArray = pathArray.filter(<span class="hljs-function">(<span class="hljs-params">path</span>) =&gt;</span> path !== <span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> breadcrumbs = pathArray.map(<span class="hljs-function">(<span class="hljs-params">path, index</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> href = <span class="hljs-string">"/"</span> + pathArray.slice(<span class="hljs-number">0</span>, index + <span class="hljs-number">1</span>).join(<span class="hljs-string">"/"</span>);
      <span class="hljs-keyword">return</span> {
        href,
        <span class="hljs-attr">label</span>: path.charAt(<span class="hljs-number">0</span>).toUpperCase() + path.slice(<span class="hljs-number">1</span>),
        <span class="hljs-attr">isCurrent</span>: index === pathArray.length - <span class="hljs-number">1</span>,
      };
    });

    setBreadcrumbs(breadcrumbs);
  }, [router.asPath]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Breadcrumb</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span> <span class="hljs-attr">isCurrent</span>=<span class="hljs-string">{router.pathname</span> === <span class="hljs-string">"/"</span>} <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>
          Home
        <span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
        {breadcrumbs &amp;&amp;
          breadcrumbs.map((breadcrumb) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">BreadcrumbItem</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{breadcrumb.href}</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">{breadcrumb.href}</span>
              <span class="hljs-attr">isCurrent</span>=<span class="hljs-string">{breadcrumb.isCurrent}</span>
            &gt;</span>
              {breadcrumb.label}
            <span class="hljs-tag">&lt;/<span class="hljs-name">BreadcrumbItem</span>&gt;</span>
          ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">Breadcrumb</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp;
</code></pre>
<p>For the initial home route, we are simply matching <code>router.pathname</code> with <code>/</code>.</p>
<p>Now in the <code>BreadcrumbItem.jsx</code> file, change the code to the following - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">const</span> BreadcrumbItem = <span class="hljs-function">(<span class="hljs-params">{ children, href, isCurrent, ...props }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> {<span class="hljs-attr">...props</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{href}</span> <span class="hljs-attr">passHref</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{isCurrent</span> &amp;&amp; "<span class="hljs-attr">text-blue-500</span>"}
          <span class="hljs-attr">aria-current</span>=<span class="hljs-string">{isCurrent</span> ? "<span class="hljs-attr">page</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">false</span>"}
        &gt;</span>
          {children}
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BreadcrumbItem;
</code></pre>
<p>Here, we are setting <code>aria-current</code> to <code>page</code> if it is the current route.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you have a working Breadcrumb navigation now. Feel free to experiment with it and have a nice day 😁🤞</p>
<h2 id="heading-important-links">🔗 Important Links</h2>
<ul>
<li><a target="_blank" href="https://nextjs-breadcrumb-example.vercel.app/">Deployed Preview</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/nextjs-breadcrumb-example">GitHub Repository with the code</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Notiger - Get realtime notifications on events from your application]]></title><description><![CDATA[This month started with Hashnode announcing another hackathon, this time in partnership with Netlify, an amazing platform to host websites and serverless functions. This got me thinking and this time an idea came to my mind quite early on in the mont...]]></description><link>https://blog.anishde.dev/notiger-get-realtime-notifications-on-events-from-your-application</link><guid isPermaLink="true">https://blog.anishde.dev/notiger-get-realtime-notifications-on-events-from-your-application</guid><category><![CDATA[Netlify]]></category><category><![CDATA[NetlifyHackathon]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[Firebase]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Tue, 01 Mar 2022 07:20:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646117836387/JMgYE869K.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This month started with <a target="_blank" href="https://townhall.hashnode.com/netlify-hackathon">Hashnode announcing another hackathon</a>, this time in partnership with <a target="_blank" href="https://www.netlify.com/">Netlify</a>, an amazing platform to host websites and serverless functions. This got me thinking and this time an idea came to my mind quite early on in the month. However, there were school exams so I did most of the work in the last 8 days 😕</p>
<h2 id="heading-what-is-notiger">🤔 What is Notiger?</h2>
<p>Notiger is a tool for developers that allows one to fire events from their applications. These events are stored in a database and can be accessed at any time. Also, if one enables notifications, they will get a notification whenever a new event is fired.</p>
<p><a target="_blank" href="https://www.notiger.xyz/">Live Demo</a> / <a target="_blank" href="https://github.com/AnishDe12020/notiger">GitHub Repository</a></p>
<h2 id="heading-a-little-bit-about-how-the-idea">💡A little bit about how the idea</h2>
<p>My <a target="_blank" href="https://anishde.dev/">portfolio site</a> has got a huge role to play in this idea. I have a contact form through which one can send me messages. Now, storing the messages in a database makes sense but it would be better to have an easier way to view these messages and also get notified when they are sent.</p>
<p>I also figured that many people would like such a solution as not everyone has the time to implement the logic for saving form responses and events to a database. It has many other use cases, I will get to them in a later part in this blog post.</p>
<h2 id="heading-the-tech-stack">📚 The tech stack</h2>
<p>Notiger has got to do everything from authentication to data storage to sending out push notifications. Here is the tech stack it uses - </p>
<ul>
<li><a target="_blank" href="https://nextjs.org/">NextJS</a> as the frontend framework</li>
<li><a target="_blank" href="https://tailwindcss.com/">TailwindCSS</a> as a CSS utility class library to style the frontend</li>
<li><a target="_blank" href="https://www.radix-ui.com/">Radix UI</a> for unstyled components like Modals (dialogs) and accordions</li>
<li><a target="_blank" href="https://headlessui.dev/">Headless UI</a> for animations</li>
<li><a target="_blank" href="https://next-auth.js.org/">NextAuth</a>, a simple library to implement authentication</li>
<li><a target="_blank" href="https://firebase.google.com/products/cloud-messaging">Firebase Cloud Messaging</a> for sending out notifications</li>
<li><a target="_blank" href="https://www.mongodb.com/">MongoDB</a> to store user data and events</li>
</ul>
<p>and of course...</p>
<ul>
<li><a target="_blank" href="https://www.netlify.com/">Netlify</a> for deploying the frontend. It also supports serverless functions and hence I was able to use NextJS API routes as my backend</li>
</ul>
<h2 id="heading-how-does-notiger-work">⚙️ How does Notiger work?</h2>
<p>After creating a notiger account, you create a project. Each project can have streams that will receive the events. When an event is received, it is stored in a database, <a target="_blank" href="https://mongodb.com/">MongoDB</a> in this case and a notification is pushed to all devices with notifications enabled that the user has.</p>
<p>An example event - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646108561333/FnImQKDG6.png" alt="image.png" /></p>
<p>As you can see, this event is actually from Netlify. Netlify has this amazing feature that allows us to set up webhook notifications for specific events within Netlify. You can learn more about it <a target="_blank" href="https://docs.netlify.com/site-deploys/notifications/#outgoing-webhooks">here</a>. There are many other applications (like GitHub) that allow us to set up webhook notifications for some events. Notiger can receive these webhooks, store the payload (which usually contains important data regarding the event) in a database, and send out a notification whenever the webhook is fired.</p>
<h3 id="heading-how-did-i-implement-authentication-with-nextauth">How did I implement authentication with NextAuth?</h3>
<p><a target="_blank" href="https://next-auth.js.org/">NextAuth</a> is a JS SDK that allows us to add authentication to our NextJS applications easily. Firstly, we need to set up an API route that will handle all the authentication. Thankfully, NextAuth makes it really easy by providing us with some boilerplate code, and also, we need to only add about 1 file for this. Here is my code - 
<code>pages/api/auth/[...nextauth].ts</code> - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth"</span>;
<span class="hljs-keyword">import</span> GoogleProvider <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth/providers/google"</span>;
<span class="hljs-keyword">import</span> { MongoDBAdapter } <span class="hljs-keyword">from</span> <span class="hljs-string">"@next-auth/mongodb-adapter"</span>;
<span class="hljs-keyword">import</span> clientPromise <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../lib/mongodb-nextauth"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NextAuth({
  adapter: MongoDBAdapter(clientPromise),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    <span class="hljs-keyword">async</span> session({ session, token, user }) {
      session.token = token;
      <span class="hljs-keyword">return</span> session;
    },
    <span class="hljs-keyword">async</span> jwt({ token, user }) {
      <span class="hljs-keyword">if</span> (user) {
        token.user = user;
      }
      <span class="hljs-keyword">return</span> token;
    },
  },
  session: {
    strategy: <span class="hljs-string">"jwt"</span>,
  },
  pages: {
    signIn: <span class="hljs-string">"/auth"</span>,
    signOut: <span class="hljs-string">"/auth"</span>,
  },
});
</code></pre>
<p>Now, here, I have added social authentication with Google (in just 4 lines of code!) and customized the <code>session</code> and the <code>jwt</code> callbacks. Also, I am storing user data in a database, MongoDB in this case. The JWT callback creates a JWT with the user data object, which is used in other parts of the application. The session callback is customized to include the JWT token when the current session is retrieved. I have also implemented a custom page for authentication, <code>/auth</code>.</p>
<h4 id="heading-securing-pages-with-middleware">Securing pages with middleware</h4>
<p>Now, I don't want my users to be greeted with errors by visiting something like the dashboard page when they are not logged in. Thankfully, NextAuth lets us secure pages with just 1 line of code using <a target="_blank" href="https://nextjs.org/docs/middleware">NextJS Middleware</a>. For example, this is how the dashboard page is secured - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth/middleware"</span>;
</code></pre>
<p>Yes, that is it!!!</p>
<p>If one is not logged in they, are redirected to the <code>/auth</code> page and once they have logged in, they are redirected to the dashboard page (or any other page from where they were redirected to <code>/auth</code>).</p>
<p>Now, I could have used server-side rendering on my pages to secure them as well, but it requires more code and is also slower and more resource-intensive. Netlify is also one of those hosting providers which support middleware with zero additional configuration which makes using middleware make more sense.</p>
<h3 id="heading-push-notifications-with-firebase-cloud-messaging">Push notifications with Firebase Cloud Messaging</h3>
<p>This was one of the most challenging things to implement. Here is why - </p>
<ul>
<li>No official docs on implementing it with React or NextJS</li>
<li>Guides/videos on how to implement it with NextJS are very less and even then they are outdated or things don't straight-up work at times</li>
<li>Ran into multiple issues when implementing (mainly because I was using Brave to test Notiger and by default, Brave blocks Firebase Cloud Messaging)</li>
</ul>
<p>First of all, I added a service worker that would receive the message in the background (that is, when the application is out of focus or closed) and fire a push notification - 
<code>firebase-messaging-sw.js</code> - </p>
<pre><code class="lang-js">importScripts(<span class="hljs-string">"https://www.gstatic.com/firebasejs/9.6.7/firebase-app-compat.js"</span>)
importScripts(<span class="hljs-string">"https://www.gstatic.com/firebasejs/9.6.7/firebase-messaging-compat.js"</span>)

<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console,
  <span class="hljs-attr">authDomain</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console,
  <span class="hljs-attr">projectId</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console,
  <span class="hljs-attr">storageBucket</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console,
  <span class="hljs-attr">messagingSenderId</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console,
  <span class="hljs-attr">appId</span>: # Retrieve <span class="hljs-keyword">from</span> the Firebase Console
};

<span class="hljs-keyword">const</span> app = firebase.initializeApp(firebaseConfig);
<span class="hljs-keyword">const</span> messaging = firebase.messaging(app);

messaging.onBackgroundMessage(<span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Notification payload: "</span>, payload);

  <span class="hljs-keyword">return</span> self.registration.showNotification(payload.data.name || <span class="hljs-string">"New Event"</span>, {
    <span class="hljs-attr">body</span>: payload.data.description || <span class="hljs-string">""</span>,
  });
})
</code></pre>
<p>I have also added a handler for foreground notifications (that is when the application is in focus) that would create a toast - </p>
<pre><code class="lang-ts">useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">import</span>(<span class="hljs-string">"../lib/firebase"</span>).then(<span class="hljs-function">(<span class="hljs-params">{ messaging }</span>) =&gt;</span> {
      onMessage(messaging, <span class="hljs-function"><span class="hljs-params">payload</span> =&gt;</span> {
        toast.custom(<span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> &lt;EventToast t={t} payload={payload} /&gt;);
      });
    });
  }, []);
</code></pre>
<p>I had to import messaging dynamically on the client-side here as it was getting imported on the server-side during building and was throwing errors.</p>
<h3 id="heading-how-are-events-handled">How are events handled?</h3>
<p>I am using NextJS API routes for the backend of the application and any webhooks are posted to the events API routes, <code>/api/events</code></p>
<p>Here is the code for handling events - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> apiKey = req.headers[<span class="hljs-string">"x-api-key"</span>] || req.query.apiKey;
    <span class="hljs-keyword">if</span> (!apiKey) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({
            error: <span class="hljs-string">"Missing API key"</span>,
        });
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!(<span class="hljs-keyword">await</span> ApiKey.exists({
            key: apiKey
        }))) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({
            error: <span class="hljs-string">"Invalid API key"</span>,
        });
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">const</span> streamIdFormatted = <span class="hljs-keyword">new</span> ObjectId(streamId <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>);
        <span class="hljs-keyword">const</span> body = req.body;
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> body === <span class="hljs-string">"object"</span>) {
            <span class="hljs-keyword">if</span> (sizeof(body) &lt;= <span class="hljs-number">16384</span>) {
                <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> Event({
                    streamId: streamIdFormatted,
                    ...body,
                });

                event.save(<span class="hljs-function">(<span class="hljs-params">err, event</span>) =&gt;</span> {
                    <span class="hljs-keyword">if</span> (err) {
                        res.status(<span class="hljs-number">500</span>).json({
                            error: err.message
                        });
                    } <span class="hljs-keyword">else</span> {
                        res.status(<span class="hljs-number">200</span>).json(event);
                    }
                });

                <span class="hljs-keyword">const</span> stream = <span class="hljs-keyword">await</span> Stream.findOne({
                    _id: streamIdFormatted,
                });

                <span class="hljs-keyword">const</span> registrationTokens = <span class="hljs-keyword">await</span> FCMToken.find({
                    ownerId: stream.ownerId,
                });

                <span class="hljs-keyword">if</span> (registrationTokens.length &gt; <span class="hljs-number">0</span>) {
                    messaging
                        .sendMulticast({
                            data: body,
                            tokens: registrationTokens.map(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token.token),
                        })
                        .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(res));
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Body too large. Keep it under 16384 bytes"</span>);
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Body must be an object (json)"</span>);
        }
    }
} <span class="hljs-keyword">catch</span> (error) {
    res.status(<span class="hljs-number">400</span>).json({
        error: error.message
    });
}
</code></pre>
<p>First of all, everything is wrapped in a <code>try...catch</code> block so that we return an error in case something goes wrong.</p>
<p>In the <code>try</code> block, we are looking for 2 parameters, the <code>streamId</code> and the <code>apiKey</code>. The <code>streamId</code> must be passed in as a query parameter to the application. The <code>apiKey</code> should be passed in as a the <code>x-api-key</code> header usually but in case it is not possible, it can also be passed in as a query parameter. Here is how the requests will look like - </p>
<p>With the API key passed in as a header - </p>
<pre><code class="lang-sh">curl --location --request POST <span class="hljs-string">'https://www.notiger.xyz/api/events?streamId=&lt;STREAM_ID&gt;'</span> \
--header <span class="hljs-string">'x-api-key: &lt;API_KEY&gt;'</span> \
--header <span class="hljs-string">'Content-Type: application/json'</span> \
--data-raw <span class="hljs-string">'{
    "name": "Test Event",
    "description": "Test Description",
    "icon": "🎇"
}'</span>
</code></pre>
<p>With the API key as a query parameter - </p>
<pre><code class="lang-sh">curl --location --request POST <span class="hljs-string">'https://www.notiger.xyz/api/events?streamId=&lt;STREAM_ID&gt;&amp;apiKey=&lt;API_KEY&gt;'</span> \
--header <span class="hljs-string">'Content-Type: application/json'</span> \
--data-raw <span class="hljs-string">'{
    "name": "Test Event",
    "description": "Test Description",
    "icon": "🎇"
}'</span>
</code></pre>
<p>Do note that you can send this POST request from other programming languages as well. Like with JavaScript, for instance, you can use <code>axios</code> or with Python, you can use <code>requests</code>.</p>
<p>If the API key is valid, we save the event to the database, given that the event payload is under 16 kilobytes in size and is in the form of a JSON object. This is to prevent spam. Checks for <code>streamId</code> is done beforehand itself. </p>
<p>After saving the event to the database, we retrieve the stream associated with the event and then the Firebase Cloud Messaging registration tokens associated with the owner of the stream. This is needed to send the notifications to the devices with notifications enabled the user has. Next, we send the notification.</p>
<h3 id="heading-understanding-the-event-payload">Understanding the event payload</h3>
<p>Whenever a webhook is fired, a payload is passed in as well. Notiger will accept all payloads under 16 kilobytes which are in the form of a JSON object. This payload usually contains more details about an event. For example, in the case of a build success event on Netlify, it contains information about the site, the time it was published, the deployed URL, the serverless functions deployed to AWS Lambda, and many more. </p>
<p>Notiger also adds an <code>_id</code>, <code>__v</code> and <code>streamId</code> field to each event. The <code>_id</code> field is the id of the event stored in MongoDB. In fact, the date it was created can also be retrieved from it.</p>
<p>Code to retrieve created timestamp of a MongoDB document - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> getCreatedAtFromMongoId = (mongoId: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">parseInt</span>(mongoId.substring(<span class="hljs-number">0</span>, <span class="hljs-number">8</span>), <span class="hljs-number">16</span>) * <span class="hljs-number">1000</span>), <span class="hljs-string">"PPpp"</span>);
};
</code></pre>
<p>I go over it in more detail in this <a target="_blank" href="https://twitter.com/AnishDe12020/status/1497067113969709059">tweet</a></p>
<p>The <code>__v</code> field is the version of the document and increments whenever it is updated. The <code>streamId</code> field is the id of the stream the event belongs to.</p>
<h2 id="heading-use-cases">⚗️ Use cases</h2>
<p>What are the applications where Notiger can be used?</p>
<p>I have already gone over the example of the contact form and Netlify build notifications. There are, however, many more use cases, some quite advanced - </p>
<h3 id="heading-iot-devices">IoT Devices</h3>
<p>As smart home devices and other IoT devices are getting more popular and common, one is looking for making better use of them. When a specific event occurs, like say when the temperature goes above 30°C in a lab environment, an event can be triggered that will push a notification to, say, the lab owner's phone so that they can take immediate action. </p>
<h3 id="heading-manufacturing">Manufacturing</h3>
<p>Many manufacturing tasks often take a long time and notifications can help here too. Say, there is a 3D print going on and as soon as it is done, the 3D printer can fire a webhook notifying the owner that the print is done. It can also be used in cases of mechanical failures etc.</p>
<h3 id="heading-randampd">R&amp;D</h3>
<p>Complex computational operations take a long time and, here as well, notifications can be fired in case of failures, successes, etc.</p>
<h3 id="heading-saas">SaaS</h3>
<p>SaaS owners often want to get a real-time feed of the number of sign-ups, paid users, etc. on their applications. Notifications can be fired on events such as sign-ups, plan upgrades, bug reports, etc.</p>
<h2 id="heading-how-to-use-notiger">🖱️ How to use Notiger?</h2>
<p>One of my goals with Notiger was to make it as easy to use as possible. Let us go through a mini-guide on how to use it</p>
<p>The first step is to sign up by clicking the login button at the top-right corner or visiting <a target="_blank" href="https://www.notiger.xyz/auth?callbackUrl=/"><code>/auth</code></a>. Here, you will be prompted to sign in with Google.</p>
<p>Next, head over to the <a target="_blank" href="https://www.notiger.xyz/dashboard">dashboard page</a> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115285234/-uhf914Mg.png" alt="image.png" /></p>
<p>Next, create a project - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115337757/HQ68CjfxE.png" alt="image.png" /></p>
<p>Click on the project and you should be taken to the project page. Here is how it should look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646119189582/kI4gw9JH-.png" alt="image.png" /></p>
<p>Now, let us create a stream - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115514509/mdzua37wh.png" alt="image.png" /></p>
<p>Upon clicking on the stream, we can see that there are no events yet - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115551814/a3ws9wzdj.png" alt="image.png" /></p>
<p>We can see the API route by clicking on the button that says "See API Route" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115616742/pMwU3z01n.png" alt="image.png" /></p>
<p>This will be used when sending webhook notifications.</p>
<p>Now that we have copied the URL, we can head over to generate an API Key - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646115705763/ANMmAGRZC.png" alt="image.png" /></p>
<p>Copy this value as well.</p>
<p>For this example, I am going to be using Postman to send a webhook but in a real-world scenario, it will likely be sent from code or a shell script.</p>
<p>Here is how we do it from Postman - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646116025556/3f0PShNh7.png" alt="image.png" /></p>
<p>Also, don't forget to add a payload. It may look something like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646116735741/9DIHYUjdp.png" alt="image.png" /></p>
<p>Do note that you can pass in the API key as a query parameter called <code>apiKey</code> as well. Now click on "Send".</p>
<p>We will get back a response, somewhat like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646116690269/KAMFowtzQ.png" alt="image.png" /></p>
<p>Also, now if we check our stream in the Notiger projects dashboard, we will be able to see the event - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646116852330/fYEjPZzhf.png" alt="image.png" /></p>
<p>To enable notifications, click the bell on the bottom-right corner - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646116887226/GtcX71v0I.png" alt="image.png" /></p>
<h2 id="heading-conclusion">✨ Conclusion</h2>
<p>It had been an amazing journey building Notiger, squashing bugs, and writing this blog post! Can't wait to see how this side project does in the days to come!</p>
<p>Bye, and have a nice day 😁🤞</p>
<h2 id="heading-important-links">🔗 Important Links</h2>
<ul>
<li><a target="_blank" href="https://www.notiger.xyz/">Notiger</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/notiger">Notiger GitHub Repository</a></li>
<li><a target="_blank" href="https://www.netlify.com/">Netlify</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Privacy friendly website analytics with Umami and NextJS]]></title><description><![CDATA[Website analytics is truly a very important thing. We can understand our audience well and can tailor our content to our audience for higher engagement. Google Analytics had always been the go-to solution as it is popular, easy to set up, and gives a...]]></description><link>https://blog.anishde.dev/privacy-friendly-website-analytics-with-umami-and-nextjs</link><guid isPermaLink="true">https://blog.anishde.dev/privacy-friendly-website-analytics-with-umami-and-nextjs</guid><category><![CDATA[Next.js]]></category><category><![CDATA[analytics]]></category><category><![CDATA[privacy]]></category><category><![CDATA[hosting]]></category><category><![CDATA[website]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Thu, 17 Feb 2022 10:19:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645091755632/-NyymMbeL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Website analytics is truly a very important thing. We can understand our audience well and can tailor our content to our audience for higher engagement. <a target="_blank" href="https://analytics.google.com/">Google Analytics</a> had always been the go-to solution as it is popular, easy to set up, and gives a lot of data.</p>
<p>However, Google Analytics has got its own set of issues. One must ask for a cookie consent to use Google Analytics as Google Analytics uses cookies. The Google Analytics script is also quite big and is known to slow down websites. There have been recent allegations against Google Analytics for not being privacy-friendly and <a target="_blank" href="https://techcrunch.com/2022/02/10/cnil-google-analytics-gdpr-breach/">many European authorities have also found it breaching GDPR</a>.</p>
<p>So, what is the solution?</p>
<p>Over the years, many privacy-friendly analytics solutions have emerged including <a target="_blank" href="https://usefathom.com/">Fathom Analytics</a>, <a target="_blank" href="https://plausible.io/">Plausible Analytics</a>, and <a target="_blank" href="https://umami.is/">Umami Analytics</a>. The last 2 are open-source and all 3 of them are cookie-less and have a lightweight script that should not affect website load times.</p>
<p>We are going to be focusing on Umami in this article</p>
<h2 id="heading-a-little-bit-about-umami">A little bit about Umami</h2>
<p>Umami is an open-source self-hosted analytics service. This means the source code can be accessed by anyone and one must host it themselves. Now, you might say that this costs money and it is not free but today we are going to look at how we can host it for free. Also, Umami uses NextJS API routes for the backend and hence it can run on any serverless architecture. We are going to be looking at setting it up on <a target="_blank" href="https://railway.app/">Railway</a> today, however, it can also be hosted on <a target="_blank" href="https://vercel.com/dashboard">Vercel</a> or <a target="_blank" href="https://www.netlify.com/">Netlify</a>. We are also going to look at adding analytics to a NextJS application.</p>
<p>You can see a <a target="_blank" href="https://app.umami.is/share/8rmHaheU/umami.is">live demo of the platform here</a></p>
<p>Fun fact: <a target="_blank" href="https://hashnode.com/">Hashnode</a> also uses Umami and is rolling out an Umami dashboard as advanced analytics 😎</p>
<p>You can see the <a target="_blank" href="https://stats.hashnode.com/share/VDldVSkU/9f4dd26c-c7e6-4fa1-88aa-87d90a0dba43">public analytics for my blog here</a></p>
<h2 id="heading-hosting-umami-for-free-on-railway">Hosting Umami for free on Railway</h2>
<p><a target="_blank" href="https://railway.app/">Railway</a> is an awesome hosting platform that lets you host applications quickly and easily. The free plan allows usage of up to $5/month which should be good enough for a few small to medium-sized websites.</p>
<p>In fact, I have been using it for the past 3-4 months and it has been an amazing experience. My usage costs are usually lower than $2/month and hence I have never paid anything. You don't even need to link your credit card!</p>
<p>My usage this month (3 websites) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645083158123/UEZNXrJL_.png" alt="image.png" /></p>
<p>You can also link a credit card to get $10 of usage per month for free (you will be charged for anything above that).</p>
<p>You can sign up <a target="_blank" href="https://railway.app?referralCode=AnishDe12020">here</a></p>
<h3 id="heading-setting-up-the-project-on-railway">Setting up the project on Railway</h3>
<p>We are going to follow the <a target="_blank" href="https://umami.is/docs/running-on-railway">official guide on hosting it on Railway</a></p>
<p>First of all, we must fork the repository. This will help us make changes to the source code to fit our own needs and more importantly, receive updates in the future (as we will see later in the tutorial). Head over to the <a target="_blank" href="https://github.com/mikecao/umami">Umami GitHub repository</a> and click on fork on the top-right corner - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645084023444/UIfe4bLPm.png" alt="image.png" /></p>
<p>You may be asked to select your personal account or an organization if you are in any. I would recommend going for personal account unless it is for an organization.</p>
<p>Once you have signed up for an account, click on "New Project" (note that I already have an existing project and hence the layout looks like this. It may be different for you) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645083729581/NrhvEnIUM.png" alt="image.png" /></p>
<p>Now, select "Deploy from Repo" on the new project screen - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645084230742/IsU6U7Fb2.png" alt="image.png" /></p>
<p>Do note that if you didn't sign up with GitHub, you will be prompted to connect your GitHub account.</p>
<p>Search and select Umami there.</p>
<p>Make sure that the master branch has been selected. Now click on deploy - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645085133125/PEO4Np0hp.png" alt="image.png" /></p>
<p>This might take some time (2-5 minutes).</p>
<p>This is how it should look like after deploying (do note that I am currently using the Metro UI and the layout might look a little different) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645085314989/SGu2FefSM.png" alt="image.png" /></p>
<p>Now, we need to add a database. We are going to be using PostgreSQL for this example. Now, Railway has built-in support for databases and hence we can spin up a PostgreSQL instance within Railway itself for free.</p>
<p>Click on this "New" button - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645085285069/quu1-MwRS.png" alt="image.png" /></p>
<p>Select "Databases" and then select "PostgreSQL" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645085252955/_pxkdwdGj.png" alt="image.png" /></p>
<p>This might take some time as well.</p>
<p>Do note that if you are using the old UI, you have to select the "Add Plugin" button.</p>
<p>Now, we need to add two environment variables, <code>PORT</code> and <code>HASH_SALT</code>. Click on the card that says "umami" and go to the "Variables" tab - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645085586275/6b3hX2IJX.png" alt="image.png" /></p>
<p>In the old UI, there will be a button called "Variables" in the sidebar. Click that and then add the following variables under "custom".</p>
<p>We need to put a random string for the <code>HASH_SALT</code> environment variable. Use any random string generator like <a target="_blank" href="https://devkit.one/generators/random-string">this one</a>. Let us go with 20 characters including uppercase and lowercase letters, numbers, and symbols - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645086073622/Oaa9dkb2U.png" alt="image.png" /></p>
<p>Now paste that into Railway and click "Add" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645086092990/Z5TMAndNtH.png" alt="image.png" /></p>
<p>Also, add an environment variable called <code>PORT</code> and set it to <code>3000</code> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645086013249/TBX1KtxPZ.png" alt="image.png" /></p>
<p>Note that Railway will redeploy our application every time we add an environment variable.</p>
<h3 id="heading-setting-up-our-database-schema">Setting up our database schema</h3>
<p>Now, we need to make tables in our database. For this, we need to locally clone the project. Go ahead and clone it with git and open a terminal in that repository (I am using the GitHub CLI to clone here but you can use <code>git</code> as well) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645086274311/ESnkCPq02.png" alt="image.png" /></p>
<p>Now, we need to <a target="_blank" href="https://docs.railway.app/develop/cli">install the Railway CLI</a>. You can install it with NPM with the following command - </p>
<pre><code><span class="hljs-built_in">npm</span> i -g @railway/cli
</code></pre><p>You can also install it with <a target="_blank" href="https://brew.sh/">Homebrew</a> with the following command - </p>
<pre><code>brew install railwayapp<span class="hljs-operator">/</span>railway<span class="hljs-operator">/</span>railway
</code></pre><p>Now run the following command to authenticate the CLI with your Railway account - </p>
<pre><code>railway <span class="hljs-keyword">login</span>
</code></pre><p>Note that if you face any issues while doing this, you can also try logging in with the following command -</p>
<pre><code>railway <span class="hljs-keyword">login</span> <span class="hljs-comment">--browserless</span>
</code></pre><p>Now run the following command to link the local directory with your Railway project - </p>
<pre><code>railway <span class="hljs-keyword">link</span>
</code></pre><p>Now head over to Railway and click the PostgreSQL card and go to the "Variables" tab - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645086844780/5Z7sCtfB6.png" alt="image.png" /></p>
<p>Now run the following command in the terminal - </p>
<pre><code>railway run psql <span class="hljs-operator">-</span>h PGHOST <span class="hljs-operator">-</span>U PGUSER <span class="hljs-operator">-</span>d PGDATABASE <span class="hljs-operator">-</span>f sql<span class="hljs-operator">/</span>schema.postgresql.sql
</code></pre><p>Replace the values caps with their corresponding values from the Railway dashboard (from the environment variables tab for PostgreSQL from the previous step)</p>
<p>Now press enter to run the command.</p>
<p>Do note that you need the PostgreSQL CLI for this. If you don't have it, you can follow <a target="_blank" href="https://www.timescale.com/blog/how-to-install-psql-on-mac-ubuntu-debian-windows/">this guide to install it</a>.</p>
<p>Now run the following command to deploy it - </p>
<pre><code><span class="hljs-attribute">railway</span> up
</code></pre><p>Hooray, we have successfully deployed Umami 🥳</p>
<h2 id="heading-using-umami">Using Umami</h2>
<p>After deploying, you will get an URL to deployment logged on to your CLI. You can also retrieve this URL from the Railway web app.</p>
<p>You can also set up a custom subdomain (or even a custom domain) from the Umami dashboard - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645087633934/7mcDLz429.png" alt="image.png" /></p>
<p>You will see a login screen now. The username is "admin" and the default password is "umami" (we will change this).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645087925651/mO7f7rbEO.png" alt="image.png" /></p>
<p>Our dashboard should look like this now - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645087905782/XJLyLBPL1.png" alt="image.png" /></p>
<p>Now, there is a banner saying there is a new version out! While writing this tutorial, <a target="_blank" href="https://github.com/mikecao">Mikecao</a>, the creator of Umami, pushed a new version 😅</p>
<p>Now, that is a good thing because now I get to show you how to update Umami 😎</p>
<p>Before that, let us just quickly change our password as "umami" isn't a secure password.</p>
<p>Head over to Settings --&gt; Profile and click on "Change Password"</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645088278499/VheBGhUUXY.png" alt="image.png" /></p>
<p>Enter "umami" in the "Current Password" field and then set a new secure password and click "Save" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645088387324/VmfJRvZft.png" alt="image.png" /></p>
<h3 id="heading-updating-umami">Updating Umami</h3>
<p>Head over to the forked Umami repository on GitHub. You should see that our branch is behind by a few commits - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645088470198/r7hgqcKVi.png" alt="image.png" /></p>
<p>Click on "Fetch Upstream" and then "Fetch and merge" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645088510722/8sBS4wExL.png" alt="image.png" /></p>
<p>That is it! A new deployment will be initiated on Railway and in a few minutes, you should be up and running the latest version - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645088575876/UhIEb2SZl.png" alt="image.png" /></p>
<h2 id="heading-adding-umami-to-a-nextjs-website">Adding Umami to a NextJS website</h2>
<p>Now, let us look at adding Umami to a NextJS website. For this let us first create a new NextJS application (note that it will work with existing NextJS applications as well) - </p>
<pre><code>npx create<span class="hljs-operator">-</span>next<span class="hljs-operator">-</span>app umami<span class="hljs-operator">-</span>tutorial
</code></pre><p>Let us now move into that directory - </p>
<pre><code><span class="hljs-built_in">cd</span> umami-tutorial
</code></pre><p>Now, open it in your favorite text editor. We will be using VSCode for this tutorial - </p>
<pre><code><span class="hljs-attribute">code</span> .
</code></pre><p>Now, open the <code>pages/_app.js</code> file. It should look like this - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">'../styles/globals.css'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span></span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp
</code></pre>
<p>Now, let us add the script tag for Umami. This is how our <code>_app.js</code> should look like now - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Script <span class="hljs-keyword">from</span> <span class="hljs-string">"next/script"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/globals.css"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      {process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL &amp;&amp;
        process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">Script</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">{process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL}</span>
            <span class="hljs-attr">data-website-id</span>=<span class="hljs-string">{process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}</span>
            <span class="hljs-attr">strategy</span>=<span class="hljs-string">"lazyOnload"</span>
          /&gt;</span><span class="xml">
        )}
      <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp;
</code></pre>
<p>Here, we are using the <a target="_blank" href="https://nextjs.org/docs/api-reference/next/script">NextJS Script component</a> and lazy loading the script so that it doesn't block our website from loading.</p>
<p>We will also need to add the environment variables but before that, we need to add the website to Umami. </p>
<p>Head over to Umami and then to Settings --&gt; Websites - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090114917/h3EVdgmDM.png" alt="image.png" /></p>
<p>Now, click on "Add website"</p>
<p>I am naming this "Umami Tutorial" but you can name it whatever you want to. In the next field, make sure to enter the domain and NOT THE URL to the website. Note that I have quickly created a GitHub repository and deployed this NextJS app to <a target="_blank" href="https://vercel.com/">Vercel</a>. I have also checked "Enable Share URL" so that I can share the analytics for this website with you guys 😁</p>
<p>Here it is - <a target="_blank" href="https://umami-tutorial.up.railway.app/share/3lOPyajp/Umami%20Tutorial">https://umami-tutorial.up.railway.app/share/3lOPyajp/Umami%20Tutorial</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090318295/8b01p6l-xH.png" alt="image.png" /></p>
<p>Now, click on "Save" and then "Get tracking code" - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090384630/csMsmGMW7.png" alt="image.png" /></p>
<p>From the modal that appears, just copy the values of <code>data-website-id</code> and <code>src</code> - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090430194/_Ep6VvsVs.png" alt="image.png" /></p>
<p>Now, create a new file in your NextJS app called <code>.env.local</code> and add the following environment variables - </p>
<pre><code>NEXT_PUBLIC_UMAMI_SCRIPT_URL= <span class="hljs-comment"># Your script URL, the value under `src`</span>
NEXT_PUBLIC_UMAMI_WEBSITE_ID= <span class="hljs-comment"># The website's id, the value under `data-website-id`</span>
</code></pre><p>Now, visit the website on your browser and take a look at the Umami dashboard, it should record a view and a visit under the "Realtime" tab - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090808765/DClWJJP28.png" alt="image.png" /></p>
<p>We can see more detailed analytics under the details page of the website - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645090870636/yVqIm4_VA.png" alt="image.png" /></p>
<p>More data will pile up as you start getting visitors on your site</p>
<p>Note: Some browsers like brave have in-built ad-blockers which blocks such scripts from loading in many cases. Even third-party ad-blockers can be responsible for this. If no data is showing up in your Umami dashboard, try a browser without ad-blockers (or private mode), try restarting your development server, and make sure that the values of the environment variables are right.</p>
<p>Woohoo, that was a lot!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We got Umami set up and running and added analytics to a NextJS application. Umami does a lot more like recording events. Take a look at <a target="_blank" href="https://umami.is/docs">their documentation for more information</a></p>
<p>I hope everything worked out for you. Do feel free to comment on this article or reach out to me on <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a> and I will help you out 😄</p>
<h2 id="heading-important-links">Important Links</h2>
<ul>
<li><a target="_blank" href="https://umami.is/">Umami</a></li>
<li><a target="_blank" href="https://umami.is/docs/about">Umami Docs</a></li>
<li><a target="_blank" href="https://umami.is/docs/running-on-railway">Official guide on setting up Umami on Railway</a></li>
<li><a target="_blank" href="https://railway.app/">Railway</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/umami-tutorial">Repository for this tutorial</a></li>
<li><a target="_blank" href="https://umami-tutorial.vercel.app/">Demo website for this tutorial</a></li>
<li><a target="_blank" href="https://umami-tutorial.up.railway.app/share/3lOPyajp/Umami%20Tutorial">Public analytics for the demo website</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[TwNFT - Mint your tweets as NFTs easily and for free]]></title><description><![CDATA[🤔 What is TwNFT?
TwNFT is a simple web application that allows you to mint your tweets as NFTs for free.
It is my submission for the Thirdweb x Hashnode Hackathon.
Live Demo / GitHub Repository
🌐 What is Thirdweb?
Getting started with web3 can be d...]]></description><link>https://blog.anishde.dev/twnft-mint-your-tweets-as-nfts-easily-and-for-free</link><guid isPermaLink="true">https://blog.anishde.dev/twnft-mint-your-tweets-as-nfts-easily-and-for-free</guid><category><![CDATA[thirdweb]]></category><category><![CDATA[Firebase]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[thirdweb Hackathon]]></category><category><![CDATA[Twitter]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Mon, 31 Jan 2022 11:03:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643626826069/Py_29X16z.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643621521059/4bLysGMEz.gif" alt="let-us-get-started-minions" /></p>
<h2 id="heading-what-is-twnft">🤔 What is TwNFT?</h2>
<p>TwNFT is a simple web application that allows you to mint your tweets as <strong>NFTs</strong> for free.</p>
<p>It is my submission for the <a target="_blank" href="https://townhall.hashnode.com/thirdweb-hackathon">Thirdweb x Hashnode Hackathon</a>.</p>
<p><a target="_blank" href="https://twnft.vercel.app/">Live Demo</a> / <a target="_blank" href="https://github.com/AnishDe12020/twnft">GitHub Repository</a></p>
<h2 id="heading-what-is-thirdweb">🌐 What is Thirdweb?</h2>
<p>Getting started with web3 can be difficult even though it is the hype nowadays. We need to write something called a smart contract, which is required to perform actions on the blockchain. To write smart contracts on the Ethereum blockchain, we need to learn a new programming language called <a target="_blank" href="https://soliditylang.org/">Solidity</a>.</p>
<p><a target="_blank" href="https://thirdweb.com/">Thirdweb</a> provides us with smart contracts which have been written by professionals in the field. They also provide us SDKs which makes using these easy. This allows people with basic programming knowledge to make web3 applications with ease. Thirdweb also takes care of deploying these smart contracts to the blockchain.</p>
<p>Let us now go back to TwNFT</p>
<h2 id="heading-where-did-the-idea-come-from">💡Where did the idea come from?</h2>
<p>This hackathon was announced back on the 5th of January, 2022 but I didn't get a solid idea until the 18th of January. So where did it come from?</p>
<p>I came across an application called <a target="_blank" href="https://gitnft.quine.sh/">GitNFT</a> from one of <a class="user-mention" href="https://hashnode.com/@dailydevtips">Chris Bongers</a>'s articles. GitNFT lets you mint git commits as NFTs and that is when I thought, "How about minting tweets as NFTs?". </p>
<p>I did some research and didn't find any application that did this so it was a golden opportunity for me 🤩</p>
<h2 id="heading-the-tech-stack">📚 The tech stack</h2>
<p>What technologies did I use for TwNFT?</p>
<ul>
<li><a target="_blank" href="https://nextjs.org/">NextJS</a> for the website</li>
<li><a target="_blank" href="https://tailwindcss.com/">TailwindCSS</a> for styling</li>
<li><a target="_blank" href="https://firebase.google.com/">Firebase</a> for Twitter authentication and data storage</li>
<li><a target="_blank" href="https://vercel.com/">Vercel</a> for deploying the frontend</li>
<li><a target="_blank" href="https://www.npmjs.com/package/express">Express</a> for the backend API</li>
<li><a target="_blank" href="https://dashboard.heroku.com/apps">Heroku</a> to deploy the backend</li>
</ul>
<p>and of course...</p>
<ul>
<li><a target="_blank" href="https://thirdweb.com/">Thirdweb</a> for web3 authentication and minting the NFT</li>
</ul>
<h2 id="heading-how-does-twnft-work">🧐 How does TwNFT work?</h2>
<p>One needs to first sign in with Twitter and then put in the tweet URL for the tweet they want to mint. Before minting, the image that will be minted can also be customized. One needs to assign a name to the NFT and optionally add a description (or else, the tweet's content will be used).</p>
<p>Before minting, we have 2 checks to ensure that the person minting the NFT owns that tweet (this is why they are asked to log in with Twitter) and if the tweet has been minted before or not. We don't allow minting the same tweet multiple times, and this is to ensure that every NFT minted is unique. </p>
<p>Now let us take a deeper dive into the web3 part</p>
<h3 id="heading-a-deeper-dive-into-web3-authentication-with-thirdweb">A deeper dive into web3 authentication with Thirdweb</h3>
<p>Setting up authentication with Thirdweb is as easy as adding ~10 lines of code. I am using the <a target="_blank" href="https://www.npmjs.com/package/@3rdweb/hooks"><code>@3rdweb/hooks</code></a> package for this application and it is a breeze. The <a target="_blank" href="https://www.npmjs.com/package/@3rdweb/react"><code>@3rdweb/react</code></a> package is easier to implement as it also packs in the UI. However, if you want a custom UI (which I wanted), the hooks package is a better choice.</p>
<p>Do note that you need to be using <a target="_blank" href="https://reactjs.org/">ReactJS</a> to use any of the aforementioned packages.</p>
<p>Coming to the code, firstly, you need to add the Thirdweb Provider to the application - </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { ThirdwebWeb3Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@3rdweb/hooks"</span>;

<span class="hljs-keyword">const</span> connectors = {
  <span class="hljs-attr">injected</span>: {},
  <span class="hljs-attr">walletconnect</span>: {},
};

<span class="hljs-keyword">const</span> supportedChainIds = [<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">137</span>, <span class="hljs-number">250</span>, <span class="hljs-number">43114</span>, <span class="hljs-number">80001</span>];

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ThirdwebWeb3Provider</span>
      <span class="hljs-attr">connectors</span>=<span class="hljs-string">{connectors}</span>
      <span class="hljs-attr">supportedChainIds</span>=<span class="hljs-string">{supportedChainIds}</span>
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ThirdwebWeb3Provider</span>&gt;</span></span>
  );
}
</code></pre>
<p>Then, it is as easy of using the <code>useWeb3()</code> hook provided to us by either packages to retrieve the <code>connectWallet</code> function. We have to now just call this function with the wallet type - </p>
<pre><code class="lang-jsx">&lt;button
  onClick={<span class="hljs-function">() =&gt;</span> {
    connectWallet(<span class="hljs-string">"injected"</span>);
  }}
  &gt;
  Login <span class="hljs-keyword">with</span> Metamask
&lt;/button&gt;
</code></pre>
<p>Here <code>injected</code> is for the wallet injected in the browser. This is <a target="_blank" href="https://metamask.io/">Metamask</a> in most cases.</p>
<p>For a detailed guide on implementing web3 authentication with Thirdweb, check out their <a target="_blank" href="https://thirdweb.com/portal/guides/sign-in-with-ethereum-using-thirdweb-connectwallet">official guide</a>.</p>
<h3 id="heading-a-deeper-dive-into-minting-the-nft-with-thirdweb">A deeper dive into minting the NFT with Thirdweb</h3>
<p>I am using the <a target="_blank" href="https://www.npmjs.com/package/@3rdweb/SDK">Thirdweb Typescript SDK</a> for minting the NFT on the server-side. Minting should take place on the server-side for security reasons.</p>
<p>Minting with the Thirdweb SDK is extremely easy. Let us see how it works - </p>
<p>First, we initialize the SDK with our private key. Do keep this secret as anyone who has your private key can gain access to your wallet. I am using <a target="_blank" href="https://www.npmjs.com/package/ethers"><code>ethers</code></a> for initializing a wallet here.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> SDK = <span class="hljs-keyword">new</span> ThirdwebSDK(
  <span class="hljs-keyword">new</span> ethers.Wallet(
    process.env.PRIVATE_KEY,
    ethers.getDefaultProvider(<span class="hljs-string">"https://rinkeby-light.eth.linkpool.io/"</span>)
  )
);
</code></pre>
<p>Next, we initialize the NFT Module that will let us mint the NFT - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> nftModule = sdk.getNFTModule(process.env.NFT_MODULE_ADDRESS);
</code></pre>
<p>At last, we call the <code>mintTo</code> function asynchronously - </p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> nftModule.mintTo(
  payload.receiverAddress,
  nftMedatada
);
</code></pre>
<p>Here the result gives us the NFT's <code>tokenId</code>. The <code>tokenId</code> is a unique identifier for the NFT in that collection. </p>
<p>Now, that is it. We have minted an NFT!!!</p>
<p>For a more detailed guide, you can check out Thirdweb's <a target="_blank" href="https://thirdweb.com/portal/guides/mint-nft-collection-using-typescript-sdk">official guide on minting an NFT</a>.</p>
<h3 id="heading-what-was-this-nft-module">❓ What was this NFT Module?</h3>
<p>Thirdweb provides us with many modules and the NFT Collection module is one of them. It allows you to mint an NFT in an NFT collection and that is what we just did!!! 
The official collection for this project can be found on OpenSea <a target="_blank" href="https://testnets.opensea.io/collection/twnft">here</a>.</p>
<h2 id="heading-using-twnft">🖱️ Using TwNFT</h2>
<p>To get started, log in with Twitter and head over to the <a target="_blank" href="https://twnft.vercel.app/mint">/mint</a> page. </p>
<p>Next, put in the URL to the tweet you want to mint in the textbox on the top and click the arrow -</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643624998718/ui-X376Lf.png" alt="image.png" /></p>
<p>You should now see a preview of the NFT, something a bit like this - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625030434/G2CF1HC0ZQ.png" alt="image.png" /></p>
<p>Feel free to customize the image by clicking on the buttons on the options bar - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625193144/G3fFrq2n1.png" alt="image.png" /></p>
<p>Next, click on the "Connect Wallet" button and connect your Metamask wallet. Walletconnect support is coming soon.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625230012/X2U4fiR74.png" alt="image.png" /></p>
<p>You should now see a button saying "Mint NFT" instead of "Connect Wallet" on the options bar. Clicking that should bring up this modal where you can fill out the name of the NFT, and optionally add a description - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625284907/1qDxme8Wh.png" alt="image.png" /></p>
<p>After some time, you should get the option to go to the tweet page - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625434052/po2NcD7zd.png" alt="image.png" /></p>
<p>The tweet page will look somewhat like this (do give the image some time to load for the first time) - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625465700/gVijzcG5w.png" alt="image.png" /></p>
<p>Notice that it says the NFT is still being minted and it is going to take 5-10 minutes. Check back after 5-10 minutes and this is what the page should look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643625506211/EFaHw8vDeU.png" alt="image.png" /></p>
<p>The NFT is now under your wallet and you can do anything with it, including listing it for sale, selling it, and even transferring it to another wallet. </p>
<h2 id="heading-sidenote">🖊️ Sidenote</h2>
<p>Currently, TwNFT is running on the Rinkeby Test Network. This means all NFTs minted will be on the test network and not on the main network.</p>
<p>However, this is subject to change in the future.</p>
<h2 id="heading-conclusion">✨ Conclusion</h2>
<p>It has been a great journey over the last 13 days making TwNFT, squashing bugs, and implementing features. Excited to see how it goes 😆</p>
<p>Bye, and have a nice day 😁🤞</p>
<h2 id="heading-important-links">🔗 Important Links</h2>
<ul>
<li><a target="_blank" href="https://twnft.vercel.app/">TwNFT</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/twnft">TwNFT GitHub Repository</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/twnft-backend">TwNFT Backend GitHub Repository</a></li>
<li><a target="_blank" href="https://testnets.opensea.io/collection/twnft">TwNFT OpenSea collection</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Make a Back to Top Button and Page Progress Bar with HTML, CSS, and JavaScript]]></title><description><![CDATA[This article was first posted on Freecodecamp on the 21st of December, 2021. Link - https://www.freecodecamp.org/news/back-to-top-button-and-page-progressbar-with-html-css-and-js/

You've probably seen a "back-to-top" button at the bottom-right corne...]]></description><link>https://blog.anishde.dev/how-to-make-a-back-to-top-button-and-page-progress-bar-with-html-css-and-javascript</link><guid isPermaLink="true">https://blog.anishde.dev/how-to-make-a-back-to-top-button-and-page-progress-bar-with-html-css-and-javascript</guid><category><![CDATA[HTML5]]></category><category><![CDATA[CSS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Wed, 22 Dec 2021 05:18:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640144896880/b4sCz6SdF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This article was first posted on Freecodecamp on the 21st of December, 2021. Link - https://www.freecodecamp.org/news/back-to-top-button-and-page-progressbar-with-html-css-and-js/</p>
</blockquote>
<p>You've probably seen a "back-to-top" button at the bottom-right corner on many websites when you're scrolling around. Clicking on that button takes you back to the top of the page.</p>
<p>This is a great feature to have on any website, and today we are going to see how to build it with nothing but HTML, CSS, and JavaScript.</p>
<p>We are also going to look at how to add a page progress bar, one at the top which will increase in progress as we scroll down and decrease as we scroll up.</p>
<p>Note that you can add this to any website, whether it's an existing one or something you have just started working on. The only requirement is that the website should have enough content (or a big enough body height) to be scrollable, or else it will not make sense to add this.</p>
<p>Here is the CodePen of what we are going to build (scroll to see the magic):</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/anishde12020/pen/poWPPoe">https://codepen.io/anishde12020/pen/poWPPoe</a></div>
<h2 id="heading-how-to-make-a-back-to-top-button-for-your-website">How to Make a Back to Top Button for Your Website</h2>
<p>First of all, I am going to make the body of the website huge so that it can be scrolled:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">5000px</span>;
}
</code></pre>
<p>I am also going to add a linear gradient to the document body so that we can know that the document is being scrolled:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">5000px</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(#<span class="hljs-number">00</span>ff04, #<span class="hljs-number">09</span>aad3);
}
</code></pre>
<p>Let's also quickly add the Back To Top button to the markup:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"back-to-top"</span>&gt;</span>Back To Top<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Let's also position the button like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top</span> {
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">bottom</span>: <span class="hljs-number">2rem</span>;
}
</code></pre>
<p>Here, we are giving it a fixed position so that it remains in view even if the document is scrolled. We are pushing it <code>2rem</code> from the bottom and right side of the screen as well.</p>
<p>This is how our document should be looking like now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640147340388/eZ59pcQvi.png" alt="image.png" /></p>
<p>Now, it is time for the fun part – adding the logic.</p>
<h3 id="heading-how-to-only-show-the-back-to-top-button-on-scroll">How to only show the Back To Top button on scroll</h3>
<p>Now, we don't want the Back To Top button to be visible all the time – like when the user is at the top of the page. So we are going to show it conditionally.
For this example, we are only going to show it when the user has scrolled at least 100 pixels.</p>
<p>First of all, we need to hide the button whenever the user opens the site. We also need to make sure that we add this style, separate from the button's base styles, as the button needs to be shown on scroll.</p>
<p>HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"back-to-top hidden"</span>&gt;</span>Back To Top<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.hidden</span> {
  <span class="hljs-attribute">display</span>: none;
}
</code></pre>
<p>Here's the code for conditionally showing the button:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> showOnPx = <span class="hljs-number">100</span>;
<span class="hljs-keyword">const</span> backToTopButton = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".back-to-top"</span>)

<span class="hljs-keyword">const</span> scrollContainer = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">document</span>.documentElement || <span class="hljs-built_in">document</span>.body;
};

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"scroll"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (scrollContainer().scrollTop &gt; showOnPx) {
    backToTopButton.classList.remove(<span class="hljs-string">"hidden"</span>)
  } <span class="hljs-keyword">else</span> {
    backToTopButton.classList.add(<span class="hljs-string">"hidden"</span>)
  }
})
</code></pre>
<p>Here, the <code>scrollContainer</code> function returns <code>document.documentElement</code>, which is nothing but the HTML element of our document. In case that is not available, the <code>document.body</code> element is returned instead.</p>
<p>Next, we are adding an event listener to our document that will trigger the callback function on scroll. The <code>scrollTop</code> (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight">MDN Reference</a>) value that we are getting from the respective <code>scrollContainer</code> is nothing but the number of pixels that element has been scrolled from the top.</p>
<p>Here, when that value is higher than our set <code>showOnPx</code> value, that is <code>100px</code>, we remove the hidden class from our button. If that is not the case, we add the class to the button (especially useful for when the user scrolls up manually).</p>
<p>Now, let's work on the logic to scroll to the top whenever the user clicks the button.</p>
<h3 id="heading-how-to-scroll-to-top-whenever-the-user-clicks-the-back-to-top-button">How to scroll to top whenever the user clicks the Back To Top Button</h3>
<p>Let's quickly write a function for this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> goToTop = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">document</span>.body.scrollIntoView();
};
</code></pre>
<p>The <code>scrollIntoView()</code> (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView">MDN Reference</a>) function scrolls the page to bring the element it is being called upon into view. Here we are calling it on the body so the page will be scrolled to the top.</p>
<p>Now, we need this function to be called whenever the Back To Top Button is clicked:</p>
<pre><code class="lang-js">backToTopButton.addEventListener(<span class="hljs-string">"click"</span>, goToTop)
</code></pre>
<p>That's it! We have successfully added the Back To Top functionality to our website.</p>
<h3 id="heading-how-to-make-the-scroll-smooth">How to make the scroll smooth</h3>
<p>Now, that back to top scroll was quite harsh. Let's look at making it smoother. We can do this by passing in the <code>behaviour</code> as <code>smooth</code> to the <code>scrollIntoView()</code> function.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> goToTop = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">document</span>.body.scrollIntoView({
    <span class="hljs-attr">behavior</span>: <span class="hljs-string">"smooth"</span>,
  });
};
</code></pre>
<p>That's it! Now the scrolling is nice and smooth.</p>
<h3 id="heading-how-to-style-the-back-to-top-button">How to style the Back To Top button</h3>
<p>Right now, the Back To Top button is a simple HTML button with some text – and that looks quite ugly. So let us style it.</p>
<p>Before that, we are going to replace the text with an SVG so let me quickly grab one from <a target="_blank" href="https://heroicons.com/">HeroIcons</a>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"back-to-top hidden"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span>
    <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"back-to-top-icon"</span>
    <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span>
    <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>
    <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span>
      <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span>
      <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span>
      <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span>
      <span class="hljs-attr">d</span>=<span class="hljs-string">"M7 11l5-5m0 0l5 5m-5-5v12"</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>We give the icon a class called <code>back-to-top-icon</code>. This is important as the icon is not visible right away and so needs to be styled in order to be visible.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top-icon</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">color</span>: black;
}
</code></pre>
<p>This is how our button should look now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640147755329/qtFf8nbgS.png" alt="image.png" /></p>
<p>The button still looks quite ugly, so let's style it:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top</span> {
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">bottom</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#141c38</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span>;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<p>Now, the up arrow in our button is not visible, let us change its color to something lighter so that it is visible:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top-icon</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#7ac9f9</span>;
}
</code></pre>
<p>We can also add a hover effect just to make it a tad better:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">60%</span>;
}
</code></pre>
<p>Now, this is how our button should look:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640148509869/3sSxu0osA.png" alt="image.png" /></p>
<h3 id="heading-how-to-make-the-buttons-entry-smoother">How to make the button's entry smoother</h3>
<p>The button seems to appear out of nowhere whenever we scroll. Let's change this behaviour by adding a transition to it and instead of changing the display, we are going to change its opacity:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.back-to-top</span> {
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">bottom</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#7ac9f9</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span>;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">transition</span>: opacity <span class="hljs-number">0.5s</span>;
}
</code></pre>
<pre><code class="lang-css"><span class="hljs-selector-class">.hidden</span> {
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0%</span>;
}
</code></pre>
<p>This also makes our hover effect smoother.</p>
<p>Now let's focus on the page progress bar.</p>
<h2 id="heading-how-to-add-a-page-progress-bar-to-your-website">How to Add a Page Progress Bar to Your Website</h2>
<p>We will be making a progress bar by using a <code>div</code>. As the user scrolls through the page, we will determine the percentage scrolled and keep increasing the <code>width</code>. Let's add the <code>div</code> first and give it a class name of <code>progress-bar</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress-bar"</span> /&gt;</span>
</code></pre>
<p>Now we'll add some styles to it:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.progress-bar</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">background</span>: white;
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
}
</code></pre>
<p>We are making it fixed so that it is visible as the user scrolls. We are also positioning it at the top of the page.</p>
<p>Now, let's add the JavaScript that sets the width of the progress bar:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> pageProgressBar = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".progress-bar"</span>)
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"scroll"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> scrolledPercentage =
      (scrollContainer().scrollTop /
        (scrollContainer().scrollHeight - scrollContainer().clientHeight)) *
      <span class="hljs-number">100</span>;

  pageProgressBar.style.width = <span class="hljs-string">`<span class="hljs-subst">${scrolledPercentage}</span>%`</span>

  <span class="hljs-keyword">if</span> (scrollContainer().scrollTop &gt; showOnPx) {
    backToTopButton.classList.remove(<span class="hljs-string">"hidden"</span>);
  } <span class="hljs-keyword">else</span> {
    backToTopButton.classList.add(<span class="hljs-string">"hidden"</span>);
  }
});
</code></pre>
<p>Note that we are using our existing document scroll event listener function.</p>
<p>This is how our progress bar should look like when scrolled:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640149003520/z78kGlhLi.png" alt="image.png" /></p>
<h3 id="heading-how-to-calculate-the-percentage-scrolled">How to calculate the percentage scrolled</h3>
<p>Calculating percentage scrolled is actually quite simple. The <code>scrollTop</code> (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight">MDN Reference</a>) property is the number of pixels scrolled as mentioned earlier.</p>
<p><code>scrollHeight</code> (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight">MDN Reference</a>) is the minimum height required to fit in all its children in the element it is being called upon.</p>
<p>And finally, <code>clientHeight</code> (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight">MDN Reference</a>) is the inner height of the element it is being called upon.</p>
<p>The <code>clientHeight</code> is subtracted from the <code>scrollHeight</code> because if we don't do that, the area visible will be taken into account as well so we would never hit 100% scrolled.</p>
<p>I have put together this diagram to explain it better:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640149482325/gtpVcM6pG.png" alt="image.png" /></p>
<p>Here, the line without the arrows represents the <code>clientHeight</code> which is the height of the content visible to us. The line with the arrows represents the <code>scrollHeight</code> and shows that this line continues in both directions. This is the height of the view required to fit in all the content.</p>
<p>At last, the <code>scrollTop</code> value is divided by the difference of <code>scrollHeight</code> and <code>clientHeight</code> and we get a decimal value of the amount scrolled. This is multiplied by <code>100</code> to get the value in percentage that we use to determine the width of the <code>div</code>, that is the progress on our progress bar.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you have found this article helpful and are able to implement a Back To Top Button and a Page Progress Bar on your website.</p>
<p>Do reach out to me on <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a> if you want to ask me anything. The next step would be to implement this on your website and make changes as you see fit.</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><a target="_blank" href="https://codepen.io/anishde12020/pen/poWPPoe">CodePen for this example</a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView">MDN Reference for <code>scrollIntoView()</code></a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop">MDN Reference for <code>scrollTop</code></a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight">MDN Reference for <code>scrollHeight</code></a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight">MDN Reference for <code>clientHeight</code></a></li>
</ul>
<blockquote>
<p>This article was first posted on Freecodecamp on the 21st of December, 2021. Link - https://www.freecodecamp.org/news/back-to-top-button-and-page-progressbar-with-html-css-and-js/</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[How to Use the .github Repository]]></title><description><![CDATA[Note: This article was first published in the Freecodecamp publication on the 15th of December, 2021. The link to the original article - https://www.freecodecamp.org/news/how-to-use-the-dot-github-repository/

GitHub has many special repositories. Fo...]]></description><link>https://blog.anishde.dev/how-to-use-the-github-repository</link><guid isPermaLink="true">https://blog.anishde.dev/how-to-use-the-github-repository</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Thu, 16 Dec 2021 04:03:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639627184519/Yqo5BERsU.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Note: This article was first published in the Freecodecamp publication on the 15th of December, 2021. The link to the original article - https://www.freecodecamp.org/news/how-to-use-the-dot-github-repository/</p>
</blockquote>
<p><strong>GitHub has many special repositories. For instance, you can create a repository that matches your username, add a README file to it, and all the information in that file will be visible on your GitHub profile.</strong></p>
<p>You might already be familiar with the <code>.github</code> directory you'll find in many repositories. The <code>.github</code> directory houses workflows, issue templates, pull request templates, funding information, and some other files specific to that project.</p>
<p>But another special repository you can create is the <code>.github</code> repository. It acts as a fallback for all of your repositories that don't have an actual <code>.github</code> directory with issue templates and other community health files.</p>
<p>For example, say I have a repository named <code>.github</code> with generic bug report and feature request issue templates. And say I create another repository called <code>new-project</code>, but I don't add a <code>.github</code> directory with issue templates to it.</p>
<p>Then if someone goes to the <code>new-project</code> repo and opens an issue, they'll be presented with an option to choose from the generic templates already in the <code>.github</code> directory.</p>
<p>Similarly, if I add a code of conduct to my <code>.github</code> repository, it will be shown across all my repositories that don't explicitly have one.</p>
<p>Just note that the files inside a repository's <code>.github</code> directory will be chosen over the ones in the <code>.github</code> directory. For example, if my new-project repo has a <code>.github</code> directory with a feature request issue template inside, that will be used instead of the generic feature request template from the <code>.github</code> repo.</p>
<p>Let's see how this special repository works in action.</p>
<p>Now let us see this in action</p>
<h2 id="heading-how-to-use-github-on-personal-github-accounts">How to Use .github on Personal GitHub Accounts</h2>
<p>Creating this special repository is as easy as creating any other repository on GitHub. So go ahead and open GitHub on your web browser and create the repository like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639469936665/Xo__mfEdt.png" alt="image.png" /></p>
<p>After you're done creating the repository, you can start adding files to it. The first file I will add is a bug report issue form. I am not going to go over the details of creating an issue form in this article, but you can have a look at a <a target="_blank" href="https://blog.anishde.dev/creating-a-bug-report-form-in-github">previous article I wrote about GitHub Issue forms.</a></p>
<p><code>.github/ISSUE_TEMPLATE/bug_report.yml</code></p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">🐛Bug</span> <span class="hljs-string">Report</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">File</span> <span class="hljs-string">a</span> <span class="hljs-string">bug</span> <span class="hljs-string">report</span> <span class="hljs-string">here</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">"[BUG]: "</span>
<span class="hljs-attr">labels:</span> [<span class="hljs-string">"bug"</span>]
<span class="hljs-attr">assignees:</span> [<span class="hljs-string">"AnishDe12020"</span>]
<span class="hljs-attr">body:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">markdown</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">|
        Thanks for taking the time to fill out this bug report 🤗
        Make sure there aren't any open/closed issues for this topic 😃
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">textarea</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">bug-description</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">label:</span> <span class="hljs-string">Description</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">bug</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Give</span> <span class="hljs-string">us</span> <span class="hljs-string">a</span> <span class="hljs-string">brief</span> <span class="hljs-string">description</span> <span class="hljs-string">of</span> <span class="hljs-string">what</span> <span class="hljs-string">happened</span> <span class="hljs-string">and</span> <span class="hljs-string">what</span> <span class="hljs-string">should</span> <span class="hljs-string">have</span> <span class="hljs-string">happened</span>
    <span class="hljs-attr">validations:</span>
      <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">textarea</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">steps-to-reproduce</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">label:</span> <span class="hljs-string">Steps</span> <span class="hljs-string">To</span> <span class="hljs-string">Reproduce</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Steps</span> <span class="hljs-string">to</span> <span class="hljs-string">reproduce</span> <span class="hljs-string">the</span> <span class="hljs-string">behavior.</span>
      <span class="hljs-attr">placeholder:</span> <span class="hljs-string">|
        1. Go to '...'
        2. Click on '...'
        3. Scroll down to '...'
        4. See error
</span>    <span class="hljs-attr">validations:</span>
      <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">textarea</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">additional-information</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">label:</span> <span class="hljs-string">Additional</span> <span class="hljs-string">Information</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">|</span>
        <span class="hljs-string">Provide</span> <span class="hljs-string">any</span> <span class="hljs-string">additional</span> <span class="hljs-string">information</span> <span class="hljs-string">such</span> <span class="hljs-string">as</span> <span class="hljs-string">logs,</span> <span class="hljs-string">screenshots,</span> <span class="hljs-string">likes,</span> <span class="hljs-string">scenarios</span> <span class="hljs-string">in</span> <span class="hljs-string">which</span> <span class="hljs-string">the</span> <span class="hljs-string">bug</span> <span class="hljs-string">occurs</span> <span class="hljs-string">so</span> <span class="hljs-string">that</span> <span class="hljs-string">it</span> <span class="hljs-string">facilitates</span> <span class="hljs-string">resolving</span> <span class="hljs-string">the</span> <span class="hljs-string">issue.</span>
</code></pre>
<p>I am also going to create a feature request form.</p>
<p><code>.github/ISSUE_TEMPLATE/feature_request.yml</code></p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">✨Feature</span> <span class="hljs-string">Request</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Request</span> <span class="hljs-string">a</span> <span class="hljs-string">new</span> <span class="hljs-string">feature</span> <span class="hljs-string">or</span> <span class="hljs-string">enhancement</span>
<span class="hljs-attr">labels:</span> [<span class="hljs-string">"enhancement"</span>]
<span class="hljs-attr">title:</span> <span class="hljs-string">"[FEAT]: "</span>
<span class="hljs-attr">body:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">markdown</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">|
        Please make sure this feature request hasn't been already submitted by someone by looking through other open/closed issues
</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">textarea</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">description</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">label:</span> <span class="hljs-string">Description</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Give</span> <span class="hljs-string">us</span> <span class="hljs-string">a</span> <span class="hljs-string">brief</span> <span class="hljs-string">description</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">or</span> <span class="hljs-string">enhancement</span> <span class="hljs-string">you</span> <span class="hljs-string">would</span> <span class="hljs-string">like</span>
    <span class="hljs-attr">validations:</span>
      <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">textarea</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">additional-information</span>
    <span class="hljs-attr">attributes:</span>
      <span class="hljs-attr">label:</span> <span class="hljs-string">Additional</span> <span class="hljs-string">Information</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Give</span> <span class="hljs-string">us</span> <span class="hljs-string">some</span> <span class="hljs-string">additional</span> <span class="hljs-string">information</span> <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">request</span> <span class="hljs-string">like</span> <span class="hljs-string">proposed</span> <span class="hljs-string">solutions,</span> <span class="hljs-string">links,</span> <span class="hljs-string">screenshots,</span> <span class="hljs-string">etc.</span>
</code></pre>
<p>I am also going to be adding a pull request template.</p>
<p><code>.github/pull_request_template.md</code></p>
<pre><code class="lang-md"><span class="xml"><span class="hljs-comment">&lt;!-- 
Thanks for creating this pull request 🤗

Please make sure that the pull request is limited to one type (docs, feature, etc.) and keep it as small as possible. You can open multiple prs instead of opening a huge one.
--&gt;</span></span>

<span class="xml"><span class="hljs-comment">&lt;!-- If this pull request closes an issue, please mention the issue number below --&gt;</span></span>
Closes # <span class="xml"><span class="hljs-comment">&lt;!-- Issue # here --&gt;</span></span>

<span class="hljs-section">## 📑 Description</span>
<span class="xml"><span class="hljs-comment">&lt;!-- Add a brief description of the pr --&gt;</span></span>

<span class="xml"><span class="hljs-comment">&lt;!-- You can also choose to add a list of changes and if they have been completed or not by using the markdown to-do list syntax
- [ ] Not Completed
- [x] Completed
--&gt;</span></span>

<span class="hljs-section">## ✅ Checks</span>
<span class="xml"><span class="hljs-comment">&lt;!-- Make sure your pr passes the CI checks and do check the following fields as needed - --&gt;</span></span>
<span class="hljs-bullet">-</span> [ ] My pull request adheres to the code style of this project
<span class="hljs-bullet">-</span> [ ] My code requires changes to the documentation
<span class="hljs-bullet">-</span> [ ] I have updated the documentation as required
<span class="hljs-bullet">-</span> [ ] All the tests have passed

<span class="hljs-section">## ℹ Additional Information</span>
<span class="xml"><span class="hljs-comment">&lt;!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. --&gt;</span></span>
</code></pre>
<p>The last file I am going to be adding is a code of conduct – but this is going to be on the root of the repository. Despite that, this will work as intended (code of conduct files are usually kept on the root of the repository). Note that I am using the <a target="_blank" href="https://www.contributor-covenant.org/">Contributor Convent</a> convention.</p>
<p><code>CODE_OF_CONDUCT.md</code> - </p>
<pre><code class="lang-md">
<span class="hljs-section"># Contributor Covenant Code of Conduct</span>

<span class="hljs-section">## Our Pledge</span>

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

<span class="hljs-section">## Our Standards</span>

Examples of behavior that contributes to a positive environment for our
community include:

<span class="hljs-bullet">*</span> Demonstrating empathy and kindness toward other people
<span class="hljs-bullet">*</span> Being respectful of differing opinions, viewpoints, and experiences
<span class="hljs-bullet">*</span> Giving and gracefully accepting constructive feedback
<span class="hljs-bullet">*</span> Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
<span class="hljs-bullet">*</span> Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

<span class="hljs-bullet">*</span> The use of sexualized language or imagery, and sexual attention or advances of
  any kind
<span class="hljs-bullet">*</span> Trolling, insulting or derogatory comments, and personal or political attacks
<span class="hljs-bullet">*</span> Public or private harassment
<span class="hljs-bullet">*</span> Publishing others' private information, such as a physical or email address,
  without their explicit permission
<span class="hljs-bullet">*</span> Other conduct which could reasonably be considered inappropriate in a
  professional setting

<span class="hljs-section">## Enforcement Responsibilities</span>

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

<span class="hljs-section">## Scope</span>

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

<span class="hljs-section">## Enforcement</span>

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

<span class="hljs-section">## Enforcement Guidelines</span>

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

<span class="hljs-section">### 1. Correction</span>

<span class="hljs-strong">**Community Impact**</span>: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

<span class="hljs-strong">**Consequence**</span>: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

<span class="hljs-section">### 2. Warning</span>

<span class="hljs-strong">**Community Impact**</span>: A violation through a single incident or series of
actions.

<span class="hljs-strong">**Consequence**</span>: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

<span class="hljs-section">### 3. Temporary Ban</span>

<span class="hljs-strong">**Community Impact**</span>: A serious violation of community standards, including
sustained inappropriate behavior.

<span class="hljs-strong">**Consequence**</span>: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

<span class="hljs-section">### 4. Permanent Ban</span>

<span class="hljs-strong">**Community Impact**</span>: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

<span class="hljs-strong">**Consequence**</span>: A permanent ban from any sort of public interaction within the
community.

<span class="hljs-section">## Attribution</span>

This Code of Conduct is adapted from the [<span class="hljs-string">Contributor Covenant</span>][<span class="hljs-symbol">homepage</span>],
version 2.1, available at
[<span class="hljs-string">https://www.contributor-covenant.org/version/2/1/code_of_conduct.html</span>][<span class="hljs-symbol">v2.1</span>].

Community Impact Guidelines were inspired by
[<span class="hljs-string">Mozilla's code of conduct enforcement ladder</span>][<span class="hljs-symbol">Mozilla CoC</span>].

For answers to common questions about this code of conduct, see the FAQ at
[<span class="hljs-string">https://www.contributor-covenant.org/faq</span>][<span class="hljs-symbol">FAQ</span>]. Translations are available at
[<span class="hljs-string">https://www.contributor-covenant.org/translations</span>][<span class="hljs-symbol">translations</span>].

[<span class="hljs-symbol">homepage</span>]: <span class="hljs-link">https://www.contributor-covenant.org</span>
[<span class="hljs-symbol">v2.1</span>]: <span class="hljs-link">https://www.contributor-covenant.org/version/2/1/code_of_conduct.html</span>
[<span class="hljs-symbol">Mozilla CoC</span>]: <span class="hljs-link">https://github.com/mozilla/diversity</span>
[<span class="hljs-symbol">FAQ</span>]: <span class="hljs-link">https://www.contributor-covenant.org/faq</span>
[<span class="hljs-symbol">translations</span>]: <span class="hljs-link">https://www.contributor-covenant.org/translations</span>
</code></pre>
<p>We can add more files like funding information, contributing guides, and much more. For more information, you can look at the <a target="_blank" href="https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/creating-a-default-community-health-file">GitHub docs regarding community health files</a></p>
<h3 id="heading-the-github-repository-in-action">The <code>.github</code> repository in action</h3>
<p>My <a target="_blank" href="https://github.com/AnishDe12020/blog">blogs repository</a> doesn't have any issue templates, code of conduct, or any other file except for the markdown files of my blogs and a README. So it's the best repository to test upon if this feature is working or not.</p>
<p>I can already see the code of conduct appearing here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639473735984/4Dk1gl1ZS.png" alt="image.png" /></p>
<p>If I try to create an issue, I am presented with the templates as well:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639473797715/5fqH-4IYX.png" alt="image.png" /></p>
<p>This will also work when creating a pull request.</p>
<h2 id="heading-how-to-use-the-github-repository-for-an-organizationpublic-account">How to Use the .github Repository for an Organization/Public Account</h2>
<p>The <code>.github</code> repository on an organization account works just like the <code>.github</code> repository on a personal GitHub account – except there is one difference.</p>
<p>Organizations can also have profile READMEs that show up on the organization page on GitHub. This README resides on the <code>profile</code> directory of the organization's <code>.github</code> repository. To demonstrate this, I will quickly create a demo organization.</p>
<p>When creating the <code>.github</code> repository for an organization, you should get this message:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639473052293/s2QEAhtHG.png" alt="image.png" /></p>
<p>Also when adding the profile README to <code>profile/README.md</code>, you should be getting this message:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639473117849/vf0IEmbTH.png" alt="image.png" /></p>
<p>Now, I am going to add some content to that README file and commit it. When I visit the organization's home page this is what we should see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639473242631/svqbJ3PfG.png" alt="image.png" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you now know what the <code>.github</code> repository does. You should also know how to set up default community health files for your repositories and a profile README for your organization.</p>
<p>Feel free to reach out to me on Twitter and have a nice day 😃</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><a target="_blank" href="https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/creating-a-default-community-health-file">GitHub Documentation on Community Health Files</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020/.github">My <code>.github</code> repository</a></li>
<li><a target="_blank" href="https://github.com/AnishDe12020-test/.github">My test organization's <code>.github</code> repository</a></li>
<li><a target="_blank" href="https://www.contributor-covenant.org/">Contributor Convent</a></li>
<li><a target="_blank" href="https://blog.anishde.dev/creating-a-bug-report-form-in-github">Article on getting started with GitHub issue forms</a></li>
</ul>
<p>I am currently working on a project called DevKit which is a PWA that will house developer tools in one single application and provide ways to get your work done quickly. Do check it out at <a target="_blank" href="https://www.devkit.one/">https://www.devkit.one/.</a></p>
<blockquote>
<p>Note: This article was first published in the Freecodecamp publication on the 15th of December, 2021. The link to the original article - https://www.freecodecamp.org/news/how-to-use-the-dot-github-repository/</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Adding an in-browser code preview to your React Application with Sandpack]]></title><description><![CDATA[Sandpack is a live coding environment that runs on the browser. It is made by the team behind CodeSandbox. The main objective here is to provide interactive examples to play around with, to users. I see it being widely used in things like blog posts ...]]></description><link>https://blog.anishde.dev/adding-an-in-browser-code-preview-to-your-react-application-with-sandpack</link><guid isPermaLink="true">https://blog.anishde.dev/adding-an-in-browser-code-preview-to-your-react-application-with-sandpack</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[#codenewbies]]></category><category><![CDATA[documentation]]></category><category><![CDATA[Libraries]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sat, 11 Dec 2021 13:18:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639228519537/gLvn_u4gu.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://sandpack.codesandbox.io/">Sandpack</a> is a live coding environment that runs on the browser. It is made by the team behind <a target="_blank" href="https://codesandbox.io/">CodeSandbox</a>. The main objective here is to provide interactive examples to play around with, to users. I see it being widely used in things like blog posts and documentation (in fact the, work in progress, <a target="_blank" href="https://beta.reactjs.org/learn">new React Docs</a> is using Sandpack). In this article, we are going to look at how to add Sandpack to a React Application and then we will look at integrating it with <a target="_blank" href="https://github.com/hashicorp/next-mdx-remote">Next MDX Remote</a> in a NextJS Application.</p>
<h2 id="heading-adding-sandpack-to-our-project">Adding Sandpack to our Project</h2>
<p>We are going to be adding Sandpack to a react application (made with <a target="_blank" href="https://github.com/facebook/create-react-app">create react app</a>) though the process should be quite the same for NextJS or Gatsby. </p>
<p>Create a starter react project and navigate into it - </p>
<pre><code class="lang-sh">npx create-react-app sandpack-demo
<span class="hljs-built_in">cd</span> sandpack-demo
</code></pre>
<blockquote>
<p>Note: Feel free to use the Yarn package manager if that is what you prefer.</p>
</blockquote>
<p>Now, let us install Sandpack</p>
<pre><code class="lang-sh">npm install @codesandbox/sandpack-react
</code></pre>
<p>That is it for dependencies, now let us move on to adding Sandpack to the application.</p>
<p>Go ahead and delete <code>App.css</code>,  <code>App.test.js</code>, <code>setupTests.js</code>, and <code>logo.svg</code>. Also remove all the boilerplate code in <code>App.js</code>. It should look like this - </p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Now, let us import Sandpack in <code>App.js</code> - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Sandpack } <span class="hljs-keyword">from</span> <span class="hljs-string">"@codesandbox/sandpack-react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@codesandbox/sandpack-react/dist/index.css"</span>;
</code></pre>
<p>Here, we are also importing a CSS file that contains the styles for the editor and preview.</p>
<p>We should also add the Sandpack component - </p>
<pre><code class="lang-js">&lt;Sandpack /&gt;
</code></pre>
<p>That is it!!! Now let us start the dev server by running <code>npm start</code>. Navigate to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> and this is what you should see - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639209440921/6ANlUGyU7.png" alt="image.png" /></p>
<h3 id="heading-custom-templates">Custom Templates</h3>
<p>The default template that Sandpack uses is vanilla js but we can also use other templates like react, vue, angular, etc. Let us see the react template in action. Just add the <code>template</code> attribute and specify the value as <code>react</code> - </p>
<pre><code class="lang-js">&lt;Sandpack template=<span class="hljs-string">"react"</span> /&gt;
</code></pre>
<p>Feel free to go through the <a target="_blank" href="https://sandpack.codesandbox.io/docs/getting-started/custom-content">Sandpack Custom Content documentation</a> for more templates and information on how to add your custom code.</p>
<h3 id="heading-custom-theme">Custom Theme</h3>
<p>We can also customize the theme. Let us look at adding a pre-built theme - </p>
<pre><code class="lang-js">&lt;Sandpack template=<span class="hljs-string">"react"</span> theme=<span class="hljs-string">"sandpack-dark"</span> /&gt;
</code></pre>
<p>This is how the editor should look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639210107545/AhqKN0slh.png" alt="image.png" /></p>
<p>Feel free to go through the <a target="_blank" href="https://sandpack.codesandbox.io/docs/getting-started/custom-ui">Sandpack Custom UI documentation</a> for more themes and information on building your theme.</p>
<p>At last, this is how our <code>App.js</code> looks like - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Sandpack } <span class="hljs-keyword">from</span> <span class="hljs-string">"@codesandbox/sandpack-react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@codesandbox/sandpack-react/dist/index.css"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Sandpack</span> <span class="hljs-attr">template</span>=<span class="hljs-string">"react"</span> <span class="hljs-attr">theme</span>=<span class="hljs-string">"sandpack-dark"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Now, that was just getting started with Sandpack but now let us look at it being used in a more real world example. Feel free to go through the <a target="_blank" href="https://sandpack.codesandbox.io/docs/">Sandpack documentation</a> for more detailed guides and an API reference.</p>
<h2 id="heading-using-sandpack-with-next-mdx-remote">Using Sandpack with Next MDX Remote</h2>
<p><a target="_blank" href="https://github.com/hashicorp/next-mdx-remote">Next MDX Remote</a> is a library that parses MDX content (markdown but with support for JSX as well) and helps load them via <code>getStaticProps</code> or <code>getServersideProps</code> in NextJS. It is mainly used for documentation and blog posts. Today, we are going to be adding Next MDX Remote to a NextJS application and customize the code component by replacing it with Sandpack. First of all, let us make a new NextJS application and navigate into it - </p>
<pre><code class="lang-sh">npx create-next-app sandpack-next-mdx-remote
<span class="hljs-built_in">cd</span> sandpack-next-mdx-remote
</code></pre>
<p>Now, let us delete <code>Home.module.css</code> under the <code>styles</code> directory and remove the boilerplate code in <code>index.js</code> under the <code>pages</code> directory. This is how it should look like - </p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
}
</code></pre>
<h3 id="heading-adding-next-mdx-remote">Adding Next MDX Remote</h3>
<p>The next step is to add and setup Next MDX Remote so let us do that - </p>
<pre><code class="lang-sh">npm install next-mdx-remote
</code></pre>
<p>Now, let us go to <code>index.js</code> and add the following code - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote/serialize"</span>;
<span class="hljs-keyword">import</span> { MDXRemote } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params">{ source }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">MDXRemote</span> {<span class="hljs-attr">...source</span>} /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> source = <span class="hljs-string">"```html\n&lt;h1&gt;Hello World&lt;/h1&gt;\n```"</span>;

  <span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> serialize(source);

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">props</span>: { <span class="hljs-attr">source</span>: mdxSource } };
};
</code></pre>
<p>Note that I am just writing down some basic markdown with a code block. Usually, this markdown is sourced from external files and paired with frontmatter but that is not something I am going to go over in this article.</p>
<p>Now let us start the development server by running <code>npm run dev</code>. Upon navigating to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a>, this is what our page should look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639212161614/eLQBXGSiy.png" alt="image.png" /></p>
<p>Note that a simple HTML <code>code</code> element is being rendered now</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639212200694/GbuCJJR00.png" alt="image.png" /></p>
<p>Now, I could add syntax highlighting to this using <a target="_blank" href="https://www.npmjs.com/package/remark-prism">remark prism</a> but as we are anyways going to use Sandpack, let us move onto that instead.</p>
<h3 id="heading-adding-sandpack-to-next-mdx-remote">Adding Sandpack to Next MDX Remote</h3>
<p>First of all, let us install the Sandpack package - </p>
<pre><code class="lang-sh">npm install @codesandbox/sandpack-react
</code></pre>
<p>Now let us create a directory called <code>components</code> and add a file named <code>CustomMDXCode.js</code> in there. Add the following code to that file - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Sandpack } <span class="hljs-keyword">from</span> <span class="hljs-string">"@codesandbox/sandpack-react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@codesandbox/sandpack-react/dist/index.css"</span>;

<span class="hljs-keyword">const</span> CustomMDXCode = <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Sandpack</span>
      <span class="hljs-attr">template</span>=<span class="hljs-string">{props.template}</span>
      <span class="hljs-attr">files</span>=<span class="hljs-string">{{</span> [`/${<span class="hljs-attr">props.filename</span>}`]<span class="hljs-attr">:</span> <span class="hljs-attr">props.children</span> }}
    /&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> CustomMDXCode;
</code></pre>
<p>Here, we are importing Sandpack, making a custom component, which is passed in some props. These props will contain the filename of the file, the template to use, and of course, the code. Note that we are adding a <code>/</code> to the beginning of the filename through string interpolation as it is required by Sandpack. </p>
<p>Now, let us go back to our <code>index.js</code> file and make some changes to leverage the use of the new component - </p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote/serialize"</span>;
<span class="hljs-keyword">import</span> { MDXRemote } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-mdx-remote"</span>;
<span class="hljs-keyword">import</span> CustomMDXCode <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/CustomMDXCode"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params">{ source }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">MDXRemote</span>
        <span class="hljs-attr">components</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">code:</span> <span class="hljs-attr">props</span> =&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">CustomMDXCode</span> {<span class="hljs-attr">...props</span>} /&gt;</span> }}
        {...source}
      /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getStaticProps = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> source =
    <span class="hljs-string">"```js template=react filename=App.js\nexport default function App() {\n  return &lt;h1&gt;Just some text...&lt;/h1&gt;\n}\n```"</span>;

  <span class="hljs-keyword">const</span> mdxSource = <span class="hljs-keyword">await</span> serialize(source);

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">props</span>: { <span class="hljs-attr">source</span>: mdxSource } };
};
</code></pre>
<p>Here, we are adding a custom component for the code attribute (reference for all mdx components - <a target="_blank" href="https://mdxjs.com/table-of-components/">https://mdxjs.com/table-of-components/</a>), which is nothing but the Sandpack component we created earlier. We have also changed the markdown source to <code>javascript</code>, added a <code>template</code> attribute and pointed that to <code>react</code>, added a <code>filename</code> attribute and named the file <code>App.js</code>, and wrote a simple function that displays some text for the code part.</p>
<p>This is how our page should look like now - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639214269118/VUgIybb6A6.png" alt="image.png" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That is it for this article. I hope you enjoyed it and learned how to add Sandpack to your react application. Feel free to comment on this post or reach out to me via <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a> in case you have any questions.</p>
<h2 id="heading-links">Links</h2>
<p>Sandpack - <a target="_blank" href="https://sandpack.codesandbox.io/">https://sandpack.codesandbox.io/</a></p>
<p>Sanpack Documentation - <a target="_blank" href="https://sandpack.codesandbox.io/docs/">https://sandpack.codesandbox.io/docs/</a></p>
<p>Sandpack GitHub - <a target="_blank" href="https://github.com/codesandbox/sandpack">https://github.com/codesandbox/sandpack</a></p>
<p>Next MDX Remote - <a target="_blank" href="https://github.com/hashicorp/next-mdx-remote">https://github.com/hashicorp/next-mdx-remote</a></p>
<p>All MDX Component - <a target="_blank" href="https://mdxjs.com/table-of-components/">https://mdxjs.com/table-of-components/</a></p>
]]></content:encoded></item><item><title><![CDATA[Make a toast with HTML, CSS, and JS]]></title><description><![CDATA[Introduction
Toasts are very useful for showing users some information. It has a wide variety of uses from displaying success messages for successful actions, showing error messages in case something goes wrong, and much more. Today we are going to b...]]></description><link>https://blog.anishde.dev/make-a-toast-with-html-css-and-js</link><guid isPermaLink="true">https://blog.anishde.dev/make-a-toast-with-html-css-and-js</guid><category><![CDATA[HTML5]]></category><category><![CDATA[CSS]]></category><category><![CDATA[CSS Animation]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[HTML]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Sun, 05 Dec 2021 09:55:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1638697970037/zAYKG5UvP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Toasts are very useful for showing users some information. It has a wide variety of uses from displaying success messages for successful actions, showing error messages in case something goes wrong, and much more. Today we are going to build a simple toast with HTML and CSS. We are going to be using some javascript to add some interactivity.</p>
<h3 id="heading-what-we-are-making">What we are making -</h3>
<p>We are going to be making a toast that shows up when a button is clicked. It can also be closed, which is hidden away, by clicking a close button.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/anishde12020/pen/JjrYMrW">https://codepen.io/anishde12020/pen/JjrYMrW</a></div>
<h2 id="heading-basic-css-to-make-a-toast">Basic CSS to make a toast</h2>
<p>To make a toast animate in or out, we need to make the toast first. For this example, I am going to add a simple icon and some text in a box and that is going to be our toast.</p>
<p>So, in the markup, let us start by adding a <code>div</code> for out toast - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"toast"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toast"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Now, we need to add an icon. I am going to grab a simple information icon from <a target="_blank" href="https://heroicons.com/">HeroIcons</a> and put in the SVG - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"toast"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toast"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Let us also add a text - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"toast"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toast"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text"</span>&gt;</span>Some Information<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>This is what our page should look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638691425468/rYVyqWbLO.png" alt="image.png" /></p>
<p>The icon is so big that is doesn't even fit in the view. Let us fix this design with some CSS and then style it.</p>
<p>First, we are going to style the icon by defining a width and a height - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.icon</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">2rem</span>;
}
</code></pre>
<p>Let us now make our toast a flexbox and add some margin on the icon. I am also going to position the toast on the top-right using an absolute position.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.icon</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.toast</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50px</span>;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">80px</span>;
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638691746319/Xe8uqB4fX.png" alt="image.png" /></p>
<p>Everything looks good except for the styling. Let us add some colors and other styles - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.icon</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.text</span> {
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.toast</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50px</span>;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">80px</span>;
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">5px</span> solid <span class="hljs-number">#029c91</span>;
}
</code></pre>
<p>We have changed the background color of the toast, added a border to it, added some border radius, and changed the colors of the icon and the text so that they are visible on the black background.</p>
<p>This is how our toast should now look like - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638691938348/ZVHA6pQDM.png" alt="image.png" /></p>
<p>Let us also add a button that will trigger the animation, that is, show the toast - </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"toast"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toast"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text"</span>&gt;</span>Some Information<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"show-toast"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"show-toast"</span>&gt;</span>Show Toast<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Let us also style this button as it looks quite ugly now</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.show-toast</span> {
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<p>Let us also disable any overflow - </p>
<pre><code class="lang-css"><span class="hljs-selector-tag">html</span>,
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">overflow</span>: hidden;
}
</code></pre>
<p>This is how everything should look like now - </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638692227596/Bp9zQYJd3.png" alt="image.png" /></p>
<h2 id="heading-adding-animations">Adding animations</h2>
<p>Now that we have the toast and a button to trigger the animations, it is time to add the animations.</p>
<p>First of all, we are going to give the toast a starting point by putting it outside the view. So let us edit the CSS for the toast - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.toast</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50px</span>;
  <span class="hljs-attribute">right</span>: -<span class="hljs-number">500px</span>;
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">5px</span> solid <span class="hljs-number">#029c91</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0%</span>;
}
</code></pre>
<p>Now let us make a new class called <code>toast-active</code> that will get added to the toast whenever the button is clicked - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.toast-active</span> {
  <span class="hljs-attribute">right</span>: <span class="hljs-number">80px</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">100%</span>;
}
</code></pre>
<p>Notice that we are also changing the opacity during the transition. This just makes it look a little better.</p>
<p>Now let us write some javascript to add this class to the toast whenever the button is clicked - </p>
<pre><code class="lang-js"><span class="hljs-keyword">let</span> toast = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"toast"</span>);
<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"show-toast"</span>).addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  toast.classList.add(<span class="hljs-string">"toast-active"</span>)
});
</code></pre>
<p>Here, whenever the button is clicked, the <code>toast-active</code> class is being added to the toast. Right now the animation is instant, which doesn't look good. Let us add a transition - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.toast</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50px</span>;
  <span class="hljs-attribute">right</span>: -<span class="hljs-number">500px</span>;
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">5px</span> solid <span class="hljs-number">#029c91</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0%</span>;
  <span class="hljs-attribute">transition</span>: all <span class="hljs-number">0.25s</span> ease-out;
}
</code></pre>
<p>Here the transition goes on for a quarter of a second and we have also eased it out so it isn't harsh.</p>
<h2 id="heading-adding-a-close-button-to-the-toast">Adding a close button to the toast</h2>
<p>We would like to give the user a close button that they can click to close the toast.</p>
<p>First of all, we need to add a button the the toast in our markup -</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"toast"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"toast"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text"</span>&gt;</span>Some Information<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"close-button"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"close-button"</span>&gt;</span>
    <span class="hljs-symbol">&amp;#10005;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"show-toast"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"show-toast"</span>&gt;</span>Show Toast<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>Let us also style it so that it is visible - </p>
<pre><code class="lang-css"><span class="hljs-selector-class">.close-button</span> {
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<p>Now, when this button will be clicked, it will just do the reverse of what the show toast button did, that is, remove the <code>toast-active</code> class - </p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"close-button"</span>).addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  toast.classList.remove(<span class="hljs-string">"toast-active"</span>);
})
</code></pre>
<p>Now, clicking the cross symbol (close button) in the toast should take it away from the screen with a transition.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If everything has worked out well so far, give yourself a pat on the back because you have just built a toast with nothing but HTML, CSS, and JS!!! </p>
<p>If you had any issues, feel free to comment down below or reach out to me via <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a>.</p>
<h2 id="heading-links">Links</h2>
<p>Codepen for this project - https://codepen.io/anishde12020/pen/JjrYMrW</p>
<p>HeroIcons - https://heroicons.com/</p>
<p>My Twitter - https://twitter.com/AnishDe12020</p>
]]></content:encoded></item><item><title><![CDATA[How to Write Good Commit Messages with Commitlint]]></title><description><![CDATA[This article was first published on Freecodecamp on the 13th of November, 2021 - https://www.freecodecamp.org/news/how-to-use-commitlint-to-write-good-commit-messages/

We are often in a hurry to commit our changes in Git and so we write something ra...]]></description><link>https://blog.anishde.dev/how-to-write-good-commit-messages-with-commitlint</link><guid isPermaLink="true">https://blog.anishde.dev/how-to-write-good-commit-messages-with-commitlint</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[ci]]></category><dc:creator><![CDATA[Anish De]]></dc:creator><pubDate>Mon, 22 Nov 2021 08:39:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1637567563481/O4DnwuzYH.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This article was first published on Freecodecamp on the 13th of November, 2021 - https://www.freecodecamp.org/news/how-to-use-commitlint-to-write-good-commit-messages/</p>
</blockquote>
<p>We are often in a hurry to commit our changes in Git and so we write something random in our commit messages. In fact, I have seen people putting the date and time or even something like <code>commit 1</code>, <code>commit 2</code> in their messages.</p>
<p>This is not a good practice, as commit messages should be helpful and make sense so that the people working on the project, reading the code, or contributing to it understand the changes from the message itself.</p>
<p>Now let's look at a simple way to solve this issue.</p>
<h1 id="heading-what-is-commitlint">What is Commitlint?</h1>
<p><a target="_blank" href="https://commitlint.js.org/#/">Commitlint</a> is a simple tool that lints your commit messages and makes sure they follow a set of rules.</p>
<p>It runs as a husky pre-commit hook, that is, it runs before the code is committed and blocks the commit in case it fails the lint checks.</p>
<h1 id="heading-how-to-use-commitlint-with-a-simple-javascript-project">How to Use Commitlint with a Simple JavaScript Project</h1>
<p>In this example, we are going to see how we can set up commitlint in a simple JavaScript project. To get started, let's create an empty project first:</p>
<pre><code>mkdir commitlint_example &amp;&amp; cd commitlint_example

npm init
<span class="hljs-comment"># OR</span>
yarn init
<span class="hljs-comment"># Just accept the defaults when prompted to configure the project</span>
</code></pre><p>Now, let's initialise an empty Git repository:</p>
<pre><code>git <span class="hljs-keyword">init</span>
</code></pre><p>We must also add a <code>.gitignore</code> file to prevent some files from being committed:</p>
<pre><code>node_modules/
</code></pre><p>Now we'll add a file called <code>index.js</code> and just log out something for now:</p>
<pre><code>console.log("Hello, World!!!")
</code></pre><p>Running <code>node .</code> should print out the text on your terminal like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637567812155/mXZW8Jtc-.png" alt="image.png" /></p>
<p>Running <code>node .</code> prints out Hello, World!!!</p>
<h1 id="heading-how-to-set-up-commitlint">How to Set Up commitlint</h1>
<p>We're going to set up commitlint following the <a target="_blank" href="https://commitlint.js.org/#/guides-local-setup">official local setup docs here</a>.</p>
<p>Firstly, we need to install the commitlint CLI and add a commitlint config (in this case the default <a target="_blank" href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits Config</a>).</p>
<pre><code><span class="hljs-built_in">npm</span> install @commitlint/cli @commitlint/config-conventional --save-dev
<span class="hljs-comment"># OR</span>
yarn add -D @commitlint/cli @commitlint/config-conventional
</code></pre><p>We need to add some configuration to a file named <code>commitlint.config.js</code> like this:</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">extends</span>: [
        <span class="hljs-string">"@commitlint/config-conventional"</span>
    ],
}
</code></pre>
<p>Now we need to install husky to run commitlint as a pre-commit hook.</p>
<pre><code>npm <span class="hljs-keyword">install</span> husky <span class="hljs-comment">--save-dev</span>
<span class="hljs-comment"># OR</span>
yarn <span class="hljs-keyword">add</span> -D husky
</code></pre><p>We also need to enable the <a target="_blank" href="https://typicode.github.io/husky/#/">husky</a> hooks:</p>
<pre><code>npx husky <span class="hljs-keyword">install</span>
<span class="hljs-comment"># OR</span>
yarn husky <span class="hljs-keyword">install</span>
</code></pre><p>We can add a prepare step which enables the husky hooks upon installation:</p>
<pre><code>npm <span class="hljs-keyword">set</span>-script <span class="hljs-keyword">prepare</span> "husky install"
</code></pre><p>Now that we are done installing husky, we need to add a pre-commit hook to run commitlint before the code is committed.</p>
<pre><code>npx husky <span class="hljs-keyword">add</span> .husky/<span class="hljs-keyword">commit</span>-msg "npx --no -- commitlint --edit $1"
# <span class="hljs-keyword">OR</span>
yarn husky <span class="hljs-keyword">add</span> .husky/<span class="hljs-keyword">commit</span>-msg "yarn commitlint --edit $1"
</code></pre><p>Now we're done setting up commitlint. So let's test to see if it works.</p>
<p>First, we'll stage all files to commit them:</p>
<pre><code>git <span class="hljs-keyword">add</span> -A
</code></pre><p>Now, let's try to commit the changes, without following the default convention:</p>
<pre><code>git <span class="hljs-keyword">commit</span> -m "set up a basic js project, added commitlint and husky for liniting commit messages"
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637568416654/qKDqMBQC8.png" alt="image.png" />
You should get the above output (or something similar) which errors out. If the commit is successful, you have likely gone wrong somewhere. Make sure that you have run all the commands above and try undoing the commit, running the scripts, and committing again until it fails.</p>
<p>Now it is time to commit properly. Run this command:</p>
<pre><code>git <span class="hljs-keyword">commit</span> -m "ci: initialised basic js project, added commitlint and husky to lint commit messages"
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637568454821/TMptoU09l.png" alt="image.png" />
And now it all looks good.</p>
<h1 id="heading-how-the-default-commitlint-convention-works">How the Default commitlint Convention Works</h1>
<p>The default commitlint convention uses the <a target="_blank" href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits Convention</a> where there is a type, optionally a scope, a subject, and optionally a body and footer.</p>
<p>For example I can fix a bug related to UI and then the commit message can be <code>fix(ui): Button was not showing up properly on mobile view.</code> Here the type is <code>fix</code>, that is, a fix for a bug, the scope is <code>ui</code> as the fix was related to the ui, and the subject provides more context about the issue.</p>
<p>Note that I can supply multiple scopes, for example, <code>feat(ui,lang): added an option to save the image as svg and added language support for Spanish.</code> Here we introduce 2 features – a new button to save an image as svg and language support for Spanish. This means that there are 2 scopes. The scopes can be separated by the 3 delimiters - <code>,</code>, <code>/</code> and <code>\</code>.</p>
<p>Just a quick note here: you should usually keep commits small and specific, and while there might be some edge cases, this is not one. We're just using it for example purposes.</p>
<p>Breaking changes are usually represented with an exclamation <code>!</code> mark but you can also write them in bold in the footer of the commit message. Doing both is the best practice where the footer gives more information. Here's an example:</p>
<pre><code><span class="hljs-attribute">refactor(runtime)!</span>: Dropped support for NodeJS v12

<span class="sql">BREAKING <span class="hljs-keyword">CHANGE</span>: Support <span class="hljs-keyword">for</span> NodeJS v12 has been dropped due <span class="hljs-keyword">to</span> the latest refactor, please <span class="hljs-keyword">upgrade</span> <span class="hljs-keyword">to</span> the latest LTS <span class="hljs-keyword">version</span> <span class="hljs-keyword">of</span> NodeJS</span>
</code></pre><p>This brings us to multi-line commit messages. Sometimes we need to give more context on something. In this case, it best to include the info in the commit message to make it clear to anyone trying to understand what all has changed and why it has changed in a commit. Here's an example:</p>
<pre><code>docs: Added an aria-label <span class="hljs-keyword">in</span> the IconButton example
aria-label <span class="hljs-keyword">is</span> a required prop <span class="hljs-keyword">by</span> the IconButton component. <span class="hljs-keyword">If</span> it <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> present, the build will fail
</code></pre><ul>
<li>Advantages of using commitlint
Automatic changelogs – Due to commits following a standard convention, tools like <a target="_blank" href="https://github.com/conventional-changelog/standard-version">standard-version</a> can automatically generate changelogs</li>
<li>Better understanding of commits – A commit with a specific type and scope will help you understand what code the commit changes</li>
<li>Adherence to a particular convention – When you have a big project and a lot of people committing to it, people might forget to use the convention. commitlint blocks such commits so that the commits adhere to the defined convention.
Now you know the basics of commitlint. And in the next part of this article, we are going to dive a little deeper and see how to write custom commitlint rules and how to run a  commitlint CI in GitHub Actions.</li>
</ul>
<h1 id="heading-how-to-create-custom-commitlint-rules">How to Create Custom commitlint Rules</h1>
<p>The <a target="_blank" href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits Convention</a> works for most projects. But sometimes you might want to add some more rules specific to your use case.</p>
<blockquote>
<p>For a complete reference, please <a target="_blank" href="https://commitlint.js.org/#/reference-rules">look at the official documentation here</a>.</p>
</blockquote>
<p>For our example here, we'll use an application which has a library of buttons made with TailwindCSS. You can add your creation to this application through a pull request.</p>
<p>Now these commits can have different types, so let's take a <code>button</code> for this example. This would require me to override the <code>type-enum</code> rule in the conventional commits convention.</p>
<p>To do this, I will create a <code>rules</code> object in my commitlint config and add <code>button</code> as a type. This is how our <code>commitlint.config.js</code> should look:</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">extends</span>: [
        <span class="hljs-string">"@commitlint/config-conventional"</span>
    ],
    <span class="hljs-attr">rules</span>: {
        <span class="hljs-string">"type-enum"</span>: [<span class="hljs-number">2</span>, <span class="hljs-string">"always"</span>, [<span class="hljs-string">"build"</span>, <span class="hljs-string">"chore"</span>, <span class="hljs-string">"ci"</span>, <span class="hljs-string">"docs"</span>, <span class="hljs-string">"feat"</span>, <span class="hljs-string">"fix"</span>, <span class="hljs-string">"perf"</span>, <span class="hljs-string">"refactor"</span>, <span class="hljs-string">"revert"</span>, <span class="hljs-string">"style"</span>, <span class="hljs-string">"test"</span>, <span class="hljs-string">"button"</span>]],
    }
}
</code></pre>
<p>Here I have just added the button type on top of the default types. Now let's commit this:</p>
<pre><code>git <span class="hljs-keyword">add</span> -A
git <span class="hljs-keyword">commit</span> -m "ci(commitlint): added button as a type of commit"
</code></pre><p>Now we'll test our button type. For this example, I am just going to add a new line to our index.js file. This is how it should look:</p>
<pre><code class="lang-js"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello, World!!!"</span>)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"New Button"</span>)
</code></pre>
<p>Now, let's commit it:</p>
<pre><code>git <span class="hljs-keyword">add</span> -A
git <span class="hljs-keyword">commit</span> -m "button: added a new console.log to qualify as a button"
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637569506925/D_-UuHYusQ.png" alt="image.png" />
You should get the above output.</p>
<h1 id="heading-how-to-use-commitlint-with-github-actions">How to Use Commitlint with GitHub Actions</h1>
<p>Commit messages are linted locally, but sadly such checks can be skipped locally. So we can add a step in our CI/CD workflow to double-check.</p>
<p>In this example, we are going to be using <a target="_blank" href="https://github.com/features/actions">GitHub Actions</a> but there are <a target="_blank" href="https://commitlint.js.org/#/guides-ci-setup">official guides</a> for Travis CI, Circle CI, and GitLab CI as well.</p>
<h2 id="heading-how-to-push-our-code-to-github">How to Push our Code to GitHub</h2>
<p>Firstly, we need to push our code to GitHub to use GitHub Actions. So let's do that real quick.</p>
<p>I am going to be using the <a target="_blank" href="https://github.com/cli/cli">GitHub CLI</a> for this but you can do it via the GUI – just don't forget to add the upstream to the repository.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637569760265/0PN6WWM3o.png" alt="image.png" /></p>
<p>We can push the code using <code>git push origin master</code>.</p>
<h1 id="heading-how-to-set-up-the-workflow">How to Set Up the Workflow</h1>
<p>We are going to be using a pre-built GitHub Action for this example, which you can find here: <a target="_blank" href="https://github.com/wagoid/commitlint-github-action">https://github.com/wagoid/commitlint-github-action</a>.</p>
<p>We need to make a new folder called <code>.github</code> and then a new folder in it called <code>workflows</code>. Then we can add a file called <code>commitlint.yml</code> and add the workflow configuration.</p>
<p><code>.github/workflows/commitlint.yml</code></p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Lint</span> <span class="hljs-string">Commit</span> <span class="hljs-string">Messages</span>
<span class="hljs-attr">on:</span> [<span class="hljs-string">pull_request</span>, <span class="hljs-string">push</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">commitlint:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">wagoid/commitlint-github-action@v4</span>
</code></pre>
<p>This workflow will run every time code is pushed to GitHub and every time a pull request is opened. To test it, let's commit and push our code.</p>
<pre><code>git <span class="hljs-keyword">add</span> -A
git <span class="hljs-keyword">commit</span> -m "ci(commitlint,workflow): added GitHub action workflow to run commitlint on push and pr"
git push origin master
</code></pre><p>Now, we can go to the GitHub repository and then the actions tab and we can see the workflow.</p>
<blockquote>
<p>I made a typo in the name of the workflows folder so I had to fix that and commit and push again so the commit name is different.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637569850084/yiiRhd9n0.png" alt="image.png" />
When you look at the details, you can see that the workflow has been successful as all the commits until now have adhered to the convention.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637569859337/svUf979R1.png" alt="image.png" /></p>
<p>We can also inspect the logs:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1637569866028/l1c3xkuWZ.png" alt="image.png" /></p>
<p>What's next?
I hope everything has worked well for you so far. If you had any issues, feel free to reach out to me on <a target="_blank" href="https://twitter.com/AnishDe12020">Twitter</a> and I will be happy to help 😃.</p>
<p>Now that you have commitlint set up, it's a good idea to add automated changelogs. So head over to the <a target="_blank" href="https://github.com/conventional-changelog/standard-version">standard version</a> repository and try to implement it on your own!</p>
<p>Helpful Links</p>
<ul>
<li>Demo Repository - https://github.com/AnishDe12020/commitlint-example</li>
<li>Commitlint website and docs - https://commitlint.js.org/#/</li>
<li>Commitlint GitHub action - https://github.com/wagoid/commitlint-github-action</li>
<li>Standard Version GitHub Repository - https://github.com/conventional-changelog/standard-version</li>
<li>Husky website and docs - https://typicode.github.io/husky/#/</li>
<li>Conventional Commits - https://www.conventionalcommits.org</li>
</ul>
]]></content:encoded></item></channel></rss>