<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max Niederman</title>
    <description>The latest articles on DEV Community by Max Niederman (@maxniederman).</description>
    <link>https://dev.to/maxniederman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F436823%2F9d49c4dd-dfe4-4fe0-be25-0e00a6470c2e.png</url>
      <title>DEV Community: Max Niederman</title>
      <link>https://dev.to/maxniederman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxniederman"/>
    <language>en</language>
    <item>
      <title>On the Width of Webpages</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Thu, 14 Jul 2022 04:56:23 +0000</pubDate>
      <link>https://dev.to/maxniederman/on-the-width-of-webpages-jch</link>
      <guid>https://dev.to/maxniederman/on-the-width-of-webpages-jch</guid>
      <description>&lt;p&gt;Websites should never be wider than their viewports. I think that most people understand this, but it’s incredible how often I come across sites which get this wrong. It seems that every other site I read on my phone looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4oa5UPZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wbgb1n73nre0vjll675e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oa5UPZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wbgb1n73nre0vjll675e.png" alt="A flawed but common mobile layout." width="369" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An actual phone, of course, cannot simply render the content outside of the screen. Instead, browsers handle overflowing by allowing users to move the viewport by scrolling.&lt;/p&gt;

&lt;p&gt;This is a good default for &lt;em&gt;horizontal&lt;/em&gt; overflow, but it's usually bad for vertical overflow. When mobile users scroll vertically, it is easy to accidentally scroll horizontally as well, which can be extremely distracting from the content.&lt;/p&gt;

&lt;p&gt;Most web developers test their sites primarily on desktop browsers, where the viewport is much wider, so it's easy not to notice the issue. Even when developers test using desktop browsers' mobile emulation tools, they typically use a mouse or touchpad, so they are less likely to accidentally scroll horizontally.&lt;/p&gt;

&lt;p&gt;Web developers should adopt the following priniples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whenever possible, the design should respond to the viewport's width rather than rely on users to scroll.&lt;/li&gt;
&lt;li&gt;Failing that, large elements should scroll &lt;em&gt;individually&lt;/em&gt;, using &lt;code&gt;overflow-x: scroll&lt;/code&gt;, rather than force the entire body to widen.&lt;/li&gt;
&lt;li&gt;Websites should be tested in an environment as close as possible to the one in which they will be used.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>ttyper - a Terminal-Based Typing Test</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Mon, 15 Mar 2021 20:32:48 +0000</pubDate>
      <link>https://dev.to/maxniederman/ttyper-a-terminal-based-typing-test-2ih7</link>
      <guid>https://dev.to/maxniederman/ttyper-a-terminal-based-typing-test-2ih7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published &lt;a href="https://maxniederman.com/ttyper" rel="noopener noreferrer"&gt;here&lt;/a&gt; on my blog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TL;DR: &lt;a href="https://github.com/max-niederman/ttyper" rel="noopener noreferrer"&gt;here&lt;/a&gt;'s the project on GitHub and it's installable as &lt;code&gt;ttyper&lt;/code&gt; from Crates.io.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I was already pretty happy with the typing test I used to use, &lt;a href="https://monkeytype.com" rel="noopener noreferrer"&gt;monkeytype&lt;/a&gt;; however, I'm a big fan of CLI tools, so I wanted to be able to practice my typing without reaching for a browser or even a native application. I searched around for terminal-based typing tests, but none of them were quite what I had in mind.&lt;/p&gt;

&lt;p&gt;Since there weren't any pre-existing tools which meant my needs, I thought it would be a good opportunity to learning about TUIs (terminal user interfaces) to make one myself. I decided to use Rust with &lt;a href="https://github.com/fdehau/tui-rs" rel="noopener noreferrer"&gt;tui-rs&lt;/a&gt;, after being inspired by tools built with it such as &lt;a href="https://github.com/extrawurst/gitui" rel="noopener noreferrer"&gt;gitui&lt;/a&gt;, &lt;a href="https://github.com/imsnif/bandwhich" rel="noopener noreferrer"&gt;bandwhich&lt;/a&gt;, and &lt;a href="https://github.com/imsnif/diskonaut" rel="noopener noreferrer"&gt;diskonaut&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal Typing Test
&lt;/h2&gt;

&lt;p&gt;I decided to go with a fairly simple, tried-and-true design: a list of words and an input box where you type each word one-by-one. I wanted an experience similar to &lt;a href="https://typings.gg/" rel="noopener noreferrer"&gt;typings.gg&lt;/a&gt; or &lt;a href="https://10fastfingers.com" rel="noopener noreferrer"&gt;10fastfingers&lt;/a&gt;, but with the ability to return to a previous word after submission.&lt;/p&gt;

&lt;p&gt;Here's what the final interface for the test input looks like:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2995s6j9n5v70w9bvwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2995s6j9n5v70w9bvwi.png" alt="Test TUI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding WPM and Accuracy
&lt;/h2&gt;

&lt;p&gt;Calculating the results of the test was far harder than I had originally expected, althouhg this may be due in part to the fact that I wanted more insights into my typing performance, such as keywise accuracy and WPM data, as well as a WPM graph.&lt;/p&gt;

&lt;p&gt;First of all, I represented the test, more or less, as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;termion&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;correct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You can read the real code &lt;a href="https://github.com/max-niederman/ttyper/blob/main/src/test/mod.rs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this way, each "event" (keypress) can have a correctness value. Then, to find the accuracy, we can divide the number of correct events by the total number of events with correctness.&lt;/p&gt;

&lt;p&gt;We can also find the WPM per each event from its time and its predecessor's time.&lt;/p&gt;

&lt;p&gt;There's still a lot of things I want to change with ttyper's results calculations and UI, like adding a WPM over time graph and fixing weirdness with the overall WPM calculations, but it's working well enough for someone like me, who doesn't care too much about the exact WPM as long as I'm improving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Contents
&lt;/h2&gt;

&lt;p&gt;For the test contents, I added an optional argument to specify a file to read, which is then split at whitespace and carriage returns. Then, I added functionality for automatically generating test content. It works by reading a language file and populating a list with a certain number of random lines from that file. Currently, &lt;code&gt;english100&lt;/code&gt;, &lt;code&gt;english200&lt;/code&gt;, and &lt;code&gt;english1000&lt;/code&gt; are installed automatically. In the future, I'd like to add weighted language files based on n-gram analysis to make the test contents more realistic.&lt;/p&gt;

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

&lt;p&gt;If you want to try ttyper our for yourself, you can install it with Cargo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install ttyper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>showdev</category>
      <category>rust</category>
    </item>
    <item>
      <title>Netlify vs. Vercel: A Comparison</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Sun, 04 Oct 2020 17:03:53 +0000</pubDate>
      <link>https://dev.to/maxniederman/netlify-vs-vercel-a-comparison-5643</link>
      <guid>https://dev.to/maxniederman/netlify-vs-vercel-a-comparison-5643</guid>
      <description>&lt;p&gt;Netlify and Vercel are serverless deployment platforms for web applications, both designed to allow you to deploy as quickly and easily as possible. Both have very similar feature sets, so which one should you use? In this article, I'll be answering that question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build + Edge = 💕
&lt;/h3&gt;

&lt;p&gt;The core feature set of both Netlify and Vercel is the build + Edge stack. Essentially, you trigger a build step either by using Git or uploading manually. Once built, your app is automatically deployed to Netlify or Vercel's Edge Network to ensure a fast UX. This makes it incredibly easy to steup a static site (e.g. a blog like this one), since all you have to do is push your code. They're fairly equal in this matter, so I wouldn't consider either to be better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Functions
&lt;/h3&gt;

&lt;p&gt;Static sites are all well and good, but most applications need a backend to function. For this purpose, both Netlify and Vercel offer serverless functions via AWS Lambda.&lt;/p&gt;

&lt;h4&gt;
  
  
  Netlify
&lt;/h4&gt;

&lt;p&gt;For Netlify, you create a &lt;code&gt;functions&lt;/code&gt; directory in your project folder. You can then add AWS Lambda handlers in JavaScript or Go. Routing is handled based on the directory structure. You can read more at the official documentation &lt;a href="https://docs.netlify.com/functions/overview"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Vercel
&lt;/h4&gt;

&lt;p&gt;Vercel has a very similar interface, using an &lt;code&gt;api&lt;/code&gt; directory in your project, but it pulls out ahead in the features department with its support for Python and Ruby as well as JavaScript and Go, and it also supports a custom Express.js-like &lt;code&gt;(req, res) =&amp;gt; {...}&lt;/code&gt; format for JavaScript functions, as well as optional Edge caching for responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Side Rendering
&lt;/h3&gt;

&lt;p&gt;Server-Side rendering, or SSR, is a huge win for Vercel, as it's just not really feasible with Netlify. On Vercel, you can deploy a server-side rendered Next.js application within minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Netlify has a built-in authentication platform based on the &lt;a href="https://www.gotrueapi.org/"&gt;GoTrue API&lt;/a&gt; which you can easily setup. With Vercel, you'll have to setup authentication some other way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend for Static Sites
&lt;/h3&gt;

&lt;p&gt;Netlify provides three features which make it trivially easy to add common functionality to your site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify Forms: Manage forms and submissions without setting up any backend&lt;/li&gt;
&lt;li&gt;Netlify Analytics (Paid): Server-side analytics you can setup with no client-side code whatsoever&lt;/li&gt;
&lt;li&gt;Split Testing: Netlify allows you to easily A/B test two deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;Both Netlify and Vercel have generous free plans. You can see the full pricing info &lt;a href="https://www.netlify.com/pricing/"&gt;here&lt;/a&gt;, &lt;a href="https://vercel.com/pricing"&gt;here&lt;/a&gt;, and &lt;a href="https://vercel.com/docs/platform/fair-use-policy"&gt;here&lt;/a&gt;, but here's a quick comparison:&lt;/p&gt;

&lt;h4&gt;
  
  
  Build
&lt;/h4&gt;

&lt;p&gt;Vercel is ahead here, with a soft limit of 100 build hours / month for free and 400 with the Pro plan ($20/month per member) compared to Netlify's 300 build minutes / month free and then $7/500 minutes. Most projects won't need anywhere near this much time anyway, so it probably won't matter for you.&lt;/p&gt;

&lt;h4&gt;
  
  
  Edge
&lt;/h4&gt;

&lt;p&gt;Both Netlify and Vercel give you 100GB of bandwidth free (although that's a soft limit for Vercel). Netlify sells additional bandwidth at $20/100 and Vercel gives you 1 TB with the Pro plan (also a soft limit).&lt;/p&gt;

&lt;h4&gt;
  
  
  Serverless Functions
&lt;/h4&gt;

&lt;p&gt;Netlify bills based on the number of invocations, whereas Vercel bills based on GB-hours since you can customize your serveless function instances. Netlify gives you 125k invocations free, and then charges "$25+ when exceeded" (your guess is as good as mine). Vercel gives you 100GB-hours free, and 1000GB-hours with the Pro plan.&lt;/p&gt;

&lt;h4&gt;
  
  
  Netlify Forms, Identity, and Analytics
&lt;/h4&gt;

&lt;p&gt;Forms get 100 free submissions, then cost "$19+ when exceeded." Identity gives you 1000 free monthly active users, then charges $99 when exceeded. Analytics costs a flat rate of $9/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;We can see a clear divergence of interests between Netlify and Vercel, two extremely similar products at first glance. Netlify is focused on making it easy to create client-side, static applications, and delivers on that goal wonderfully. Vercel, on the other hand, focuses on making back-end work simple. If you're creating a static site, Netlify is the clear choice, but if you need more server-side functionality and you don't mind spending a little time, Vercel is the way to go.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Why you should try using Deno</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Sun, 27 Sep 2020 18:18:18 +0000</pubDate>
      <link>https://dev.to/maxniederman/why-you-should-try-using-deno-11j</link>
      <guid>https://dev.to/maxniederman/why-you-should-try-using-deno-11j</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published &lt;a href="https://maxniederman.com/deno-advantages"&gt;here&lt;/a&gt; on my blog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://deno.land"&gt;Deno's website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure by default. No file, network, or environment access, unless explicitly enabled.&lt;/li&gt;
&lt;li&gt;Supports TypeScript out of the box.&lt;/li&gt;
&lt;li&gt;Ships only a single executable file.&lt;/li&gt;
&lt;li&gt;Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).&lt;/li&gt;
&lt;li&gt;Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://deno.land"&gt;Deno&lt;/a&gt; aims to replace Node.js as a JS runtime for servers. It's even led by the original creator of Node.js, Ryan Dahl, and it's created with the goal of fixing the biggest problems with Node.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Main Advantage of Deno
&lt;/h3&gt;

&lt;p&gt;Have you ever started working on a Node project and ended up spending upwards of 15 minutes &lt;em&gt;just to setup&lt;/em&gt; all of your additional tooling like comilers (TypeScript, Babel, CoffeeScript, etc.), testing frameworks (Jest, Cypress, Mocha, Chai, etc.), linters and formatters, and more. I know I sure have, and it really sucks. I want to get coding as quickly as possible, and Deno is vastly superior to Node in this matter. Deno has the following features built in, no configuration necessary:&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-in TypeScript Support
&lt;/h4&gt;

&lt;p&gt;Deno will run your TypeScript code without ever having to touch a &lt;code&gt;tsconfig.json&lt;/code&gt; file. This way you can focus on your code, rather than transforming it to JS. You can also use your existing JavaScript code as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-in Testing
&lt;/h4&gt;

&lt;p&gt;Deno has a built-in test runner that you can use for testing JavaScript or TypeScript code. You can write tests inline in your files like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 plus 2 equals 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run all tests using the &lt;code&gt;deno&lt;/code&gt; executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about Deno's tests &lt;a href="https://deno.land/manual/testing"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-in Linter and Formatter
&lt;/h4&gt;

&lt;p&gt;Deno has both a linter and formatter built-in, available through the &lt;code&gt;deno&lt;/code&gt; binary. You can read the linter docs &lt;a href="https://deno.land/manual/tools/linter"&gt;here&lt;/a&gt; and the formatter docs &lt;a href="https://deno.land/manual/tools/formatter"&gt;here&lt;/a&gt;. No need to setup your own unless you want to.&lt;/p&gt;

&lt;h4&gt;
  
  
  Built-in Bundler
&lt;/h4&gt;

&lt;p&gt;Deno has a built-in bundler to package your code, so you don't need to setup your own bundler either.&lt;/p&gt;




&lt;p&gt;Deno also has some other advantages over writing in Node:&lt;/p&gt;

&lt;h4&gt;
  
  
  Secure by Default
&lt;/h4&gt;

&lt;p&gt;Access to the filesystem, network, environment, and the ability to run subprocesses all must be explicitly enabled with CLI options. You can even allow access to only certain hosts or files.&lt;/p&gt;

&lt;h4&gt;
  
  
  Browser compatibility
&lt;/h4&gt;

&lt;p&gt;Node.js lacks a lot of browser APIs. For instance, if you wanted to fetch a resource over HTTP, you could just use the &lt;code&gt;fetch&lt;/code&gt; api. In Node, you would have to import a whole library just to do this. This is not the case for Deno. Deno includes the majority of browser APIs. In fact, Deno code is designed to be as compatible as possible with the browser. Anything specific to Deno is under the &lt;code&gt;Deno&lt;/code&gt; namespace, so if you remove that from your code, it should run in any modern browser. It can also run WebAssembly binaries.&lt;/p&gt;




&lt;p&gt;These are some of the reasons that I personally use Deno rather than Node whenver I can.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Web analytics from scratch - Part 2: The Client</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Sun, 20 Sep 2020 19:10:36 +0000</pubDate>
      <link>https://dev.to/maxniederman/web-analytics-from-scratch-part-2-the-client-5547</link>
      <guid>https://dev.to/maxniederman/web-analytics-from-scratch-part-2-the-client-5547</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published &lt;a href="https://maxniederman.com/scratch-analytics/2-the-client"&gt;here&lt;/a&gt; on my blog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is Part 2 of this series. You can read the first part &lt;a href="https://maxniederman.com/scratch-analytics/1-page-views"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracker Client
&lt;/h3&gt;

&lt;p&gt;Now that we have a server which can keep track of our analytics data, we need to add a client which will tell the server the page has been loaded.&lt;br&gt;
This is extremely simple since we only have to send a single request with two JSON properties when the page loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;analytics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiBase&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tracker`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can just load and call it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"analytics.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://yourserver.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's really all it takes to implement our super-simple analytics on the client side. This has the added benefit that the bundle size of the client is negligible (&amp;lt;250B unminified).&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Analytics Data
&lt;/h3&gt;

&lt;p&gt;We have a way to collect and store analytics data, but now we need a way to get it.&lt;/p&gt;

&lt;p&gt;We can already look at the data using &lt;code&gt;redis-cli&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; HGETALL &amp;lt;YOUR_HOST&amp;gt;:resources
1) "/"
2) "42"
3) "/index.html"
4) "7"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this isn't a great solution for quickly looking at analytics data. We want some way to present this data to the user nicely. We can do this by adding an endpoint to our API to get this data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hgetall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:resource`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we call this by making an HTTP request with a &lt;code&gt;host&lt;/code&gt; query parameter and get back some JSON like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you might notice that the numbers are, in fact, strings. This is because Redis stores everything as a string, so we'll need to cast it to a number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper function to cast Object values&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;castValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;castFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;castFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hgetall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:resource`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Cast values to numbers&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;castValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it'll reply this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;In the next part of this series, we'll add more statistics and work on a visual dashboard.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Web analytics from scratch - Part 1: Page Views</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Sun, 13 Sep 2020 20:18:05 +0000</pubDate>
      <link>https://dev.to/maxniederman/web-analytics-from-scratch-part-1-page-views-33ag</link>
      <guid>https://dev.to/maxniederman/web-analytics-from-scratch-part-1-page-views-33ag</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published &lt;a href="https://maxniederman.com/scratch-analytics/1-page-views"&gt;here&lt;/a&gt; on my blog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Google Analytics is used in nearly 70% of websites for a good reason. It's a very powerful tool, and more importantly, it's free and easy to use, but there's a downside: Google owns and uses all of your data. "Just use Matamo or Open Web Analytics!" I hear you say, and you're right, that's the easiest solution, but I think we can all agree it's no fun.&lt;/p&gt;

&lt;p&gt;In this series we'll be creating a simple website analytics service from scratch. This part will be about creating a basic API to keep track of raw page views, but more statistics will obviously be added later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a Web Server
&lt;/h3&gt;

&lt;p&gt;First we'll need to setup a basic web server. I'll be using &lt;a href="https://fastify.io"&gt;Fastify&lt;/a&gt;, but it shouldn't be too difficult to use Express or another framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Routes&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a web server, we can add a route the client will use when it loads the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FastifyPluginCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FastifyPluginCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Record client data&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Recorded analytics data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then register it in &lt;code&gt;app.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Storing data
&lt;/h3&gt;

&lt;p&gt;Now that the client can send us information, we'll need a way to store it. Since we won't be storing personal data as per the GDPR and CCPA, we'll essentially just be storing a bunch of counters. I decided &lt;a href="https://redis.io"&gt;Redis&lt;/a&gt; would be perfect for this.&lt;/p&gt;

&lt;p&gt;With Fastify, we can share a single Redis client using the &lt;code&gt;fastify-redis&lt;/code&gt; plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fastifyRedis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify-redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastifyRedis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_HOST&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can keep track of views like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;views&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Recorded analytics data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we then call this endpoint a few times we can see the count in our redis console like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; GET views
"3"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pageviews
&lt;/h3&gt;

&lt;p&gt;We'll obviously want to store more data, like views per-page. First, we can add a &lt;code&gt;resource&lt;/code&gt; property to the tracker endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hincrby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:resources`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Recorded analytics data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Stay tuned for the next part of this series, in which we'll be creating a client that can log this information, and endpoints to get analytics data once it's recorded.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How anyone can make and deploy a modern website for free</title>
      <dc:creator>Max Niederman</dc:creator>
      <pubDate>Sun, 06 Sep 2020 18:34:31 +0000</pubDate>
      <link>https://dev.to/maxniederman/how-anyone-can-make-and-deploy-a-modern-website-for-free-3pom</link>
      <guid>https://dev.to/maxniederman/how-anyone-can-make-and-deploy-a-modern-website-for-free-3pom</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://maxniederman.com/easy-website"&gt;maxniederman.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Making a website today is easier than ever. With tools like &lt;a href="https://squarespace.com"&gt;Squarespace&lt;/a&gt; and &lt;a href="https://wix.com"&gt;Wix&lt;/a&gt; you can easily create an appealing (if boring) website using no code at all.&lt;/p&gt;

&lt;p&gt;This is a great solution for most; however, it comes with some disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost: Most site builders will cost you &lt;em&gt;at least&lt;/em&gt; $10 per month.&lt;/li&gt;
&lt;li&gt;Lack of Customization: Services like Squarespace provide numerous themes. These are good, but Site Builders usually lack granular control over your site.&lt;/li&gt;
&lt;li&gt;Dynamic Content &amp;amp; Functionality: Site Builders like these won't allow you to add custom functionality to your site or give you the ability to serve dynamic content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the slightly more tech-savvy, there's a better solution anyone can do for free: using website templates you can host for free using services like &lt;a href="https://pages.github.com"&gt;GitHub Pages&lt;/a&gt; or &lt;a href="https://netlify.com"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll be demonstrating how you can quickly and easily setup and host a website using &lt;a href="https://gatsbyjs.org"&gt;Gatsby&lt;/a&gt;, a static site generator which pre-renders React components, along with Netlify for hosting. It's popular for simple static websites and has an extensive template library which we'll be taking advantage of. There are many other alternatives you could use, like &lt;a href="https://jekyllrb.com"&gt;Jekyll&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we'll need to install the Gatsby CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g gatsby
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a Gatsby starter
&lt;/h3&gt;

&lt;p&gt;Now, we'll need to find a Gatsby template from the &lt;a href="https://www.gatsbyjs.com/starters/?v=2"&gt;Starter Library&lt;/a&gt;. I'll be using &lt;a href="https://lekoarts.de/en"&gt;Leko Arts&lt;/a&gt;' &lt;a href="https://github.com/LekoArts/gatsby-starter-portfolio-cara"&gt;Cara Theme&lt;/a&gt;. Each starter will have different instructions for modifying their content, so you should follow the instructions for your starter. I'll show the process for the Cara theme.&lt;/p&gt;

&lt;p&gt;Then, download your starter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gatsby new my-awesome-website https://github.com/LekoArts/gatsby-starter-portfolio-cara
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next part will be different depending on your template, but generally you'll need to modify some files with your content. Most starters will have instructions to modify the content. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gatsby develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to see your changes whenever you save. You don't even have to reload!&lt;/p&gt;

&lt;h3&gt;
  
  
  Modifying the starter
&lt;/h3&gt;

&lt;p&gt;Templates are great, but sometimes you need to tweak the template to fit your needs. Thankfully, Gatsby makes this super easy with Shadowing! Shadowing allows you to overwrite files from your theme. You can read the full guide &lt;a href="https://www.gatsbyjs.com/docs/themes/shadowing/"&gt;here&lt;/a&gt; but essentially you create a directory inside &lt;code&gt;src/&lt;/code&gt; with the name of your theme. For my template, it would be &lt;code&gt;src/@lekoarts/gatsby-theme-cara/&lt;/code&gt;. Any file you put there will shadow (overwrite) the theme's source code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying
&lt;/h3&gt;

&lt;p&gt;Once you're happy with your site you'll need to host it. Thankfully, Netlify has made this process a breeze.&lt;/p&gt;

&lt;p&gt;First, upload your site to GitHub or GitLab. This isn't a requirement but it'll make deployment much simpler. Then, you'll need to create a free Netlify account &lt;a href="https://netlify.com/signup"&gt;here&lt;/a&gt;. On your Netlify dashboard, you'll see a button to create a new site from Git. Click that and select the repository you uploaded.&lt;/p&gt;

&lt;p&gt;Click "Deploy site" and you're done!&lt;/p&gt;

&lt;h3&gt;
  
  
  Going Further
&lt;/h3&gt;

&lt;p&gt;You can create your own Gatsby site from scratch, by adding React components in the &lt;code&gt;src/pages/&lt;/code&gt; directory. If you want to serve dynamic content without rebuilding, you'll need to use a different React site generator like &lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt; or if you don't care about server-side rendering, &lt;a href="https://create-react-app.dev"&gt;Create React App&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
