<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nicolas Bello Camilletti (English)</title>
    <link>https://nbellocam.dev</link>
    <description>English posts from Nicolas Bello Camilletti&apos;s blog</description>
    <language>en</language>
    <lastBuildDate>Wed, 22 Feb 2023 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://nbellocam.dev/feed.en.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Deploy a static site to Firebase Hosting with GitHub Actions (action-hosting-deploy)</title>
      <link>https://nbellocam.dev/blog/github-actions-firebase-site</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/github-actions-firebase-site</guid>
      <pubDate>Wed, 22 Feb 2023 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>Typescript</category>
      <category>next.js</category>
      <category>web</category>
      <category>firebase</category>
      <description><![CDATA[Set up FirebaseExtended/action-hosting-deploy@v0 to deploy a static site to Firebase Hosting on every push, with preview channels for each pull request.]]></description>
      <content:encoded><![CDATA[<p>As I mentioned in a previous post, I'm now using GitHub Actions to deploy this blog into Firebase static web sites. Even when deploying to firebase it's not a hard tasks, I prefer to do it with a simple <code>git push</code> and ensure that everything will work as expected. In this post, I will share how to set up a GitHub Actions workflow to deploy an static site to Firebase.</p>
<h2 id="setting-up-everything">Setting up everything</h2>
<p>First, we need to set up Firebase. If you haven't already done so, go to the Firebase console and create a new project.</p>
<p>Once you have created a new project, you will need to install the Firebase CLI. You can do this by running the following command in your terminal:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">npm</span><span style="color:#ADDCFF"> install</span><span style="color:#91CBFF"> -g</span><span style="color:#ADDCFF"> firebase-tools</span></span></code></pre>
<p>After the Firebase CLI is installed, log in to your Firebase account by running the following command.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">firebase</span><span style="color:#ADDCFF"> login</span></span></code></pre>
<p>Then, you need to set up Firebase Hosting. To do that just type the following and follow the steps which will automatically take care of setting up the GitHub Action.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">firebase</span><span style="color:#ADDCFF"> init</span><span style="color:#ADDCFF"> hosting</span></span></code></pre>
<p>This command configure your accounts by creating a service account in firebase with the necessary perfmisions to deploy to firebase hosting and also encrypts that service account's JSON key and configure a GitHub secret with it. Finally, it creates two GitHub workflows files. And just like that, you will have everything ready to start deploying every time you push to your repository.</p>
<h2 id="the-github-actions">The GitHub Actions</h2>
<p>By running the previous steps, you will end up with a <code>.github/workflows</code> folder with two files. The <code>firebase-hosting-merge.yaml</code> is used when you merge to main and looks like the following.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#72F088">name</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">Deploy to Firebase Hosting on merge</span></span>
<span class="line"><span style="color:#ADDCFF">'on'</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">  push</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">    branches</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#F0F3F6">      - </span><span style="color:#ADDCFF">main</span></span>
<span class="line"><span style="color:#72F088">jobs</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">  build_and_deploy</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">    runs-on</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">ubuntu-latest</span></span>
<span class="line"><span style="color:#72F088">    steps</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#F0F3F6">      - </span><span style="color:#72F088">uses</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">actions/checkout@v7</span></span>
<span class="line"><span style="color:#F0F3F6">      - </span><span style="color:#72F088">uses</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">actions/setup-node@v7</span></span>
<span class="line"><span style="color:#72F088">        with</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">          node-version</span><span style="color:#F0F3F6">: </span><span style="color:#91CBFF">24</span></span>
<span class="line"><span style="color:#72F088">          cache</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">npm</span></span>
<span class="line"><span style="color:#F0F3F6">      - </span><span style="color:#72F088">run</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">npm ci &#x26;&#x26; npm run build</span></span>
<span class="line"><span style="color:#F0F3F6">      - </span><span style="color:#72F088">uses</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">FirebaseExtended/action-hosting-deploy@v0</span></span>
<span class="line"><span style="color:#72F088">        with</span><span style="color:#F0F3F6">:</span></span>
<span class="line"><span style="color:#72F088">          repoToken</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">'${{ secrets.GITHUB_TOKEN }}'</span></span>
<span class="line"><span style="color:#72F088">          firebaseServiceAccount</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">'${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'</span></span>
<span class="line"><span style="color:#72F088">          channelId</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">live</span></span>
<span class="line"><span style="color:#72F088">          projectId</span><span style="color:#F0F3F6">: { </span><span style="color:#ADDCFF">your-project-id</span><span style="color:#F0F3F6"> }</span></span></code></pre>
<p>It has four steps: the repository checkout, the Node.js setup (with the npm cache, which speeds up <code>npm ci</code> quite a bit), then <code>npm ci &#x26;&#x26; npm run build</code> to install all the dependencies and build the site (plus a few other scripts), and finally the <code>FirebaseExtended/action-hosting-deploy@v0</code> action, which deploys the site to the live channel. <code>@v0</code> is not a typo: it's the action's current major version. Note that all the secrets were created by the <code>firebase init hosting</code> command.</p>
<p>Regarding the <code>build</code> script, I mentioned that it performs other tasks as well. The following is part of my <code>package.json</code> that shows the tasks that are being run.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#ADDCFF">  "scripts"</span><span style="color:#F0F3F6">: {</span></span>
<span class="line"><span style="color:#72F088">    "copyimages"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"tsx ./scripts/copy-imgs.ts"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "prebuild"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"npm run copyimages"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "build"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"next build"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "postbuild"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"next-sitemap"</span></span>
<span class="line"><span style="color:#F0F3F6">  },</span></span></code></pre>
<p>You can see that I use the pre and post scripts to run other stuff: <code>npm run build</code> runs <code>npm run copyimages</code> before and <code>next-sitemap</code> after. Since Next.js 14 there is no <code>next export</code> command; you set <code>output: 'export'</code> in <code>next.config.js</code> and <code>next build</code> writes the static site to the <code>out</code> folder, which is what Firebase Hosting serves (<code>"public": "out"</code> in <code>firebase.json</code>). The copy images scripts it's a simple script that just copy the images from the posts folder to the public folder and <code>next-sitemap</code> as you can guess, creates a <code>sitemap.xml</code>.</p>
<p>Going back to the workflows, the other yaml file named <code>firebase-hosting-pull-request.yaml</code> is very similar but targets the pull request targeting your main branch and the only real difference is that it doesn't have the <code>channelId: live</code> configuration which means that it will create a preview channel in Firebase Hosting and comment the preview URL on the pull request. Preview channels expire after 7 days by default; you can change that with the <code>expires</code> input (for example <code>expires: 30d</code>).</p>
<p>You can find more information on this GitHub action in the <a href="https://github.com/marketplace/actions/deploy-to-firebase-hosting">marketplace site</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this tutorial, we have walked through the process of setting up a GitHub Actions workflow to deploy an static site to Firebase. GitHub Actions makes it easy to automate the deployment process, freeing up time for other tasks. With this knowledge, you can deploy your static sites to Firebase quickly and easily, ensuring that your site is always up-to-date. Not only that, but you can also have preview deployments which is a super powerfull tool to test everything.</p>
<p>Of course, this tutorial only scratches the surface of what is possible with GitHub Actions. You can use GitHub Actions to automate a wide variety of tasks, from running tests to deploying to multiple environments. With a little creativity, you can automate almost any task in your development workflow.</p>
<p>So why not give GitHub Actions a try? It may just save you hours of manual work and streamline your development process. Happy coding!</p>]]></content:encoded>
    </item>
    <item>
      <title>Unified.js explained: parsing and transforming Markdown with remark and rehype</title>
      <link>https://nbellocam.dev/blog/unified-js</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/unified-js</guid>
      <pubDate>Sun, 12 Feb 2023 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>markdown</category>
      <category>javascript</category>
      <description><![CDATA[What unified.js is, how remark and rehype plugins turn Markdown into syntax trees and HTML, and how to import it now that unified is ESM-only.]]></description>
      <content:encoded><![CDATA[<p><a href="https://unifiedjs.com/">Unified.js</a> is an awesome library for working with content (Markdown, HTML, natural language). It offers a powerful and flexible solution for transforming, analyzing, and manipulating that content. The easy way to see it is that it compiles content to syntax trees and the other way around and there is a great community building blocks to work with those syntax trees.</p>
<p>The best example I have and why I started to play around with it, it's this blog as I'm using it to process from markdown to html adding code snippets, calculate the reading time and creating a short description in the middle.</p>
<p>In this post, I will share a quick review and also show what I'm doing for my personal blog with it.</p>
<h2 id="what-is-unifiedjs">What is Unified.js?</h2>
<p>At its core, Unified.js is a parsing library that makes it possible to work with natural language data in a standardized format. This is achieved by transforming raw text into an abstract syntax tree (AST), which is a structured representation of the text's meaning and structure. With this AST, you can easily manipulate the data and extract meaningful information.</p>
<p>For example, let's say you have a blog post written in markdown. With Unified.js, you can parse the markdown text into an AST, then extract the headings, links, and other important information. This information can then be used to create a table of contents, generate a summary, or any other number of useful applications.</p>
<p>To parse natural language data with Unified.js, you simply need to pass the text to the <code>unified()</code> function, along with a parser that corresponds to the format of the data. For example, to parse a markdown file, you would use the <code>remark-parse</code> parser (<code>remarkParse</code>).</p>
<p>Here's a code sample that demonstrates how to parse a markdown file with Unified.js:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { unified } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'unified'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> remarkParse </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'remark-parse'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> markdownText</span><span style="color:#FF9492"> =</span><span style="color:#ADDCFF"> '# Hello, World!</span><span style="color:#FF9492">\n\n</span><span style="color:#ADDCFF">This is a sample markdown file.'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> ast</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> unified</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(remarkParse).</span><span style="color:#DBB7FF">parse</span><span style="color:#F0F3F6">(markdownText);</span></span></code></pre>
<p>In this example, we first import the unified and remark-parse modules, which provide the core functionality for parsing markdown data. Then, we pass the markdown text to the <code>unified()</code> function, and use the <code>remark-parse</code> parser (<code>remarkParse</code>) to parse the data into an AST.</p>
<h3 id="requireunified-doesnt-work-anymore"><code>require('unified')</code> doesn't work anymore</h3>
<p>Since unified 10 (and the remark/rehype plugins released with it), the whole ecosystem is ESM-only and <code>unified</code> is a <strong>named</strong> export. That's why the classic <code>const unified = require('unified')</code> fails, either with <code>ERR_REQUIRE_ESM</code> or with <code>unified is not a function</code>. You have two options:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#BDC4CC">// ESM ("type": "module" in package.json, or a .mjs file)</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { unified } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'unified'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#BDC4CC">// CommonJS on Node.js 22.12+ / 20.19+, where require() can load ESM</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#F0F3F6"> { </span><span style="color:#91CBFF">unified</span><span style="color:#F0F3F6"> } </span><span style="color:#FF9492">=</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'unified'</span><span style="color:#F0F3F6">);</span></span></code></pre>
<p>On older Node.js versions in a CommonJS project, use a dynamic <code>await import('unified')</code> or pin <code>unified@9</code>, the last CommonJS release.</p>
<h2 id="transforming-natural-language-data">Transforming Natural Language Data</h2>
<p>Once you've parsed natural language data into an AST, you can then use Unified.js to transform this data in a variety of ways. With its flexible architecture, you can create custom transformations that manipulate the AST and extract information in a way that is tailored to your specific needs.</p>
<p>For example, let's say you have a large collection of customer reviews, and you want to extract the sentiment of each review. With Unified.js, you can create a custom transformation that analyzes the sentiment of each review and categorizes it as positive, negative, or neutral.</p>
<p>Here's a code sample that demonstrates how to perform sentiment analysis with Unified.js:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { unified } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'unified'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> remarkParse </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'remark-parse'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { toString } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'mdast-util-to-string'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> Sentiment </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'sentiment'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> sentiment</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> new</span><span style="color:#DBB7FF"> Sentiment</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> markdownText</span><span style="color:#FF9492"> =</span></span>
<span class="line"><span style="color:#ADDCFF">  '# Customer Reviews</span><span style="color:#FF9492">\n\n</span><span style="color:#ADDCFF">I loved this product!</span><span style="color:#FF9492">\n\n</span><span style="color:#ADDCFF">I hated this product.'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> ast</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> unified</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(remarkParse).</span><span style="color:#DBB7FF">parse</span><span style="color:#F0F3F6">(markdownText);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">for</span><span style="color:#F0F3F6"> (</span><span style="color:#FF9492">const</span><span style="color:#91CBFF"> node</span><span style="color:#FF9492"> of</span><span style="color:#F0F3F6"> ast.children) {</span></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (node.type </span><span style="color:#FF9492">!==</span><span style="color:#ADDCFF"> 'paragraph'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">continue</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> text</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> toString</span><span style="color:#F0F3F6">(node);</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#F0F3F6"> { </span><span style="color:#91CBFF">score</span><span style="color:#F0F3F6"> } </span><span style="color:#FF9492">=</span><span style="color:#F0F3F6"> sentiment.</span><span style="color:#DBB7FF">analyze</span><span style="color:#F0F3F6">(text);</span></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (score </span><span style="color:#FF9492">></span><span style="color:#91CBFF"> 0</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">`Positive review: ${</span><span style="color:#F0F3F6">text</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">  } </span><span style="color:#FF9492">else</span><span style="color:#FF9492"> if</span><span style="color:#F0F3F6"> (score </span><span style="color:#FF9492">&#x3C;</span><span style="color:#91CBFF"> 0</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">`Negative review: ${</span><span style="color:#F0F3F6">text</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">  } </span><span style="color:#FF9492">else</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">`Neutral review: ${</span><span style="color:#F0F3F6">text</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>In this example, we use the <code>sentiment</code> module to perform sentiment analysis on each paragraph node in the AST (<code>mdast-util-to-string</code> gets the plain text of the paragraph, even when it has links or emphasis inside). The <code>analyze()</code> method returns a score that indicates the sentiment of the text, with positive scores indicating positive sentiment, negative scores indicating negative sentiment, and scores close to zero indicating neutral sentiment.</p>
<h2 id="my-own-personal-experience">My own personal experience</h2>
<p>As I mentioned before, I'm using it for this blog to process the markdown files and generate what you are reading. The following is the full pipeline.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { unified } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "unified"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> remarkParse </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "remark-parse"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> rehypeInferReadingTimeMeta </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "rehype-infer-reading-time-meta"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> remarkGfm </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "remark-gfm"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> remarkRehype </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "remark-rehype"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> rehypeRaw </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "rehype-raw"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> rehypeInferDescriptionMeta </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "rehype-infer-description-meta"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> rehypeShiki </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "@shikijs/rehype"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> rehypeStringify </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "rehype-stringify"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> transformImgSrc </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> "./remark-transformImgSrc"</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">export</span><span style="color:#FF9492"> default</span><span style="color:#FF9492"> async</span><span style="color:#FF9492"> function</span><span style="color:#DBB7FF"> markdownToHtml</span><span style="color:#FFB757">(slug</span><span style="color:#FF9492">:</span><span style="color:#91CBFF"> string</span><span style="color:#FFB757">, markdown</span><span style="color:#FF9492">:</span><span style="color:#91CBFF"> string</span><span style="color:#FFB757">) </span><span style="color:#F0F3F6">{</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> result</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#DBB7FF"> unified</span><span style="color:#F0F3F6">()</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(remarkParse)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(remarkGfm)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(transformImgSrc, { slug })</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(remarkRehype, { allowDangerousHtml: </span><span style="color:#91CBFF">true</span><span style="color:#F0F3F6"> })</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(rehypeRaw)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(rehypeShiki, { theme: </span><span style="color:#ADDCFF">"github-dark-high-contrast"</span><span style="color:#F0F3F6"> })</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(rehypeInferReadingTimeMeta)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(rehypeInferDescriptionMeta)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">use</span><span style="color:#F0F3F6">(rehypeStringify)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">process</span><span style="color:#F0F3F6">(markdown);</span></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#F0F3F6"> { content: result.</span><span style="color:#DBB7FF">toString</span><span style="color:#F0F3F6">(), meta: result.data.meta } </span><span style="color:#FF9492">as</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FFB757">    content</span><span style="color:#FF9492">:</span><span style="color:#91CBFF"> string</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FFB757">    meta</span><span style="color:#FF9492">:</span><span style="color:#FFB757"> Metadata</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">  };</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>Note that I'm using a lot of transformations, even some custom ones (i.e., <code>transformImgSrc</code> which replace all the images urls paths to move them inside next.js' public folder). You can see the <code>transformImgSrc</code> code below. I'm using the <code>visit</code> function to simplify the navigation in the AST and just finding all the childs of type image and then replacing the image path.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { visit } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'unist-util-visit'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> imgDirInsidePublic</span><span style="color:#FF9492"> =</span><span style="color:#ADDCFF"> 'assets'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">export</span><span style="color:#FF9492"> default</span><span style="color:#FF9492"> function</span><span style="color:#DBB7FF"> transformImgSrc</span><span style="color:#FFB757">({ slug }) </span><span style="color:#F0F3F6">{</span></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#F0F3F6"> (</span><span style="color:#FFB757">tree</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">_file</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#DBB7FF">    visit</span><span style="color:#F0F3F6">(tree, </span><span style="color:#ADDCFF">'paragraph'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">node</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      const</span><span style="color:#91CBFF"> image</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> node.children.</span><span style="color:#DBB7FF">find</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">child</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> child.type </span><span style="color:#FF9492">===</span><span style="color:#ADDCFF"> 'image'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">      if</span><span style="color:#F0F3F6"> (image) {</span></span>
<span class="line"><span style="color:#FF9492">        const</span><span style="color:#91CBFF"> fileName</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> image.url.</span><span style="color:#DBB7FF">replace</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'./'</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">''</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">        image.url </span><span style="color:#FF9492">=</span><span style="color:#ADDCFF"> `/${</span><span style="color:#F0F3F6">imgDirInsidePublic</span><span style="color:#ADDCFF">}/${</span><span style="color:#F0F3F6">slug</span><span style="color:#ADDCFF">}/${</span><span style="color:#F0F3F6">fileName</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">      }</span></span>
<span class="line"><span style="color:#F0F3F6">    });</span></span>
<span class="line"><span style="color:#F0F3F6">  };</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>Another interesting transformation are the support of GitHub markdown by using <code>remark-gfm</code> and the reading time calculation with <code>rehype-infer-reading-time-meta</code>. As mentioned before and as you can see, I'm also highlighting code snippets at build time with Shiki (<code>@shikijs/rehype</code>). So a lot of power with a few dependencies and chained calls.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The benefits of using Unified.js are many, but some of the most notable include:</p>
<ul>
<li>
<p><strong>Flexibility</strong>: Unified.js is incredibly flexible and can be used with a wide range of data formats, including markdown, HTML, and even plain text. This makes it a versatile tool for a variety of applications.</p>
</li>
<li>
<p><strong>Ease of Use</strong>: Despite its power, Unified.js is surprisingly easy to use. Its API is intuitive and straightforward, making it accessible to developers of all skill levels.</p>
</li>
<li>
<p><strong>High-Performance</strong>: Unified.js is built to handle large amounts of data, and its performance is unparalleled. It can handle even the largest of datasets with ease, making it ideal for use in high-traffic applications.</p>
</li>
<li>
<p><strong>Extensibility</strong>: Unified.js is highly extensible, and its modular design makes it easy to add custom functionality. Whether you need to add support for a new data format or create a custom transformation, Unified.js has you covered.</p>
</li>
</ul>
<p>Unified.js is a powerful and flexible library that makes it possible to work with natural language data in a standardized format. With its ease of use, high performance, and extensibility, it's no wonder that Unified.js has become a popular choice among developers. So why not give it a try? You might just be surprised at what you can build!</p>]]></content:encoded>
    </item>
    <item>
      <title>Migrating my blog once again</title>
      <link>https://nbellocam.dev/blog/migrating-once-again</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/migrating-once-again</guid>
      <pubDate>Sat, 21 Jan 2023 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>Typescript</category>
      <category>next.js</category>
      <description><![CDATA[I already have two post about the migrations of my blog engine to a new stack in the past. I first started writing in my previous work's…]]></description>
      <content:encoded><![CDATA[<p>I already have two post about the migrations of my blog engine to a new stack in the past. I first started writing in my previous work's Wordpress so my first attempt was to keep using Wordpress at that time. I created a VM on Azure, installed docker and Wordpress in it and, of course, I wrote about how I created that <a href="https://nbellocam.dev/blog/wordpress-docker-container-compose">blog using docker containers and nginx in an Azure VM</a>.</p>
<p>After that, I started using medium, to focus on writing instead of all the mantaining and other stuff but that didn't keep for long as medium started to add too many changes and walls. So, I went back to maintain my own site and created a <a href="https://nbellocam.dev/blog/migrating-to-gatsbyjs">blog in gatsby.js</a> which works great for a while.</p>
<p>A few months ago, when I was trying to write some new content, I realized that I haven't updated the dependencies for quite some time and the site started to fail on running locally as well as on deploying (as this was an static site, it was build on every push to the repository using Azure DevOps Pipelines). I tried to see what dependency was causing issues but after some time I decided that the best was to migrate once again. I was using a very old version of gatbsy with too many custom things to migrate to a new one and I wasn't very happy with the results either.</p>
<h2 id="the-base-stack">The base stack</h2>
<p>I started investigating what would be the best and created some spikes. After a time, I thought that the best would be to use something more simpler, which I was already using in the everyday, so it could be easier to build as well as maintain. That is when I decided to use next.js as the base stack. As I already had all my blog posts in markdown and I wanted to keep the static site approach, so I choose the <a href="https://github.com/vercel/next.js/tree/canary/examples/blog-starter">blog-starter example</a> as base which already has Tailwind and Typescript support and uses remark for procesing the markdown posts.</p>
<p>As the base example is a bit too simple, I started playing around with it and added things like comments support using disqus (which was what I used in the past). Additionally, I updated the styles a bit to give it a personal touch and added some extra things like analytics and such. I also updated the markdown processing pipeline to support code snippets and other smaller improvements. I will be writing a follow up post on some of those things but if you want any particular information just add a comment or ping me on my social networks (you can always find me as <code>nbellocam</code>).</p>
<h2 id="the-infrastructure">The infrastructure</h2>
<p>For building and deploying the site I wanted to move to GitHub Actions which is (very similar to Azure Pipelines) because my repository was on GitHub. My idea behind this migration was to remove tools and again simplify everything a bit.</p>
<p>Regarding the deployment, I was previously using firebase sites and it always worked fine for me, so I choose to keep using it. For the traffic I have and my general needs it works perfectly and it's free which is a great win. There are many great alternatives nowadays, but, as I mentioned before, I was already using that platform and it was simpler to keep using it as I already have my domain configured there. Additionally, I keep using cloudflare to cache the content.</p>
<h2 id="some-extra-considerations">Some extra considerations</h2>
<p>When migrating sites from a stack or platform to another, you always need to consider not only the visible costs but also all the hidden things like the work you will need to do to keep everything working as before. For example, one important thing for a blog site is to keep all the routes, sitemaps and SEO information as before. If you don't plan to do that, you will end up starting all over again with the search engines which in general is very hard. In my case, I updated the base template to use the paths I used to have and also generated a sitemap which wasn't available on the original example.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>All this migration took time and effort which I could have used to create more content or other things, but it's also great way to keep learning new things and play around with new stuff. Even when it can be hard, it's good to dedicate some time to things like this and also invest in yourself to make your life a bit simpler and happier in the long term.</p>
<p>I will be adding some extra posts about what I found most interesting during thisprocess as I did in the past, but you can ask me for any particular thing.</p>]]></content:encoded>
    </item>
    <item>
      <title>Next.js middleware for i18n: redirect users to their browser language</title>
      <link>https://nbellocam.dev/blog/next-middleware-i18n</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/next-middleware-i18n</guid>
      <pubDate>Fri, 09 Dec 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>Typescript</category>
      <category>next.js</category>
      <description><![CDATA[A Next.js middleware that reads the NEXT_LOCALE cookie and the Accept-Language header to redirect each visitor to their locale (Pages Router i18n).]]></description>
      <content:encoded><![CDATA[<p>Localization can be hard to handle in your web sites. Luckily for us there are more tools everyday. While working with next.js I ended up with a problem with the default way they handle it as they only apply localization changes on the root page, so if you navigate to a subpath you will not be able to see the page in the correct locale. After doing some spikes I ended up creating a middleware that handles this using the browser configuration.</p>
<p>In this post we will see what is a middleware for next.js and how you can solve this issue with it.</p>
<h2 id="nextjs-middleware">Next.js middleware</h2>
<p>First, let me set some context. I wrote this for Next.js 12.3 and the Pages Router's built-in i18n routing (<code>i18n</code> in <code>next.config.js</code>). The code below uses the current <code>NextRequest</code> APIs; with the App Router there's no built-in i18n routing, so you'd do the redirect yourself using the same language detection.</p>
<p>Now, what is a middleware? Middleware allows you to execute code before the request is processed. Based on the request information you can perform different actions which is a super useful tool for lots of scenarios such a authentication, A/B Testing, localized pages and more.</p>
<p>The basics are simple. Just create a <code>middleware.ts</code> file in the root of your project with the following stucture and it will be call on every request.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { NextRequest } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'next/server'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">export</span><span style="color:#FF9492"> function</span><span style="color:#DBB7FF"> middleware</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">req</span><span style="color:#FF9492">:</span><span style="color:#FFB757"> NextRequest</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#F0F3F6">  console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(req.nextUrl);</span></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#91CBFF"> undefined</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>As this will be call on everything, you might want to apply the logic only to a certain patters for that you can analyze the pathname of the request. For example you could ignore all the request that target the <code>_next</code> folder or the <code>api</code> and also files as you can see in the following example.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { NextRequest } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'next/server'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">export</span><span style="color:#FF9492"> function</span><span style="color:#DBB7FF"> middleware</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">req</span><span style="color:#FF9492">:</span><span style="color:#FFB757"> NextRequest</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> pathname</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> req.nextUrl.pathname;</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> shouldHandleLocale</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> !</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">startsWith</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/_next'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">||</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">startsWith</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/api/'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">||</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">includes</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'.'</span><span style="color:#F0F3F6">, pathname.</span><span style="color:#DBB7FF">lastIndexOf</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/'</span><span style="color:#F0F3F6">))</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (shouldHandleLocale) {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(req.nextUrl);</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#91CBFF"> undefined</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<h2 id="handling-locales">Handling locales</h2>
<p>First, let's get the user's default locale. To get this, we will take advantage of the <code>accept-language</code> header which looks like this: <code>en-US,en;q=0.9,es;q=0.8</code>. We will need to parse it and check if we are supporting those locales. The following code does that, ordering the locales by priority and then filtering them.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> LOCALES</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> [</span><span style="color:#ADDCFF">'es'</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">'en'</span><span style="color:#F0F3F6">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#DBB7FF"> getBrowserLanguage</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> (</span><span style="color:#FFB757">req</span><span style="color:#FF9492">:</span><span style="color:#FFB757"> NextRequest</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#BDC4CC">  // e.g. "en-US,en;q=0.9,es;q=0.8" -> [{ code: 'en', q: 1 }, { code: 'en', q: 0.9 }, ...]</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> header</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> req.headers.</span><span style="color:#DBB7FF">get</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'accept-language'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">??</span><span style="color:#ADDCFF"> ''</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#F0F3F6"> header</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">split</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">','</span><span style="color:#F0F3F6">)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">map</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">part</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      const</span><span style="color:#F0F3F6"> [</span><span style="color:#91CBFF">tag</span><span style="color:#F0F3F6">, </span><span style="color:#FF9492">...</span><span style="color:#91CBFF">params</span><span style="color:#F0F3F6">] </span><span style="color:#FF9492">=</span><span style="color:#F0F3F6"> part.</span><span style="color:#DBB7FF">trim</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">split</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">';'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">      const</span><span style="color:#91CBFF"> q</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> params.</span><span style="color:#DBB7FF">find</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">p</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> p.</span><span style="color:#DBB7FF">trim</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">startsWith</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'q='</span><span style="color:#F0F3F6">));</span></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">        code: tag.</span><span style="color:#DBB7FF">trim</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">substring</span><span style="color:#F0F3F6">(</span><span style="color:#91CBFF">0</span><span style="color:#F0F3F6">, </span><span style="color:#91CBFF">2</span><span style="color:#F0F3F6">).</span><span style="color:#DBB7FF">toLowerCase</span><span style="color:#F0F3F6">(),</span></span>
<span class="line"><span style="color:#BDC4CC">        // No q means 1 (highest priority), per the HTTP spec.</span></span>
<span class="line"><span style="color:#F0F3F6">        q: q </span><span style="color:#FF9492">?</span><span style="color:#F0F3F6"> Number.</span><span style="color:#DBB7FF">parseFloat</span><span style="color:#F0F3F6">(q.</span><span style="color:#DBB7FF">trim</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">slice</span><span style="color:#F0F3F6">(</span><span style="color:#91CBFF">2</span><span style="color:#F0F3F6">)) </span><span style="color:#FF9492">:</span><span style="color:#91CBFF"> 1</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      };</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">filter</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">lang</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> lang.q </span><span style="color:#FF9492">></span><span style="color:#91CBFF"> 0</span><span style="color:#F0F3F6">) </span><span style="color:#BDC4CC">// q=0 means "not acceptable"</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">sort</span><span style="color:#F0F3F6">((</span><span style="color:#FFB757">a</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">b</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> b.q </span><span style="color:#FF9492">-</span><span style="color:#F0F3F6"> a.q)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">find</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">lang</span><span style="color:#FF9492"> =></span><span style="color:#91CBFF"> LOCALES</span><span style="color:#F0F3F6">.</span><span style="color:#DBB7FF">includes</span><span style="color:#F0F3F6">(lang.code))?.code;</span></span>
<span class="line"><span style="color:#F0F3F6">};</span></span></code></pre>
<p>Now, for the following approach, you will need to define a <code>default</code> locale to detect that we need to process it and then the other locales that you are supporting, so in your configuration, set the following.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#ADDCFF">  "defaultLocale"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"default"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#ADDCFF">  "locales"</span><span style="color:#F0F3F6">: [</span><span style="color:#ADDCFF">"default"</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">"en"</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">"es"</span><span style="color:#F0F3F6">],</span></span></code></pre>
<p>Then, we will update our middleware to redirect to the detected locale if the locale was the default one. For doing this, we need to clone the url, and update the locale with the language that we detect. It's important to have a fallback as the headers approach could fail depending on the client's browser. Finally, we will perform a call to <code>NextResponse.redirect(url);</code> with the updated url.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">import</span><span style="color:#F0F3F6"> { NextRequest, NextResponse } </span><span style="color:#FF9492">from</span><span style="color:#ADDCFF"> 'next/server'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> DEFAULT_LANGUAGE</span><span style="color:#FF9492"> =</span><span style="color:#ADDCFF"> 'es'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">export</span><span style="color:#FF9492"> function</span><span style="color:#DBB7FF"> middleware</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">req</span><span style="color:#FF9492">:</span><span style="color:#FFB757"> NextRequest</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> pathname</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> req.nextUrl.pathname;</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> shouldHandleLocale</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> !</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">startsWith</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/_next'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">||</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">startsWith</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/api/'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">||</span></span>
<span class="line"><span style="color:#F0F3F6">    pathname.</span><span style="color:#DBB7FF">includes</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'.'</span><span style="color:#F0F3F6">, pathname.</span><span style="color:#DBB7FF">lastIndexOf</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/'</span><span style="color:#F0F3F6">))</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (shouldHandleLocale </span><span style="color:#FF9492">&#x26;&#x26;</span><span style="color:#F0F3F6"> req.nextUrl.locale </span><span style="color:#FF9492">===</span><span style="color:#ADDCFF"> 'default'</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#FF9492">    const</span><span style="color:#91CBFF"> url</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> req.nextUrl.</span><span style="color:#DBB7FF">clone</span><span style="color:#F0F3F6">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">    const</span><span style="color:#91CBFF"> language</span><span style="color:#FF9492"> =</span></span>
<span class="line"><span style="color:#F0F3F6">      req.cookies.</span><span style="color:#DBB7FF">get</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'NEXT_LOCALE'</span><span style="color:#F0F3F6">)?.value </span><span style="color:#FF9492">??</span></span>
<span class="line"><span style="color:#DBB7FF">      getBrowserLanguage</span><span style="color:#F0F3F6">(req) </span><span style="color:#FF9492">??</span></span>
<span class="line"><span style="color:#91CBFF">      DEFAULT_LANGUAGE</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">    url.locale </span><span style="color:#FF9492">=</span><span style="color:#F0F3F6"> language;</span></span>
<span class="line"><span style="color:#FF9492">    return</span><span style="color:#F0F3F6"> NextResponse.</span><span style="color:#DBB7FF">redirect</span><span style="color:#F0F3F6">(url);</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  return</span><span style="color:#91CBFF"> undefined</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<h2 id="final-thoughts">Final thoughts</h2>
<p>This approach works fine for most of the scenarios but you might even need more things to consider. For example, in my case I had to check that the request could be on an iframe and apply a similar approach to keep the same locale as in the parent window, for that you could play around with the <code>sec-fetch-dest</code> header to see if the request is running on an iframe and also the <code>referer</code> header to see the parent's url. Additionally, in some cases I ended up with double locale in the path (i.e., <code>/en/es/</code>) so I had to add some logic to correct those navigations.</p>
<p>Middlewares are a great tool to handling this kind of cases but you need to consider that the logic you add in there will be executed for everything which might add some performance issues.</p>]]></content:encoded>
    </item>
    <item>
      <title>Project Fugu: Bridging the Gap Between Web and Native Applications</title>
      <link>https://nbellocam.dev/blog/project-fugu</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/project-fugu</guid>
      <pubDate>Thu, 17 Nov 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>pwa</category>
      <category>fugu</category>
      <description><![CDATA[Project Fugu brings native-like capabilities to the web: the Web Share, Device Capabilities, Contacts and File System Access APIs, with code samples.]]></description>
      <content:encoded><![CDATA[<p>The web has come a long way since its inception, but there are still some gaps between what web applications can do and what native applications can do. <strong>Project Fugu</strong> is an effort to bridge that gap and bring more native-like capabilities to web applications.</p>
<p>It's is a collaborative effort by Google, Microsoft, Mozilla, and other major players in the tech industry to make the web a more capable and user-friendly platform. The project was launched in 2019 with the goal of bringing native app-like experiences to the web platform.</p>
<p>The name "<em>Fugu</em>" comes from the Japanese word for <em>pufferfish</em>, which is known for its ability to inflate itself and expand its capabilities. The name is fitting because Project Fugu aims to do just that for the web platform. Additionally, it said that that kind of fish are toxic if they are not well prepared which is similar to what can happend with these kind of features where security and privacy are super important topics to consider while developing them.</p>
<p>For information of this team inside chromium you can visit <a href="https://www.chromium.org/teams/web-capabilities-fugu/">their team site</a> and you can also take a look at the <a href="https://web.dev/fugu-status/">developer-friendly landing page of the project</a>. Additionally, you can visit the <a href="https://goo.gle/fugu-api-tracker">Fugu API Tracker</a> which gives you a full list of new capabilities added by this project. You can also find <a href="https://developer.chrome.com/blog/capabilities/">this article</a> where they explain the process and the ideas behind this project.</p>
<p>Let's explore some of the APIs that are part of Project Fugu and see how they can be used to create more powerful web applications.</p>
<h2 id="web-share-api">Web Share API</h2>
<p>The Web Share API allows web apps to easily share content with other apps on the user's device. This feature was previously only available to native apps, but now web apps can use it as well. Here's an example of how to use the Web Share API:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">if</span><span style="color:#F0F3F6"> (navigator.share) {</span></span>
<span class="line"><span style="color:#F0F3F6">  navigator</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">share</span><span style="color:#F0F3F6">({</span></span>
<span class="line"><span style="color:#F0F3F6">      title: </span><span style="color:#ADDCFF">'Example Page'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      text: </span><span style="color:#ADDCFF">'Check out this cool page!'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      url: window.location.href,</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(() </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Shared successfully'</span><span style="color:#F0F3F6">))</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">catch</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">error</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Error sharing:'</span><span style="color:#F0F3F6">, error));</span></span>
<span class="line"><span style="color:#F0F3F6">} </span><span style="color:#FF9492">else</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Web Share API not supported'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<h2 id="device-capabilities-api">Device Capabilities API</h2>
<p>One of the limitations of web applications compared to native applications is access to device capabilities. The Device Capabilities API is designed to address this limitation by providing a way for web applications to determine what capabilities are available on the device.</p>
<p>Here's an example of how the Device Capabilities API can be used to determine if the device has a camera:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">if</span><span style="color:#F0F3F6"> (</span><span style="color:#ADDCFF">'mediaDevices'</span><span style="color:#FF9492"> in</span><span style="color:#F0F3F6"> navigator </span><span style="color:#FF9492">&#x26;&#x26;</span><span style="color:#ADDCFF"> 'getUserMedia'</span><span style="color:#FF9492"> in</span><span style="color:#F0F3F6"> navigator.mediaDevices) {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> devices</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> navigator.mediaDevices.</span><span style="color:#DBB7FF">enumerateDevices</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> cameras</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> devices.</span><span style="color:#DBB7FF">filter</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">device</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> device.kind </span><span style="color:#FF9492">===</span><span style="color:#ADDCFF"> 'videoinput'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (cameras.</span><span style="color:#91CBFF">length</span><span style="color:#FF9492"> ></span><span style="color:#91CBFF"> 0</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#BDC4CC">    // The device has a camera</span></span>
<span class="line"><span style="color:#F0F3F6">  } </span><span style="color:#FF9492">else</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#BDC4CC">    // The device does not have a camera</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"><span style="color:#F0F3F6">} </span><span style="color:#FF9492">else</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#BDC4CC">  // The browser does not support the Device Capabilities API</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>In this example, we check if the browser supports the Device Capabilities API by checking if <code>navigator.mediaDevices</code> and <code>navigator.mediaDevices.getUserMedia</code> are available. If they are, we enumerate the devices on the device using navigator.<code>mediaDevices.enumerateDevices()</code> and filter for devices with kind set to 'videoinput', which corresponds to cameras. If there are any cameras, we know that the device has a camera.</p>
<h2 id="contacts-api">Contacts API</h2>
<p>Another limitation of web applications is access to the user's contacts. The Contacts API is designed to address this limitation by providing a way for web applications to access the user's contacts with their permission.</p>
<p>Here's an example of how the Contacts API can be used to display the user's contacts:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> permissions</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> navigator.permissions.</span><span style="color:#DBB7FF">query</span><span style="color:#F0F3F6">({ name: </span><span style="color:#ADDCFF">'contacts'</span><span style="color:#F0F3F6"> });</span></span>
<span class="line"><span style="color:#FF9492">if</span><span style="color:#F0F3F6"> (permissions.state </span><span style="color:#FF9492">===</span><span style="color:#ADDCFF"> 'granted'</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> contacts</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> navigator.contacts.</span><span style="color:#DBB7FF">select</span><span style="color:#F0F3F6">([</span><span style="color:#ADDCFF">'name'</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">'email'</span><span style="color:#F0F3F6">]);</span></span>
<span class="line"><span style="color:#BDC4CC">  // Display the contacts</span></span>
<span class="line"><span style="color:#F0F3F6">} </span><span style="color:#FF9492">else</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#BDC4CC">  // The user has not granted permission to access their contacts</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>In this example, we check if the user has granted permission to access their contacts using <code>navigator.permissions.query({name: 'contacts'})</code>. If permission is granted, we can use <code>navigator.contacts.select()</code> to select the contacts we want to display. In this case, we're selecting the contact's name and email.</p>
<h2 id="file-system-access-api">File System Access API</h2>
<p>The File System Access API is designed to address another limitation of web applications: limited access to the user's file system. With the File System Access API, web applications can request access to the user's file system and perform operations such as reading and writing files.</p>
<p>Here's an example of how the File System Access API can be used to read a file:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#F0F3F6"> [</span><span style="color:#91CBFF">fileHandle</span><span style="color:#F0F3F6">] </span><span style="color:#FF9492">=</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> window.</span><span style="color:#DBB7FF">showOpenFilePicker</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> file</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> fileHandle.</span><span style="color:#DBB7FF">getFile</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> text</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> file.</span><span style="color:#DBB7FF">text</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#F0F3F6">console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(text);</span></span></code></pre>
<p>In this example, we use <code>window.showOpenFilePicker()</code> to display a file picker dialog and allow the user to select a file. Once a file is selected, we use the <code>getFile()</code> method on the file handle to get the file, and then the <code>text()</code> method to read the contents of the file as text.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Project Fugu is an exciting initiative that is bringing native app-like experiences to the web platform. By adding new features and APIs, web apps can now do things that were previously only possible with native apps. As these new capabilities become more widely available, you can expect to see a new generation of web apps that are more powerful and user-friendly than ever before.</p>]]></content:encoded>
    </item>
    <item>
      <title>Creating your fist Dapp</title>
      <link>https://nbellocam.dev/blog/creating-a-dapp</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/creating-a-dapp</guid>
      <pubDate>Tue, 18 Oct 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>Typescript</category>
      <category>next.js</category>
      <category>blockchain</category>
      <description><![CDATA[Decentralized applications, or DApps, are applications that run on a blockchain network. These applications are decentralized, meaning they…]]></description>
      <content:encoded><![CDATA[<p>Decentralized applications, or DApps, are applications that run on a blockchain network. These applications are decentralized, meaning they do not have a central authority or control, and are transparent and secure. In this post, we'll be using JavaScript and Remix to create a simple DApp.</p>
<h2 id="creating-the-smart-contrat">Creating the Smart contrat</h2>
<p>To create a DApp, you'll need to set up your development environment. There are development framework for Ethereum such as <a href="https://trufflesuite.com/">Truffle</a>, <a href="https://hardhat.org/">Hardhat</a> or <a href="https://getfoundry.sh/">Foundry</a> which are very powerful and easy to use, but I want to focus on the consuming that DApp, so we will be using <a href="https://remix.ethereum.org/">Remix</a> to simplify things a bit.</p>
<p>Remix is a Web Ide that supports creating and deploying solidity's smart contracts in Ethereum. The best is that it provides a few simple examples to start with and you can also compile them and get their ABI (Application Binary Interface) which we will need to call the smart contract from our site.</p>
<p>We can create a simple smart contract like the following which stores and retrive a string.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">pragma</span><span style="color:#72F088"> solidity</span><span style="color:#91CBFF"> ^0.8.0</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">contract</span><span style="color:#FFB757"> SimpleStorage</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#91CBFF">  string</span><span style="color:#FF9492"> private</span><span style="color:#F0F3F6"> data;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  function</span><span style="color:#DBB7FF"> setData</span><span style="color:#F0F3F6">(</span><span style="color:#91CBFF">string</span><span style="color:#FF9492"> memory</span><span style="color:#F0F3F6"> _data) </span><span style="color:#FF9492">public</span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">    data </span><span style="color:#FF9492">=</span><span style="color:#F0F3F6"> _data;</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  function</span><span style="color:#DBB7FF"> getData</span><span style="color:#F0F3F6">() </span><span style="color:#FF9492">public</span><span style="color:#FF9492"> view</span><span style="color:#FF9492"> returns</span><span style="color:#F0F3F6"> (</span><span style="color:#91CBFF">string</span><span style="color:#FF9492"> memory</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#FF9492">    return</span><span style="color:#F0F3F6"> data;</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>The ABI for that smart contract will look like the following.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">[</span></span>
<span class="line"><span style="color:#F0F3F6">  {</span></span>
<span class="line"><span style="color:#72F088">    "inputs"</span><span style="color:#F0F3F6">: [],</span></span>
<span class="line"><span style="color:#72F088">    "name"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"getData"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "outputs"</span><span style="color:#F0F3F6">: [</span></span>
<span class="line"><span style="color:#F0F3F6">      {</span></span>
<span class="line"><span style="color:#72F088">        "internalType"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"string"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">        "name"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">""</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">        "type"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"string"</span></span>
<span class="line"><span style="color:#F0F3F6">      }</span></span>
<span class="line"><span style="color:#F0F3F6">    ],</span></span>
<span class="line"><span style="color:#72F088">    "stateMutability"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"view"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "type"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"function"</span></span>
<span class="line"><span style="color:#F0F3F6">  },</span></span>
<span class="line"><span style="color:#F0F3F6">  {</span></span>
<span class="line"><span style="color:#72F088">    "inputs"</span><span style="color:#F0F3F6">: [</span></span>
<span class="line"><span style="color:#F0F3F6">      {</span></span>
<span class="line"><span style="color:#72F088">        "internalType"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"string"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">        "name"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"_data"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">        "type"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"string"</span></span>
<span class="line"><span style="color:#F0F3F6">      }</span></span>
<span class="line"><span style="color:#F0F3F6">    ],</span></span>
<span class="line"><span style="color:#72F088">    "name"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"setData"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "outputs"</span><span style="color:#F0F3F6">: [],</span></span>
<span class="line"><span style="color:#72F088">    "stateMutability"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"nonpayable"</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#72F088">    "type"</span><span style="color:#F0F3F6">: </span><span style="color:#ADDCFF">"function"</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"><span style="color:#F0F3F6">]</span></span></code></pre>
<h2 id="creating-the-project-structure">Creating the project structure</h2>
<p>Now that we have our Smart Contract, let's focus on the rest of the app. First, let's start creating a new empty project with <code>npm init -y</code>.</p>
<p>Next, we need to install the following dependencies:</p>
<ul>
<li><strong>web3.js</strong>: A JavaScript library for interacting with the Ethereum blockchain.</li>
<li><strong>remixd</strong>: A tool for connecting Remix with a local folder.</li>
<li><strong>http-server</strong>: A simple web server for serving our DApp.</li>
</ul>
<p>Run the following command to install these dependencies:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">npm</span><span style="color:#ADDCFF"> install</span><span style="color:#ADDCFF"> web3</span><span style="color:#ADDCFF"> remixd</span><span style="color:#ADDCFF"> http-server</span><span style="color:#91CBFF"> --save-dev</span></span></code></pre>
<h2 id="compiling-the-smart-contract">Compiling the smart contract</h2>
<p>Now, we need to compile the smart contract into bytecode that can be deployed to the Ethereum blockchain. Let's use Remix by starting the remixd daemon by running the following command in your terminal:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">remixd</span><span style="color:#91CBFF"> -s</span><span style="color:#ADDCFF"> ~/path/to/your/project</span></span></code></pre>
<p>This command will start a daemon that connects Remix with your project directory.</p>
<p>Next, open your web browser and navigate to <a href="https://remix.ethereum.org/">https://remix.ethereum.org/</a>. Remix should automatically connect to your local folder. If not, click the "Connect to Localhost" button and select the "Shared Folder" option. Then, navigate to your project directory and click "Connect".</p>
<p>Once you've connected to Remix, create a new file called "SimpleStorage.sol" and paste the code from our smart contract. Then, select the "Solidity Compiler" tab in the left-hand menu and click the "Compile SimpleStorage.sol" button. This will compile your smart contract into bytecode that can be deployed to the blockchain.</p>
<p>Now that we've compiled our smart contract, we need to deploy it to the Ethereum blockchain. For this example, we'll be deploying the contract to a local blockchain using Ganache.</p>
<p>To get started, install <a href="https://trufflesuite.com/ganache/">Ganache from the official website</a>. Once installed, open Ganache and start a new blockchain.</p>
<p>Next, we need to update our JavaScript code to include the address of the deployed smart contract. Open a new file called "app.js" in your project directory and paste the following code:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> Web3</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'web3'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> web3</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> new</span><span style="color:#DBB7FF"> Web3</span><span style="color:#F0F3F6">(</span><span style="color:#FF9492">new</span><span style="color:#F0F3F6"> Web3.providers.</span><span style="color:#DBB7FF">HttpProvider</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'http://localhost:7545'</span><span style="color:#F0F3F6">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> abi</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> [</span></span>
<span class="line"><span style="color:#F0F3F6">  {</span></span>
<span class="line"><span style="color:#F0F3F6">    inputs: [],</span></span>
<span class="line"><span style="color:#F0F3F6">    name: </span><span style="color:#ADDCFF">'getData'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">    outputs: [</span></span>
<span class="line"><span style="color:#F0F3F6">      {</span></span>
<span class="line"><span style="color:#F0F3F6">        internalType: </span><span style="color:#ADDCFF">'string'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">        name: </span><span style="color:#ADDCFF">''</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">        type: </span><span style="color:#ADDCFF">'string'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      },</span></span>
<span class="line"><span style="color:#F0F3F6">    ],</span></span>
<span class="line"><span style="color:#F0F3F6">    stateMutability: </span><span style="color:#ADDCFF">'view'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">    type: </span><span style="color:#ADDCFF">'function'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">  },</span></span>
<span class="line"><span style="color:#F0F3F6">  {</span></span>
<span class="line"><span style="color:#F0F3F6">    inputs: [</span></span>
<span class="line"><span style="color:#F0F3F6">      {</span></span>
<span class="line"><span style="color:#F0F3F6">        internalType: </span><span style="color:#ADDCFF">'string'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">        name: </span><span style="color:#ADDCFF">'_data'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">        type: </span><span style="color:#ADDCFF">'string'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      },</span></span>
<span class="line"><span style="color:#F0F3F6">    ],</span></span>
<span class="line"><span style="color:#F0F3F6">    name: </span><span style="color:#ADDCFF">'setData'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">    outputs: [],</span></span>
<span class="line"><span style="color:#F0F3F6">    stateMutability: </span><span style="color:#ADDCFF">'nonpayable'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">    type: </span><span style="color:#ADDCFF">'function'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">  },</span></span>
<span class="line"><span style="color:#F0F3F6">];</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> contractAddress</span><span style="color:#FF9492"> =</span><span style="color:#ADDCFF"> 'YOUR_CONTRACT_ADDRESS_HERE'</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> simpleStorageContract</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> new</span><span style="color:#F0F3F6"> web3.eth.</span><span style="color:#DBB7FF">Contract</span><span style="color:#F0F3F6">(abi, contractAddress);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F0F3F6">simpleStorageContract.methods</span></span>
<span class="line"><span style="color:#F0F3F6">  .</span><span style="color:#DBB7FF">setData</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Hello, world!'</span><span style="color:#F0F3F6">)</span></span>
<span class="line"><span style="color:#F0F3F6">  .</span><span style="color:#DBB7FF">send</span><span style="color:#F0F3F6">({ from: </span><span style="color:#ADDCFF">'YOUR_ACCOUNT_ADDRESS_HERE'</span><span style="color:#F0F3F6"> })</span></span>
<span class="line"><span style="color:#F0F3F6">  .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(() </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Data set successfully!'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">    return</span><span style="color:#F0F3F6"> simpleStorageContract.methods.</span><span style="color:#DBB7FF">getData</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">call</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#F0F3F6">  })</span></span>
<span class="line"><span style="color:#F0F3F6">  .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">data</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">`Data retrieved: ${</span><span style="color:#F0F3F6">data</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">  })</span></span>
<span class="line"><span style="color:#F0F3F6">  .</span><span style="color:#DBB7FF">catch</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">err</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">    console.</span><span style="color:#DBB7FF">error</span><span style="color:#F0F3F6">(err);</span></span>
<span class="line"><span style="color:#F0F3F6">  });</span></span></code></pre>
<p>This code creates a new instance of the Web3 library and sets the provider to a local Ganache blockchain. It also includes the ABI (Application Binary Interface) for our "SimpleStorage" contract, which defines the functions and data types of our contract.</p>
<p>Replace <code>YOUR_CONTRACT_ADDRESS_HERE</code> with the address of your deployed smart contract, and replace <code>YOUR_ACCOUNT_ADDRESS_HERE</code> with the address of your Ganache account.</p>
<p>This code also includes two functions: <code>setData</code> and <code>getData</code>. The <code>setData</code> function sets a string value on our smart contract using the <code>send</code> method, which requires a <code>from</code> address. The <code>getData</code> function retrieves the current value of the <code>data</code> variable using the <code>call</code> method.</p>
<h2 id="serving-the-dapp">Serving the DApp</h2>
<p>Finally, we need to serve our DApp to users. We'll be using the http-server package to serve our DApp. Open a new file called <code>server.js</code> in your project directory and paste the following code:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> http</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'http'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#F0F3F6"> { </span><span style="color:#91CBFF">URL</span><span style="color:#F0F3F6"> } </span><span style="color:#FF9492">=</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'url'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> httpServer</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'http-server'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> WebSocket</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'ws'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> remixd</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> require</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'remixd'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#BDC4CC">// Start Remixd daemon</span></span>
<span class="line"><span style="color:#F0F3F6">remixd.</span><span style="color:#DBB7FF">start</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'http://localhost:65520'</span><span style="color:#F0F3F6">, </span><span style="color:#ADDCFF">'/'</span><span style="color:#F0F3F6">, process.</span><span style="color:#DBB7FF">cwd</span><span style="color:#F0F3F6">());</span></span>
<span class="line"></span>
<span class="line"><span style="color:#BDC4CC">// Start HTTP server</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> server</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> httpServer.</span><span style="color:#DBB7FF">createServer</span><span style="color:#F0F3F6">({ root: __dirname });</span></span>
<span class="line"><span style="color:#F0F3F6">server.</span><span style="color:#DBB7FF">listen</span><span style="color:#F0F3F6">(</span><span style="color:#91CBFF">8080</span><span style="color:#F0F3F6">, () </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Server running on port 8080!'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#BDC4CC">// Start WebSocket server</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> wss</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> new</span><span style="color:#F0F3F6"> WebSocket.</span><span style="color:#DBB7FF">Server</span><span style="color:#F0F3F6">({ port: </span><span style="color:#91CBFF">8081</span><span style="color:#F0F3F6"> });</span></span>
<span class="line"><span style="color:#F0F3F6">wss.</span><span style="color:#DBB7FF">on</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'connection'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">ws</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> url</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> new</span><span style="color:#DBB7FF"> URL</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    ws.upgradeReq.url,</span></span>
<span class="line"><span style="color:#ADDCFF">    `http://${</span><span style="color:#F0F3F6">ws</span><span style="color:#ADDCFF">.</span><span style="color:#F0F3F6">upgradeReq</span><span style="color:#ADDCFF">.</span><span style="color:#F0F3F6">headers</span><span style="color:#ADDCFF">.</span><span style="color:#F0F3F6">host</span><span style="color:#ADDCFF">}`</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> path</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> url.pathname;</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> fileManager</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> remixd.</span><span style="color:#DBB7FF">getFileManager</span><span style="color:#F0F3F6">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F0F3F6">  fileManager.</span><span style="color:#DBB7FF">setWebSocket</span><span style="color:#F0F3F6">(ws, path);</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<p>This code starts a Remixd daemon that connects to your project directory, starts an HTTP server on port 8080, and starts a WebSocket server on port 8081. The WebSocket server is used to communicate between Remix and our DApp.</p>
<p>To serve our DApp, open your terminal and run the following command:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">node</span><span style="color:#ADDCFF"> server.js</span></span></code></pre>
<p>Once the server is running, open your web browser and navigate to <code>http://localhost:8080</code>. You should see a blank web page.</p>
<p>Now that we have a server running, let's add some HTML and JavaScript to our DApp. Create a new file called "index.html" in your project directory and paste the following code:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">&#x3C;!</span><span style="color:#72F088">DOCTYPE</span><span style="color:#91CBFF"> html</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">&#x3C;</span><span style="color:#72F088">html</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">  &#x3C;</span><span style="color:#72F088">head</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">meta</span><span style="color:#91CBFF"> charset</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"utf-8"</span><span style="color:#F0F3F6"> /></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">title</span><span style="color:#F0F3F6">>Simple Storage DApp&#x3C;/</span><span style="color:#72F088">title</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">  &#x3C;/</span><span style="color:#72F088">head</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">  &#x3C;</span><span style="color:#72F088">body</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">h1</span><span style="color:#F0F3F6">>Simple Storage DApp&#x3C;/</span><span style="color:#72F088">h1</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">form</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">      &#x3C;</span><span style="color:#72F088">label</span><span style="color:#91CBFF"> for</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"data"</span><span style="color:#F0F3F6">>Data:&#x3C;/</span><span style="color:#72F088">label</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">      &#x3C;</span><span style="color:#72F088">input</span><span style="color:#91CBFF"> type</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"text"</span><span style="color:#91CBFF"> id</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"data"</span><span style="color:#91CBFF"> name</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"data"</span><span style="color:#F0F3F6"> /></span></span>
<span class="line"><span style="color:#F0F3F6">      &#x3C;</span><span style="color:#72F088">button</span><span style="color:#91CBFF"> type</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"button"</span><span style="color:#91CBFF"> id</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"setDataButton"</span><span style="color:#F0F3F6">>Set Data&#x3C;/</span><span style="color:#72F088">button</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;/</span><span style="color:#72F088">form</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">p</span><span style="color:#91CBFF"> id</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"dataOutput"</span><span style="color:#F0F3F6">>&#x3C;/</span><span style="color:#72F088">p</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">    &#x3C;</span><span style="color:#72F088">script</span><span style="color:#91CBFF"> src</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"./index.js"</span><span style="color:#F0F3F6">>&#x3C;/</span><span style="color:#72F088">script</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">  &#x3C;/</span><span style="color:#72F088">body</span><span style="color:#F0F3F6">></span></span>
<span class="line"><span style="color:#F0F3F6">&#x3C;/</span><span style="color:#72F088">html</span><span style="color:#F0F3F6">></span></span></code></pre>
<p>This code creates a simple HTML form with a text input and a button. It also includes a script tag that references a new JavaScript file called <code>index.js</code>.</p>
<p>Create a new file called <code>index.js</code> in your project directory and paste the following code:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> setDataButton</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> document.</span><span style="color:#DBB7FF">getElementById</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'setDataButton'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> dataInput</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> document.</span><span style="color:#DBB7FF">getElementById</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'data'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"><span style="color:#FF9492">const</span><span style="color:#91CBFF"> dataOutput</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> document.</span><span style="color:#DBB7FF">getElementById</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'dataOutput'</span><span style="color:#F0F3F6">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F0F3F6">setDataButton.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'click'</span><span style="color:#F0F3F6">, </span><span style="color:#FF9492">async</span><span style="color:#F0F3F6"> () </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> data</span><span style="color:#FF9492"> =</span><span style="color:#F0F3F6"> dataInput.value;</span></span>
<span class="line"><span style="color:#FF9492">  await</span><span style="color:#F0F3F6"> simpleStorageContract.methods</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">setData</span><span style="color:#F0F3F6">(data)</span></span>
<span class="line"><span style="color:#F0F3F6">    .</span><span style="color:#DBB7FF">send</span><span style="color:#F0F3F6">({ from: </span><span style="color:#ADDCFF">'YOUR_ACCOUNT_ADDRESS_HERE'</span><span style="color:#F0F3F6"> });</span></span>
<span class="line"><span style="color:#FF9492">  const</span><span style="color:#91CBFF"> retrievedData</span><span style="color:#FF9492"> =</span><span style="color:#FF9492"> await</span><span style="color:#F0F3F6"> simpleStorageContract.methods.</span><span style="color:#DBB7FF">getData</span><span style="color:#F0F3F6">().</span><span style="color:#DBB7FF">call</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#F0F3F6">  dataOutput.innerHTML </span><span style="color:#FF9492">=</span><span style="color:#ADDCFF"> `Data retrieved: ${</span><span style="color:#F0F3F6">retrievedData</span><span style="color:#ADDCFF">}`</span><span style="color:#F0F3F6">;</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<p>This code adds an event listener to the "Set Data" button that retrieves the value of the text input, sets the value on the smart contract, retrieves the updated value, and displays it on the page.</p>
<p>Replace <code>YOUR_ACCOUNT_ADDRESS_HERE</code> with the address of your Ganache account.</p>
<h2 id="time-to-test-your-dapp">Time to test your Dapp</h2>
<p>Now that we have a server running and our HTML and JavaScript files are in place, let's test our DApp!</p>
<p>Open your terminal and navigate to your project directory. Run the following command to start the http-server.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">http-server</span></span></code></pre>
<p>Open a new terminal window and navigate to your project directory. Run the following command to start the WebSocket server:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FFB757">node</span><span style="color:#ADDCFF"> server.js</span></span></code></pre>
<p>Open your web browser and navigate to <code>http://localhost:8080</code>. You should see the "Simple Storage DApp" title, a text input, and a <code>Set Data</code> button.</p>
<p>Enter a string value in the text input and click the <code>Set Data</code> button. You should see the message <code>Data retrieved: [YOUR STRING VALUE]</code> displayed on the page.</p>
<p>Congratulations, you've created your first DApp with JavaScript and Remix! This is just the beginning of what's possible with decentralized applications. Keep exploring and experimenting to unlock the full potential of blockchain technology.</p>]]></content:encoded>
    </item>
    <item>
      <title>Service Worker Caching Strategies: Cache First, Network First and Stale-While-Revalidate</title>
      <link>https://nbellocam.dev/blog/caching-strategies</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/caching-strategies</guid>
      <pubDate>Fri, 12 Aug 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>ServiceWorker</category>
      <category>Caching</category>
      <category>PWA</category>
      <description><![CDATA[The main service worker caching strategies (cache first, network first, stale-while-revalidate) with working fetch handlers and when to use each one.]]></description>
      <content:encoded><![CDATA[<p>If you're looking to create a smooth and fast web experience for your users, one of the most important things you can do is to implement a robust caching strategy for your service worker. With a well-designed caching strategy, you can ensure that your web app loads quickly, even when the network is slow or offline.</p>
<p>In this post, we'll take a look at some of the key considerations for service worker caching, and some tips for developing a strategy that will help you optimize the performance of your web app.</p>
<h2 id="understanding-service-worker-caching">Understanding Service Worker Caching</h2>
<p>Before diving into specific caching strategies, let's take a moment to understand what service worker caching is and why it's important.</p>
<p>A service worker is a script that runs in the background of your web app and provides offline capabilities, background sync, and other features. One of its key features is the ability to cache network requests and serve cached responses instead of making a network request. This is particularly useful when a user visits your web app and their device is offline or has a slow network connection.</p>
<p>Service worker caching can be used to cache a wide range of content, including images, CSS files, JavaScript files, and even API responses. By caching this content, you can ensure that your web app loads quickly, even when the network is slow or offline.</p>
<h2 id="factors-to-consider-when-developing-a-service-worker-caching-strategy">Factors to Consider When Developing a Service Worker Caching Strategy</h2>
<p>There are several factors that you should consider when developing a service worker caching strategy. These include:</p>
<ul>
<li>
<p><strong>The size of your cache</strong>: The larger your cache, the longer it will take for the browser to download it, so you need to strike a balance between the size of your cache and the speed at which your web app loads.</p>
</li>
<li>
<p><strong>The frequency of updates</strong>: If your content changes frequently, you may want to consider using a cache with a shorter lifespan or implementing a strategy that ensures that your users are always using the most up-to-date version of your content.</p>
</li>
<li>
<p><strong>Network conditions</strong>: Different network conditions can affect the performance of your web app, so it's important to consider how your caching strategy will handle slow or offline network conditions.</p>
</li>
<li>
<p><strong>User behavior</strong>: The way that users interact with your web app can also affect your caching strategy. For example, if your users tend to visit the same pages frequently, you may want to prioritize caching those pages over others.</p>
</li>
<li>
<p><strong>The nature of the content</strong>: Not all the content needs to be cached and what's more, some of it may be wrong to cache it. For example if you have real-time information or a time-depending content you probably don't want to cache it.</p>
</li>
</ul>
<h2 id="strategies-for-service-worker-caching">Strategies for Service Worker Caching</h2>
<p>Now that we've discussed some of the factors that you should consider when developing a service worker caching strategy, let's take a look at some specific strategies that you can use to optimize the performance of your web app.</p>
<h3 id="cache-first-strategy">Cache-First Strategy</h3>
<p>The cache-first strategy is one of the most commonly used caching strategies for service workers. With this strategy, the service worker first looks for a response in the cache and serves it if it's available. If the response isn't in the cache, the service worker will make a network request for the content and cache the response for future use.</p>
<p>This strategy is effective for content that doesn't change frequently, such as images or CSS files, as it ensures that your users will always have a fast and reliable way to access this content, even when the network is slow or offline.</p>
<p>A possible implementation would be something like the following.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'fetch'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches.</span><span style="color:#DBB7FF">open</span><span style="color:#F0F3F6">(cacheName).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cache</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#F0F3F6"> cache.</span><span style="color:#DBB7FF">match</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cacheResponse</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">        if</span><span style="color:#F0F3F6"> (cacheResponse) {</span></span>
<span class="line"><span style="color:#FF9492">          return</span><span style="color:#F0F3F6"> cacheResponse;</span></span>
<span class="line"><span style="color:#F0F3F6">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">        return</span><span style="color:#DBB7FF"> fetch</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">networkResponse</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#BDC4CC">          // Only cache successful responses (not 404s or 500s).</span></span>
<span class="line"><span style="color:#FF9492">          if</span><span style="color:#F0F3F6"> (networkResponse.ok) {</span></span>
<span class="line"><span style="color:#F0F3F6">            cache.</span><span style="color:#DBB7FF">put</span><span style="color:#F0F3F6">(event.request, networkResponse.</span><span style="color:#DBB7FF">clone</span><span style="color:#F0F3F6">());</span></span>
<span class="line"><span style="color:#F0F3F6">          }</span></span>
<span class="line"><span style="color:#FF9492">          return</span><span style="color:#F0F3F6"> networkResponse;</span></span>
<span class="line"><span style="color:#F0F3F6">        });</span></span>
<span class="line"><span style="color:#F0F3F6">      });</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<h3 id="network-first-strategy">Network-First Strategy</h3>
<p>The network-first strategy is the opposite of the cache-first strategy. With this strategy, the service worker first makes a network request for the content and serves the response if it's available. If the network request fails, the service worker will then look for the response in the cache and serve it if it's available.</p>
<p>This strategy is effective for content that changes frequently and needs to be up-to-date, such as API responses. By prioritizing the network request, you ensure that your users always have access to the latest version of the content.</p>
<p>However, it's important to note that this strategy may result in slower page load times if the network request is slow or fails, as the service worker will need to wait for the network request to complete before serving the cached content.</p>
<p>A possible implementation would be something like the following. Note that this implementation is similar to the last one but in this case we are first fetching and if it works we added to the cache, while if the fetch fail (e.g., the user is offline), it will try to get the request from cache.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'fetch'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches.</span><span style="color:#DBB7FF">open</span><span style="color:#F0F3F6">(cacheName).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cache</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#DBB7FF"> fetch</span><span style="color:#F0F3F6">(event.request)</span></span>
<span class="line"><span style="color:#F0F3F6">        .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">networkResponse</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">          if</span><span style="color:#F0F3F6"> (networkResponse.ok) {</span></span>
<span class="line"><span style="color:#F0F3F6">            cache.</span><span style="color:#DBB7FF">put</span><span style="color:#F0F3F6">(event.request, networkResponse.</span><span style="color:#DBB7FF">clone</span><span style="color:#F0F3F6">());</span></span>
<span class="line"><span style="color:#F0F3F6">          }</span></span>
<span class="line"><span style="color:#FF9492">          return</span><span style="color:#F0F3F6"> networkResponse;</span></span>
<span class="line"><span style="color:#F0F3F6">        })</span></span>
<span class="line"><span style="color:#F0F3F6">        .</span><span style="color:#DBB7FF">catch</span><span style="color:#F0F3F6">(() </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">          return</span><span style="color:#F0F3F6"> cache.</span><span style="color:#DBB7FF">match</span><span style="color:#F0F3F6">(event.request);</span></span>
<span class="line"><span style="color:#F0F3F6">        });</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<h3 id="stale-while-revalidate-cache-and-network-strategy">Stale-While-Revalidate (Cache and Network) Strategy</h3>
<p>The cache and network strategy, usually called stale-while-revalidate, combines elements of the cache-first and network-first strategies. With this strategy, the service worker first looks for the response in the cache and serves it if it's available. At the same time, it also makes a network request for the content and updates the cache with the latest version of the content if it's available.</p>
<p>This strategy ensures that your users have access to the latest version of the content, while also providing a fast and reliable way to access the content if the network is slow or offline.</p>
<p>A possible implementation looks like this. The user gets the cached copy right away (if there is one), and the fresh response replaces it in the cache for the next visit.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'fetch'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches.</span><span style="color:#DBB7FF">open</span><span style="color:#F0F3F6">(cacheName).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cache</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#F0F3F6"> cache.</span><span style="color:#DBB7FF">match</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cacheResponse</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">        const</span><span style="color:#91CBFF"> networkFetch</span><span style="color:#FF9492"> =</span><span style="color:#DBB7FF"> fetch</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">networkResponse</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">          if</span><span style="color:#F0F3F6"> (networkResponse.ok) {</span></span>
<span class="line"><span style="color:#F0F3F6">            cache.</span><span style="color:#DBB7FF">put</span><span style="color:#F0F3F6">(event.request, networkResponse.</span><span style="color:#DBB7FF">clone</span><span style="color:#F0F3F6">());</span></span>
<span class="line"><span style="color:#F0F3F6">          }</span></span>
<span class="line"><span style="color:#FF9492">          return</span><span style="color:#F0F3F6"> networkResponse;</span></span>
<span class="line"><span style="color:#F0F3F6">        });</span></span>
<span class="line"><span style="color:#BDC4CC">        // Keep the service worker alive until the cache is updated.</span></span>
<span class="line"><span style="color:#F0F3F6">        event.</span><span style="color:#DBB7FF">waitUntil</span><span style="color:#F0F3F6">(networkFetch.</span><span style="color:#DBB7FF">catch</span><span style="color:#F0F3F6">(() </span><span style="color:#FF9492">=></span><span style="color:#F0F3F6"> {}));</span></span>
<span class="line"><span style="color:#FF9492">        return</span><span style="color:#F0F3F6"> cacheResponse </span><span style="color:#FF9492">||</span><span style="color:#F0F3F6"> networkFetch;</span></span>
<span class="line"><span style="color:#F0F3F6">      });</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<p>It's a good fit for things like avatars or non-critical API data, where showing a slightly old version is fine. If you'd rather not write these handlers by hand, <a href="https://developer.chrome.com/docs/workbox/modules/workbox-strategies">Workbox</a> ships all of these strategies (<code>CacheFirst</code>, <code>NetworkFirst</code>, <code>StaleWhileRevalidate</code>) ready to use.</p>
<h3 id="other-strategies">Other Strategies</h3>
<p>As you can imaging, there are plenty of other strategies specially because you could perform different strategies for different endpoinds or files.</p>
<p>For example, you could check if the request method and only intercept the request if it's a GET or you could respond with a network first approch if you are dealing with an API call and a caching first approach if not. The following code show something like this (consider the <code>fetchAndCache</code> as a network first approach).</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'fetch'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (event.request.method </span><span style="color:#FF9492">!==</span><span style="color:#ADDCFF"> 'GET'</span><span style="color:#F0F3F6">) </span><span style="color:#FF9492">return</span><span style="color:#F0F3F6">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">  if</span><span style="color:#F0F3F6"> (event.request.url.</span><span style="color:#DBB7FF">includes</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/api/'</span><span style="color:#F0F3F6">)) {</span></span>
<span class="line"><span style="color:#F0F3F6">    event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span><span style="color:#DBB7FF">fetchAndCache</span><span style="color:#F0F3F6">(event.request));</span></span>
<span class="line"><span style="color:#FF9492">    return</span><span style="color:#F0F3F6">; </span><span style="color:#BDC4CC">// respondWith can only be called once per event</span></span>
<span class="line"><span style="color:#F0F3F6">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches</span></span>
<span class="line"><span style="color:#F0F3F6">      .</span><span style="color:#DBB7FF">match</span><span style="color:#F0F3F6">(event.request)</span></span>
<span class="line"><span style="color:#F0F3F6">      .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">response</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> response </span><span style="color:#FF9492">||</span><span style="color:#DBB7FF"> fetchAndCache</span><span style="color:#F0F3F6">(event.request))</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Service worker caching is an essential component of optimizing the performance of your web app. By understanding the factors that you should consider when developing a caching strategy, and by using the right strategy for each request, you can ensure that your web app loads quickly and reliably for your users, even when the network is slow or offline.</p>]]></content:encoded>
    </item>
    <item>
      <title>Understanding Service Workers</title>
      <link>https://nbellocam.dev/blog/service-workers</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/service-workers</guid>
      <pubDate>Wed, 27 Jul 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>PWA</category>
      <description><![CDATA[What a service worker is, how its lifecycle (register, install, activate) works, and how to use it to cache assets and keep your web app working offline.]]></description>
      <content:encoded><![CDATA[<p>In the previous post I mentioned service workers several times as a key component for PWAs. Service workers are a crucial component in modern web development, enabling offline functionality and improving the performance of web applications. In this post, we'll dive into what service workers are, how they work, and how to implement them in your own web applications.</p>
<h2 id="what-are-service-workers">What are Service Workers?</h2>
<p>A service worker is a JavaScript file that it's executed in the background of a web page. It's separated from the main browser thread and it works in a full asyncronows way so you will not be able to use DOM APIs or syncronous APIs such as localstorage. It acts as a proxy between the web page and the network, intercepting network requests and controlling the behavior of the page in response to network events.</p>
<p>The most notable feature of service workers is their ability to provide offline functionality. With a service worker, you can cache assets and make them available to your users even when they're offline. This can greatly improve the user experience, especially on slow or unreliable networks.</p>
<h2 id="why-are-service-workers-important">Why are Service Workers Important?</h2>
<p>Service Workers offer several key benefits to web developers and users. Firstly, they allow for offline functionality, meaning that users can still access your web application even when they are not connected to the internet. This is particularly important for web applications that are used in areas with poor or unreliable network coverage, such as remote locations or on airplanes.</p>
<p>Additionally, Service Workers can be used to improve the performance of web applications. For example, by caching resources, Service Workers can reduce the amount of data that needs to be downloaded each time the user visits the website, resulting in faster load times.</p>
<p>Finally, Service Workers allow for push notifications and background sync capabilities, which can be used to deliver timely and relevant information to users, even when they are not actively using the web application.</p>
<h2 id="how-service-workers-work">How Service Workers Work</h2>
<p>Service workers are registered by your web application, after which they are installed and activated in the background. Once activated, they can listen for network events and respond to them.</p>
<p>Here's a simple example of how a service worker might work:</p>
<ol>
<li>The user visits your web page.</li>
<li>The browser checks to see if there's a service worker registered for the page.</li>
<li>If there is, the browser downloads and installs the service worker in the background.</li>
<li>The service worker is activated and starts listening for network events.</li>
<li>The user tries to access a resource that requires a network connection.</li>
<li>The service worker intercepts the network request and checks to see if the requested resource is available in its cache.</li>
<li>If it is, the service worker returns the cached resource to the page, bypassing the network entirely.</li>
<li>If it's not, the service worker makes the network request on behalf of the page, and caches the response for future use.</li>
</ol>
<h2 id="implementing-service-workers">Implementing Service Workers</h2>
<p>To implement service workers in your own web applications, you'll need to write a JavaScript file that acts as the service worker. You'll then need to register this file in your main JavaScript code, which will activate the service worker in the background.</p>
<p>Here's an example of how you might register a service worker in your main JavaScript file:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#FF9492">if</span><span style="color:#F0F3F6"> (</span><span style="color:#ADDCFF">'serviceWorker'</span><span style="color:#FF9492"> in</span><span style="color:#F0F3F6"> navigator) {</span></span>
<span class="line"><span style="color:#F0F3F6">  window.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'load'</span><span style="color:#F0F3F6">, </span><span style="color:#FF9492">function</span><span style="color:#F0F3F6"> () {</span></span>
<span class="line"><span style="color:#F0F3F6">    navigator.serviceWorker</span></span>
<span class="line"><span style="color:#F0F3F6">      .</span><span style="color:#DBB7FF">register</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'/service-worker.js'</span><span style="color:#F0F3F6">)</span></span>
<span class="line"><span style="color:#F0F3F6">      .</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FF9492">function</span><span style="color:#F0F3F6"> (</span><span style="color:#FFB757">registration</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#F0F3F6">        console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Service worker registered: '</span><span style="color:#F0F3F6">, registration.scope);</span></span>
<span class="line"><span style="color:#F0F3F6">      })</span></span>
<span class="line"><span style="color:#F0F3F6">      .</span><span style="color:#DBB7FF">catch</span><span style="color:#F0F3F6">(</span><span style="color:#FF9492">function</span><span style="color:#F0F3F6"> (</span><span style="color:#FFB757">error</span><span style="color:#F0F3F6">) {</span></span>
<span class="line"><span style="color:#F0F3F6">        console.</span><span style="color:#DBB7FF">log</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'Service worker registration failed: '</span><span style="color:#F0F3F6">, error);</span></span>
<span class="line"><span style="color:#F0F3F6">      });</span></span>
<span class="line"><span style="color:#F0F3F6">  });</span></span>
<span class="line"><span style="color:#F0F3F6">}</span></span></code></pre>
<p>Note that we first check if the serviceWorker feature is available in the browser, if so, then we register it. If not, the site should work without problems but you will not have the advantages of that the service worker provides.</p>
<p>Once the service worker is registered, you'll need to write the code that listens for network events and caches resources. There are many libraries and frameworks available that can help with this, such as Workbox, but it's also possible to write this code from scratch using the Service Worker API.</p>
<p>Here's an example of a simple service worker that caches all assets:</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'install'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">waitUntil</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches.</span><span style="color:#DBB7FF">open</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'v1'</span><span style="color:#F0F3F6">).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cache</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#F0F3F6"> cache.</span><span style="color:#DBB7FF">addAll</span><span style="color:#F0F3F6">([</span></span>
<span class="line"><span style="color:#ADDCFF">        '/'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#ADDCFF">        '/index.html'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#ADDCFF">        '/style.css'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#ADDCFF">        '/script.js'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#ADDCFF">        '/image.jpg'</span><span style="color:#F0F3F6">,</span></span>
<span class="line"><span style="color:#F0F3F6">      ]);</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F0F3F6">self.</span><span style="color:#DBB7FF">addEventListener</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'fetch'</span><span style="color:#F0F3F6">, </span><span style="color:#FFB757">event</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">  event.</span><span style="color:#DBB7FF">respondWith</span><span style="color:#F0F3F6">(</span></span>
<span class="line"><span style="color:#F0F3F6">    caches.</span><span style="color:#DBB7FF">match</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">response</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">      if</span><span style="color:#F0F3F6"> (response) {</span></span>
<span class="line"><span style="color:#FF9492">        return</span><span style="color:#F0F3F6"> response;</span></span>
<span class="line"><span style="color:#F0F3F6">      }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">      return</span><span style="color:#DBB7FF"> fetch</span><span style="color:#F0F3F6">(event.request).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">response</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#FF9492">        let</span><span style="color:#F0F3F6"> responseClone </span><span style="color:#FF9492">=</span><span style="color:#F0F3F6"> response.</span><span style="color:#DBB7FF">clone</span><span style="color:#F0F3F6">();</span></span>
<span class="line"><span style="color:#F0F3F6">        caches.</span><span style="color:#DBB7FF">open</span><span style="color:#F0F3F6">(</span><span style="color:#ADDCFF">'v1'</span><span style="color:#F0F3F6">).</span><span style="color:#DBB7FF">then</span><span style="color:#F0F3F6">(</span><span style="color:#FFB757">cache</span><span style="color:#FF9492"> =></span><span style="color:#F0F3F6"> {</span></span>
<span class="line"><span style="color:#F0F3F6">          cache.</span><span style="color:#DBB7FF">put</span><span style="color:#F0F3F6">(event.request, responseClone);</span></span>
<span class="line"><span style="color:#F0F3F6">        });</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FF9492">        return</span><span style="color:#F0F3F6"> response;</span></span>
<span class="line"><span style="color:#F0F3F6">      });</span></span>
<span class="line"><span style="color:#F0F3F6">    })</span></span>
<span class="line"><span style="color:#F0F3F6">  );</span></span>
<span class="line"><span style="color:#F0F3F6">});</span></span></code></pre>
<p>In this example, the <code>install</code> event is listened to, which occurs when the service worker is installed. The service worker caches all assets listed in the <code>cache.addAll</code> method.</p>
<p>The <code>fetch</code> event is also listened to, which occurs whenever a network request is made. The service worker intercepts the request and checks if it's available in its cache. If it is, the cached version is returned. If not, the service worker makes the network request, caches the response, and returns it to the page.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Service workers are a powerful tool for improving the performance and offline functionality of web applications. With their ability to intercept network requests and cache resources, they can greatly enhance the user experience. While there is a learning curve to implementing service workers, the benefits are well worth the effort. Try implementing them in your own web applications and see the results for yourself!</p>]]></content:encoded>
    </item>
    <item>
      <title>Progressive Web Apps</title>
      <link>https://nbellocam.dev/blog/progressive-web-apps</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/progressive-web-apps</guid>
      <pubDate>Thu, 07 Jul 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>Typescript</category>
      <category>PWA</category>
      <description><![CDATA[The world of web development has come a long way from the days of static HTML pages. Today, we have dynamic, responsive, and fast web…]]></description>
      <content:encoded><![CDATA[<p>The world of web development has come a long way from the days of static HTML pages. Today, we have dynamic, responsive, and fast web applications that can rival native apps in terms of user experience. Progressive Web Apps (PWAs) are the next step in this evolution, bringing the best of both web and native apps to provide an optimal user experience.</p>
<p>This is a topic I really enjoy and on will I worked a considerable amount of time. I gave talks and workshops as well as created tooling. In this article, we'll explore what PWAs are, why they're important, and how to get started with building your own.</p>
<h2 id="what-are-progressive-web-apps">What are Progressive Web Apps?</h2>
<p>PWAs are web applications that take advantage of modern web technologies to provide a native app-like experience to users. They are built with web technologies like HTML, CSS, and JavaScript and run in a browser, but they can also be installed on a user's device, just like a native app.</p>
<p>PWAs are designed to be fast, reliable, and accessible, even when a user is offline. They should load instantly, regardless of network speed, and provide a smooth and seamless experience. The user experience is the key of all this and we will be focusing on improving it at the best we can.</p>
<h2 id="the-benefits-of-pwas">The Benefits of PWAs</h2>
<ol>
<li>
<p><strong>Fast and reliable</strong>: PWAs are designed to load quickly, even on slow networks, and provide a seamless experience, regardless of network speed. One of the key aspect that facilitate this is that PWA take advantage of the service worker to cache data and resources, which can also enable us to making them available offline.</p>
</li>
<li>
<p><strong>Installable</strong>: PWAs can be installed on a user's device, just like a native app. This makes it easy for users to access the app from their home screen, without having to open a browser and navigate to the website. This is one of the most interesting benefits and it's simple to get it as you only need to have a the site behind https, a Web Manifest, and a service worker.</p>
</li>
<li>
<p><strong>Cross-platform</strong>: PWAs are built with web technologies and run in a browser, making them platform-independent. This means that a single PWA can run on multiple devices, including desktops, laptops, smartphones, and tablets. Having all those platforms at hand, enables you to reach more people than only targeting a subset of those with a native application which is a great win when deciding which tools to use.</p>
</li>
<li>
<p><strong>Discoverable</strong>: PWAs can be discovered through search engines, just like any other website. This makes it easy for users to find and install the app, without having to go through an app store. Not only that, you have the possibility to add your PWA in the native app stores using things like <a href="https://www.pwabuilder.com/">PWA Builder</a> and reach even more people.</p>
</li>
<li>
<p><strong>Lower development costs</strong>: PWAs can be developed and maintained with web technologies, which are typically more cost-effective than native app development. This means that it is possible to build a high-quality app at a lower cost. Having a single code source can be very helpful as you don't need to have different teams to build each app and you can write a feature once and take advantage of it everywhere.</p>
</li>
</ol>
<h2 id="getting-started-with-pwas">Getting Started with PWAs</h2>
<p>Building a PWA is straightforward and can be done using any modern web development stack. The basics are the same as any web development, define the user experience and design the app, build it using your prefered web development stack (make sure to use modern tools and frameworks to ensure that the app is fast, reliable, and accessible). Once it's ready, test it, optimize it, publish it and promote it.</p>
<p>This seems to be the usual cycle, right? So what's the special thing when building a PWA? You will add the Web App manifest and the service worker. For the Service worker you can use tools such as <a href="https://github.com/GoogleChrome/workbox">Workbox</a> which provide you with tools to make that without effort.</p>
<p>Additionally, you can take advantage of the new Web APIs under what is known as <a href="https://bugs.chromium.org/p/chromium/issues/list?q=proj-fugu">Project Fugu</a>, which enables you to add native features such as push notifications, access to usb ports or bluetooth devices.</p>
<h2 id="a-talk-in-spanish">A talk in spanish</h2>
<p>I talked about PWAs in several conferences but always in spanish. Anyway, if you want to see one of them, you can see the following one.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/IZT3byd-IFY?si=ps7kbaNXnLuKUCHm&#x26;start=193" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<h2 id="final-thoughts">Final thoughts</h2>
<p>Building PWAs is a great way to bring the best of both web and native apps to your users. Whether you're building a simple web app or a complex native app, PWAs are a powerful tool for delivering a high-quality user experience.</p>
<p>Progressive Web Apps are not always the future but the present of web development, providing fast, reliable, and accessible apps that can be installed on a user's device, just like a native app. With the benefits of lower development costs and cross-platform compatibility, PWAs are a great investment for businesses and developers alike.</p>
<p>To ensure the success of your PWA, it's important to focus on user experience, performance, and accessibility. This means using modern tools and frameworks, testing on multiple devices and platforms, and constantly iterating to improve the app.</p>
<p>PWAs are the next step in the evolution of web development, and their potential for providing a native-like user experience on the web is limitless. So if you're looking to build a high-quality app that is accessible, fast, and reliable, consider building a PWA.</p>]]></content:encoded>
    </item>
    <item>
      <title>Progressive Enhancement: The Key to a Better Web Experience</title>
      <link>https://nbellocam.dev/blog/progressive-enhancement</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/progressive-enhancement</guid>
      <pubDate>Sun, 19 Jun 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Web</category>
      <category>Typescript</category>
      <category>PWA</category>
      <description><![CDATA[In the rapidly changing world of technology, the web has become the primary platform for information and communication. With the…]]></description>
      <content:encoded><![CDATA[<p>In the rapidly changing world of technology, the web has become the primary platform for information and communication. With the proliferation of devices and browsers, it's more important than ever to ensure that websites are accessible to everyone, regardless of their hardware or network capabilities. This is where progressive enhancement comes in.</p>
<h2 id="what-is-progressive-enhancement">What is Progressive Enhancement?</h2>
<p>Progressive enhancement is a web development strategy that prioritizes the delivery of core functionality to all users, and then adds additional features and enhancements for those with more advanced browsers and devices. The basic idea is to start with a solid foundation and build up, ensuring that everyone can access the content and information they need, regardless of the technology they're using.</p>
<h2 id="why-is-progressive-enhancement-important">Why is Progressive Enhancement important?</h2>
<p>There are several reasons why progressive enhancement is critical for web development:</p>
<ul>
<li><strong>Accessibility</strong>: By providing core functionality to all users, progressive enhancement ensures that everyone, regardless of their abilities or disabilities, can access and use the website.</li>
<li><strong>User Experience</strong>: Progressive enhancement can enhance the user experience by providing a more responsive and tailored experience, based on the capabilities of the user's device and network.</li>
<li><strong>Future-proofing</strong>: By building a foundation that works for everyone, and then adding enhancements for those with more advanced technology, progressive enhancement ensures that your website will continue to work as technology evolves.</li>
</ul>
<h2 id="how-to-implement-progressive-enhancement">How to Implement Progressive Enhancement</h2>
<p>Implementing progressive enhancement involves several steps, including:</p>
<ol>
<li>
<p><strong>Determine the core functionality</strong>: The first step is to identify the core functionality of your website, such as the ability to read and navigate content, fill out forms, and access information. This should be available to all users, regardless of their technology.</p>
</li>
<li>
<p><strong>Use HTML as the foundation</strong>: HTML is the backbone of the web and provides the core structure and content of your website. By using well-structured HTML, you can ensure that the content and information is accessible to all users.</p>
</li>
<li>
<p><strong>Use CSS for styling</strong>: CSS is used to add style and visual design to your website. When used in conjunction with HTML, it can enhance the look and feel of your website, providing a more tailored experience for users with advanced browsers.</p>
</li>
<li>
<p><strong>Use JavaScript for interactivity</strong>: JavaScript is used to add interactivity to your website, such as drop-down menus, animations, and dynamic content. When used in conjunction with HTML and CSS, it can enhance the user experience for those with advanced browsers.</p>
</li>
<li>
<p><strong>Test and validate</strong>: Finally, it's important to test and validate your website to ensure that it works as intended on a variety of devices and browsers, and that the core functionality is available to all users.</p>
</li>
</ol>
<h2 id="an-example">An example</h2>
<p>The following is an example I like a lot about this, only based on HTML. It's a simple email input which have a lot of powerfull stuff on the back.</p>
<pre class="shiki github-dark-high-contrast" style="background-color:#0a0c10;color:#f0f3f6" tabindex="0"><code><span class="line"><span style="color:#F0F3F6">&#x3C;</span><span style="color:#72F088">input</span><span style="color:#91CBFF"> type</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"email"</span><span style="color:#91CBFF"> name</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"email"</span><span style="color:#91CBFF"> id</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"email"</span><span style="color:#91CBFF"> ​</span><span style="color:#91CBFF"> required</span><span style="color:#91CBFF"> aria-required</span><span style="color:#F0F3F6">=</span><span style="color:#ADDCFF">"true"</span><span style="color:#F0F3F6"> />​</span></span></code></pre>
<p>First, for older browsers, it will work as an standard text input, which will fulfill the core objective of getting the email from the user. If the browser is a bit newer, it will support the required tag, which will say to the user that they need to to add the information before submitting it. Additionally, if you are on mobile, you will probably get the special keyboard for email input (the one that has the <code>@</code> and the <code>.com</code> handly) which will improve your user's experience. Finally, the <code>aria-required="true"</code> tag will help people using readers and such having a better experience as well. A simple but powerful example on how you can focus on the objective and then improve your users experice by adding a few tags.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Progressive enhancement is a key strategy for web development, providing a better experience for all users and future-proofing your website. It's a key aspect to consider in general and a core aspect in things like Progresive Web Apps. By starting with a solid foundation and adding enhancements for those with more advanced technology, you can ensure that your website is accessible and usable for everyone. So if you're looking to enhance your web development skills, consider embracing progressive enhancement and build a better web for all.</p>]]></content:encoded>
    </item>
    <item>
      <title>Understanding Typescript utility Types</title>
      <link>https://nbellocam.dev/blog/typescript-utility-types</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/typescript-utility-types</guid>
      <pubDate>Fri, 03 Jun 2022 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Typescript</category>
      <description><![CDATA[A practical look at TypeScript's utility types Readonly, Partial, Pick and Omit, with small examples of when each one saves you from writing types by hand.]]></description>
    </item>
    <item>
      <title>Web Accessibility tools</title>
      <link>https://nbellocam.dev/blog/accessibility</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/accessibility</guid>
      <pubDate>Tue, 12 Oct 2021 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>web</category>
      <category>accessibility</category>
      <description><![CDATA["The Web is fundamentally designed to work for all people, whatever their hardware, software, language, culture, location, or physical or…]]></description>
    </item>
    <item>
      <title>The recursion problem</title>
      <link>https://nbellocam.dev/blog/recursion-problem</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/recursion-problem</guid>
      <pubDate>Mon, 23 Aug 2021 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>javascript</category>
      <description><![CDATA[I love recursion and functional programming, but you need to understand the consequences of doing some operations. A few days ago, we were…]]></description>
    </item>
    <item>
      <title>Getting started with Speech-to-text from Azure Speech Services</title>
      <link>https://nbellocam.dev/blog/speech-to-text</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/speech-to-text</guid>
      <pubDate>Tue, 03 Sep 2019 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>IA</category>
      <category>nodejs</category>
      <category>Azure</category>
      <category>Cognitive services</category>
      <description><![CDATA[Generating video subtitles with Azure Speech-to-text from Node.js: recognizeOnceAsync vs. continuous recognition, writing WebVTT, and extracting the audio.]]></description>
    </item>
    <item>
      <title>Creating an API with Azure Functions, Azure Cosmos DB and TypeScript</title>
      <link>https://nbellocam.dev/blog/functions-cosmos-typescript</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/functions-cosmos-typescript</guid>
      <pubDate>Sun, 18 Aug 2019 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Azure</category>
      <category>api</category>
      <category>Azure Functions</category>
      <category>Serverless</category>
      <category>TypeScript</category>
      <category>Azure Cosmos DB</category>
      <description><![CDATA[Building a small REST API with Azure Functions written in TypeScript, storing and reading data from Azure Cosmos DB through the function bindings.]]></description>
    </item>
    <item>
      <title>Build and deploy a React App with multi-stage pipelines</title>
      <link>https://nbellocam.dev/blog/multi-stage-pipelines</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/multi-stage-pipelines</guid>
      <pubDate>Sat, 03 Aug 2019 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>DevOps</category>
      <category>web development</category>
      <category>Azure Pipelines</category>
      <category>Azure</category>
      <category>react</category>
      <description><![CDATA[Nowadays, it's super easy and cheap to have a quick modern site deployed with Continuous Integration and Continuous Deployment. In this post…]]></description>
    </item>
    <item>
      <title>Introduction to Azure Pipelines</title>
      <link>https://nbellocam.dev/blog/intro-to-pipelines</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/intro-to-pipelines</guid>
      <pubDate>Sun, 14 Jul 2019 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>DevOps</category>
      <category>development</category>
      <category>Azure Pipelines</category>
      <category>Azure DevOps</category>
      <description><![CDATA[In my previous post, I mentioned that I was using Azure Pipelines to build and deploy this blog, so now I will share how you can do the…]]></description>
    </item>
    <item>
      <title>Migrating to Gatsby.js</title>
      <link>https://nbellocam.dev/blog/migrating-to-gatsbyjs</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/migrating-to-gatsbyjs</guid>
      <pubDate>Mon, 10 Jun 2019 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>web</category>
      <category>writing</category>
      <category>javascript</category>
      <category>firebase</category>
      <category>Azure DevOps</category>
      <description><![CDATA[Why I moved my blog from Medium to Gatsby.js: what I wanted from the new setup, how the migration went, deploying to Firebase with Azure DevOps, and SEO.]]></description>
    </item>
    <item>
      <title>Adding a guest user to an Office 365 SharePoint site with JavaScript and Azure Functions</title>
      <link>https://nbellocam.dev/blog/adding-a-guest-user-to-an-office-365-sharepoint-site-with-javascript-and-azure-functions</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/adding-a-guest-user-to-an-office-365-sharepoint-site-with-javascript-and-azure-functions</guid>
      <pubDate>Wed, 21 Feb 2018 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Office 365</category>
      <category>Nodejs</category>
      <category>Microsoft Graph</category>
      <category>Azure Functions</category>
      <category>Sharepoint Online</category>
      <description><![CDATA[We recently had a project where we needed to invite external users to an Office 365 SharePoint Site automatically when a Microsoft Flow was…]]></description>
    </item>
    <item>
      <title>Calling Microsoft Graph API from an Azure Function using JavaScript</title>
      <link>https://nbellocam.dev/blog/calling-microsoft-graph-api-from-an-azure-function-using-javascript</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/calling-microsoft-graph-api-from-an-azure-function-using-javascript</guid>
      <pubDate>Wed, 07 Feb 2018 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>Office 365</category>
      <category>Nodejs</category>
      <category>Azure Functions</category>
      <category>JavaScript</category>
      <description><![CDATA[Not so long ago, I worked on a project where we take advantage of the Microsoft Graph API to perform some tasks. So far, nothing special as…]]></description>
    </item>
    <item>
      <title>.NET Conf UY 2017 and Nodeconf Ar 2017</title>
      <link>https://nbellocam.dev/blog/dotnet-conf-uy-2017-and-nodeconf-ar-2017</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/dotnet-conf-uy-2017-and-nodeconf-ar-2017</guid>
      <pubDate>Wed, 01 Nov 2017 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>events</category>
      <category>Web Development</category>
      <category>Progressive Web App</category>
      <category>Conference</category>
      <category>Nodejs</category>
      <description><![CDATA[Last week I had an incredible experience being part at two of the most important developer conferences in South America. First, the .NET…]]></description>
    </item>
    <item>
      <title>JavascriptServices: ASP.NET Core meets node.js</title>
      <link>https://nbellocam.dev/blog/javascriptservices-asp-net-core</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/javascriptservices-asp-net-core</guid>
      <pubDate>Wed, 24 Aug 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>netcore</category>
      <category>javascript</category>
      <description><![CDATA[In the past, I talked about how to integrate Angular 2 and ASP.NET Core just by using gulp tasks and configuring ASP.NET Core. In one of…]]></description>
    </item>
    <item>
      <title>Creating a simple API with ASP.NET Core</title>
      <link>https://nbellocam.dev/blog/creating-simple-api-asp-net-core</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/creating-simple-api-asp-net-core</guid>
      <pubDate>Mon, 08 Aug 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>netcore</category>
      <category>api</category>
      <description><![CDATA[I posted about ASP.NET Core in the past. In this opportunity, I would like to share a simpler introduction to it using the nodejs' approach.…]]></description>
    </item>
    <item>
      <title>Manifoldjs, a tool to create hosted apps</title>
      <link>https://nbellocam.dev/blog/manifoldjs-hosted-web-apps</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/manifoldjs-hosted-web-apps</guid>
      <pubDate>Mon, 01 Aug 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>web</category>
      <category>manifoldjs</category>
      <category>pwa</category>
      <description><![CDATA[Modern web technologies are evolving not only to create faster and complex websites but also to create apps. There are plenty of examples…]]></description>
    </item>
    <item>
      <title>Introduction to Docker - Global Azure Bootcamp 2016</title>
      <link>https://nbellocam.dev/blog/introduction-docker-global-azure-bootcamp-2016</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/introduction-docker-global-azure-bootcamp-2016</guid>
      <pubDate>Sun, 17 Apr 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>docker</category>
      <category>azure</category>
      <description><![CDATA[Today as part of the Global Azure Bootcamp 2016 in Buenos Aires, Argentina; I give an introduction to Docker and what are the possibilities…]]></description>
    </item>
    <item>
      <title>Angular 2 Http with ASP.NET Core APIs</title>
      <link>https://nbellocam.dev/blog/angular-2-http-asp-net-core-apis</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/angular-2-http-asp-net-core-apis</guid>
      <pubDate>Wed, 23 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>netcore</category>
      <category>angular</category>
      <description><![CDATA[In my previous post, I explained how to configure routing in an application with Angular 2 and ASP.NET Core (if you need more information…]]></description>
    </item>
    <item>
      <title>Routing in Angular 2 with ASP.NET Core</title>
      <link>https://nbellocam.dev/blog/routing-angular-2-asp-net-core</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/routing-angular-2-asp-net-core</guid>
      <pubDate>Mon, 21 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>netcore</category>
      <category>angular</category>
      <description><![CDATA[Adding the Angular 2 router to an ASP.NET Core app: RouteConfig and routerLink on the client, and a SPA fallback route in Startup.cs so deep links work.]]></description>
    </item>
    <item>
      <title>ASP.NET Core and Angular 2</title>
      <link>https://nbellocam.dev/blog/asp-net-core-and-angular-2</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/asp-net-core-and-angular-2</guid>
      <pubDate>Mon, 14 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>netcore</category>
      <category>angular</category>
      <description><![CDATA[Writing web applications nowadays is becoming a bit more complex as the frameworks evolve taking advantage of other frameworks/tools. That…]]></description>
    </item>
    <item>
      <title>Automating the letsencrypt certificate renewal</title>
      <link>https://nbellocam.dev/blog/automating-the-letsencrypt-certificate-renewal</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/automating-the-letsencrypt-certificate-renewal</guid>
      <pubDate>Fri, 11 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>letsencrypt</category>
      <description><![CDATA[In my previous post I explain how to set up nginx running in a docker container to use a SSL certificate created by letsencrypt. The problem…]]></description>
    </item>
    <item>
      <title>Using letsencrypt with nginx on docker</title>
      <link>https://nbellocam.dev/blog/letsencrypt-and-nginx-on-docker</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/letsencrypt-and-nginx-on-docker</guid>
      <pubDate>Thu, 10 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>letsencrypt</category>
      <category>docker</category>
      <description><![CDATA[Getting free TLS certificates from Let's Encrypt and serving HTTPS from an nginx container on Docker, including the renewal step.]]></description>
    </item>
    <item>
      <title>Nginx for serving multiple sites in docker</title>
      <link>https://nbellocam.dev/blog/nginx-serving-multiple-sites-docker</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/nginx-serving-multiple-sites-docker</guid>
      <pubDate>Tue, 01 Mar 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>docker</category>
      <description><![CDATA[Using an nginx container as a reverse proxy to serve several sites on port 80 from a single Docker VM, with the docker-compose and nginx config files.]]></description>
    </item>
    <item>
      <title>WordPress plugins for the cloud</title>
      <link>https://nbellocam.dev/blog/wordpress-plugins-cloud</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/wordpress-plugins-cloud</guid>
      <pubDate>Sat, 27 Feb 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>web</category>
      <category>wordpress</category>
      <description><![CDATA[When I created this blog, I wanted to minimize the impact on the VM I was using by storing whatever I could outside the VM. Additionally, I…]]></description>
    </item>
    <item>
      <title>WordPress Docker container with Compose</title>
      <link>https://nbellocam.dev/blog/wordpress-docker-container-compose</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/wordpress-docker-container-compose</guid>
      <pubDate>Thu, 25 Feb 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>devops</category>
      <category>wordpress</category>
      <category>docker</category>
      <description><![CDATA[Running WordPress and MySQL as two containers on the same VM with Docker Compose: the compose file, volumes for persistence, and the environment variables.]]></description>
    </item>
    <item>
      <title>Creating a docker VM on Azure manually</title>
      <link>https://nbellocam.dev/blog/creating-docker-vm-azure-manually</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/creating-docker-vm-azure-manually</guid>
      <pubDate>Wed, 24 Feb 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>azure</category>
      <category>docker</category>
      <description><![CDATA[In order to create this site, I take the opportunity to use Docker creating a new VM on Azure. In this article I will share the steps I used…]]></description>
    </item>
    <item>
      <title>Hello world! About me</title>
      <link>https://nbellocam.dev/blog/hello-world</link>
      <guid isPermaLink="true">https://nbellocam.dev/blog/hello-world</guid>
      <pubDate>Thu, 11 Feb 2016 00:00:00 GMT</pubDate>
      <dc:language>en</dc:language>
      <category>me</category>
      <category>about</category>
      <description><![CDATA[Hi, I'm Nicolás Bello Camilletti, a software developer that loves new technologies and new exciting challenges. Because of that, I'm…]]></description>
    </item>
  </channel>
</rss>
