<?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: Jeff Jakinovich</title>
    <description>The latest articles on DEV Community by Jeff Jakinovich (@jeffbuildstech).</description>
    <link>https://dev.to/jeffbuildstech</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%2F626874%2Fa7a19051-0fd1-4d8b-8824-f7917a149b3d.png</url>
      <title>DEV Community: Jeff Jakinovich</title>
      <link>https://dev.to/jeffbuildstech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeffbuildstech"/>
    <language>en</language>
    <item>
      <title>How To Count Strings With Emojis In JavaScript</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Fri, 09 Aug 2024 16:40:15 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/how-to-count-strings-with-emojis-in-javascript-4604</link>
      <guid>https://dev.to/jeffbuildstech/how-to-count-strings-with-emojis-in-javascript-4604</guid>
      <description>&lt;p&gt;I love emojis. Who doesn’t?&lt;/p&gt;

&lt;p&gt;I was polishing off a highly intellectual X post a few days ago when I realized something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gkotu7vjkvxw19k7vcx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gkotu7vjkvxw19k7vcx.gif" alt="Example of X post showing emojis not counted correctly" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Emojis aren’t counted the same as regular characters&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;When typing out emojis in the new post section of X, you can see how regular characters count less than emojis.&lt;/p&gt;

&lt;p&gt;After a quick search, I found out it has something to do with &lt;a href="https://help.postscript.io/hc/en-us/articles/1260804629950-How-Emojis-and-Special-Characters-Affect-Character-Counts" rel="noopener noreferrer"&gt;how they are encoded&lt;/a&gt; in the Unicode system.&lt;/p&gt;

&lt;p&gt;Essentially, emojis are made of &lt;a href="https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/overview/a-quick-tour-of-unicode.htm#:~:text=Code%20points%20are%20the%20numbers,four%20numbers%20and%2For%20letters." rel="noopener noreferrer"&gt;multiple code points&lt;/a&gt;, and &lt;code&gt;length&lt;/code&gt; only counts code points, not characters.&lt;/p&gt;

&lt;p&gt;Regardless of why it happens, I thought about all the text counters I’ve created and how many exist in SaaS land.&lt;/p&gt;

&lt;p&gt;Emojis are not getting their fair shake 😢.&lt;/p&gt;

&lt;p&gt;Simply taking the length of the string isn’t an accurate count. Take, for example, something like this:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Make&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;emojis&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;👍&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;textarea&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;small&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Characters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;countString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/small&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&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;This is a simple React component that tracks the characters typed into a text field. It is the most common implementation of this feature.&lt;/p&gt;

&lt;p&gt;But the output gives us the same problem as my X post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3936xuy6lljt6e50ic6u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3936xuy6lljt6e50ic6u.gif" alt="Example app showing how using the string length doesn't count accurately" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Modern web development makes it easy to count characters accurately&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;You can use a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter" rel="noopener noreferrer"&gt;built-in object&lt;/a&gt; called &lt;code&gt;Intl.Segmenter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is a much broader use case for the object, but it essentially breaks down strings into more meaningful items like words and sentences based on a locale you provide. It offers more granularity than simply using code points.&lt;/p&gt;

&lt;p&gt;To fix our example above, all we have to do is update our &lt;code&gt;countString&lt;/code&gt; function like this:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Make&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;emojis&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;👍&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;textarea&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;small&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Characters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;countString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/small&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&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;We create a new instance of the &lt;code&gt;Intl.Segmenter&lt;/code&gt; object and pass our text to it. We put that output into an array and then finally take the &lt;code&gt;length&lt;/code&gt;, which will be far more accurate than simply taking the &lt;code&gt;length&lt;/code&gt; of the original string.&lt;/p&gt;

&lt;p&gt;Here is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30usefafmn9ruik0xq67.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30usefafmn9ruik0xq67.gif" alt="Example app showing how using the Intl.Segmenter gives an accurate count of the characters in the string" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;So why doesn’t X count an emoji correctly?&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Short answer: I have no idea.&lt;/p&gt;

&lt;p&gt;I’ve been programming far too long to delude myself into thinking there is a simple answer.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;Intl.Segmenter&lt;/code&gt; has &lt;a href="https://caniuse.com/?search=Intl.Segmenter" rel="noopener noreferrer"&gt;good browser support&lt;/a&gt;, and any performance or memory constraints would be negligible.&lt;/p&gt;

&lt;p&gt;My best guess is that the codebase is so large and so old that it isn’t worth the side effects of a refactor.&lt;/p&gt;

&lt;p&gt;I’d be happy to learn more if anyone has better insight into this.&lt;/p&gt;

&lt;p&gt;I hope this helps 😄.&lt;/p&gt;

&lt;p&gt;Happy coding 🤙.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>react</category>
    </item>
    <item>
      <title>How To Stop Form Bots With Honeypot Fields</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Thu, 01 Aug 2024 01:48:31 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/how-to-stop-form-bots-with-honeypot-fields-8od</link>
      <guid>https://dev.to/jeffbuildstech/how-to-stop-form-bots-with-honeypot-fields-8od</guid>
      <description>&lt;p&gt;Spam bots are the worst.&lt;/p&gt;

&lt;p&gt;They can ruin a good contact form in a heartbeat.&lt;/p&gt;

&lt;p&gt;While getting rid of spam bots requires a lot of strategy and technology, simple honeypots are quite effective for their low effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a honeypot?
&lt;/h2&gt;

&lt;p&gt;In the world of cybersecurity, &lt;a href="https://en.wikipedia.org/wiki/Honeypot_(computing)" rel="noopener noreferrer"&gt;there are many types of honeypots&lt;/a&gt;, but they are all essentially bot traps.&lt;/p&gt;

&lt;p&gt;Today we are talking about honeypots used in frontend forms.&lt;/p&gt;

&lt;p&gt;When bots spam your contact form, they must read the page source to “see” what to fill out. The good news is bots don’t “see” like humans.&lt;/p&gt;

&lt;p&gt;This is precisely what a honeypot exploits.&lt;/p&gt;

&lt;p&gt;The process will look a little something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a fake input inside a form&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hide it from the human users but not the bots&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filter for the field in your backend&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It would look something like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the honeypot field
&lt;/h2&gt;

&lt;p&gt;Let’s pretend we have a basic contact form:&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/submit"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Message:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bots will fill out every input and submit our form, so we need to add our honeypot.&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/submit"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Message:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Phone:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added an extra field called &lt;code&gt;phone&lt;/code&gt;. This field should not be visible to the user but should be visible to bots. To hide the field, we can add a simple CSS rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.phone&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;The user will not be able to see our honeypot, but bots will. And when they see it, they will most likely fill it out, which means we have them trapped.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsa3hhd53mql7qxpc1c2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsa3hhd53mql7qxpc1c2.png" alt="Diagram of what user sees vs. what spam bot sees" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Make sure you pick a good name for the honeypot
&lt;/h3&gt;

&lt;p&gt;Before we discuss how to deal with this extra field in our backend, I want to point out one thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The name of our honeypot shouldn’t be random.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These bots are aware honeypots exist.&lt;/p&gt;

&lt;p&gt;They have logic that filters out any irrelevant inputs when completing forms.&lt;/p&gt;

&lt;p&gt;So if we create a field called random or, even worse, honeypot, we tell the bot the field is a trap, so they should avoid it.&lt;/p&gt;

&lt;p&gt;Instead, we should consider related topics or fields related to the form.&lt;/p&gt;

&lt;p&gt;In most contact forms, that means we’d add anything related to contact details like:&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap the bot and kick it out
&lt;/h2&gt;

&lt;p&gt;Now that we have the honeypot set up on the frontend we need to handle it in our backend.&lt;/p&gt;

&lt;p&gt;I set up a &lt;code&gt;pseudo-express&lt;/code&gt; route to show the basic logic of handling this extra field.&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/submit&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If the honeypot field is filled, it's a bot&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Potential bot detected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bad request&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Process the legitimate submission&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Legitimate submission:&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thank you for your submission!&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pull the honeypot off the body and then check if there is a value.&lt;/p&gt;

&lt;p&gt;If there is, we caught the bot!&lt;/p&gt;

&lt;p&gt;Send back a &lt;code&gt;400&lt;/code&gt; error and sit back as your form zaps bot after bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than one honeypot
&lt;/h2&gt;

&lt;p&gt;In the last few years, I’ve noticed the effectiveness of one honeypot isn’t what it used to be.&lt;/p&gt;

&lt;p&gt;The good news is we can add more honeypots.&lt;/p&gt;

&lt;p&gt;This is a bit more effort but can bolster the strategy’s effectiveness.&lt;/p&gt;

&lt;p&gt;Remember to keep all honeypot names related to the other fields in the form, and we should be good to go.&lt;/p&gt;

&lt;p&gt;I’ve been using three honey pots per form, which seems to be the sweet spot.&lt;/p&gt;

&lt;p&gt;Let me know if you have questions.&lt;/p&gt;

&lt;p&gt;Happy coding 🤙&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>I Scoured Udemy For The Best Developer Courses: Here Are The 3 Teachers That Are Worth Your Time</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Tue, 23 Jul 2024 01:56:52 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/i-scoured-udemy-for-the-best-developer-courses-here-are-the-3-teachers-that-are-worth-your-time-5hb2</link>
      <guid>https://dev.to/jeffbuildstech/i-scoured-udemy-for-the-best-developer-courses-here-are-the-3-teachers-that-are-worth-your-time-5hb2</guid>
      <description>&lt;p&gt;I’ve used books, YouTube, Coursera, Udacity, CodeCademy, Treehouse, and several other platforms while learning to code.&lt;/p&gt;

&lt;p&gt;The platform that is most responsible, however, for taking my abilities from the basics to building functional apps is &lt;strong&gt;Udemy&lt;/strong&gt;. Udemy is underrated mostly because of the ranging level of quality. It is sort of the wild west. It can be hard to know what is worth your time and what isn’t.&lt;/p&gt;

&lt;p&gt;Say no more. I’ve tested tons of different courses on Udemy so you don’t have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Here are the top 3 developer teachers on Udemy that are worth your time:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.udemy.com/user/4b4368a3-b5c8-4529-aa65-2056ec31f37e/" rel="noopener noreferrer"&gt;&lt;strong&gt;Angela Yu&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;: Best for beginner web and app developers&lt;/strong&gt;. She has a couple of great bootcamps that can get you up and running creating web apps, iOS apps, and Android apps. Her ability to use stories and visuals makes it super easy to follow along and learn fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.udemy.com/user/sgslo/" rel="noopener noreferrer"&gt;&lt;strong&gt;Stephen Grider&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;: Best for deep dives into the latest tools for web app development.&lt;/strong&gt; Stephen is prolific. The amount of information he is able to cover in one course is amazing. And he has a ton of courses. Everything ranging from getting started with web app development to more advanced things like micro-services architectures. He balances nitty-gritty details with high-level visual overviews.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.udemy.com/user/joseportilla/" rel="noopener noreferrer"&gt;&lt;strong&gt;Jose Portilla&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;: Best for Python developers and aspiring data scientists.&lt;/strong&gt; Jose’s content shares a lot of the same traits as Angela and Stephen: comprehensive courses that take the complex and make it simple. If you’ve wanted to learn Python or if you know Python and want to take the next steps into data science and machine learning, I don’t know of an easier way to make that jump. You’ll be up and running in no time.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Let me know if you try out any of these teachers or have other favorites on Udemy.&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>learning</category>
      <category>webdev</category>
      <category>python</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Key To Growing As A Programmer? Getting Really Good At This 1 Thing</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Tue, 23 Jul 2024 01:39:51 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/the-key-to-growing-as-a-programmer-getting-really-good-at-this-1-thing-3il5</link>
      <guid>https://dev.to/jeffbuildstech/the-key-to-growing-as-a-programmer-getting-really-good-at-this-1-thing-3il5</guid>
      <description>&lt;p&gt;This is boring advice but simple: read more documentation.&lt;/p&gt;

&lt;p&gt;You will never memorize everything when learning to code. There is too much information. Fortunately, programmers already know this. So they created tons of documentation for the languages, libraries, and tools they built. There are literal instruction manuals for how to get better at using these technologies.&lt;/p&gt;

&lt;p&gt;Now you just have to get comfortable reading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning to read documentation will push you to grow fast for three reasons:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Learning to read documentation is the equivalent of learning the rules so you can break them later
&lt;/h3&gt;

&lt;p&gt;You don’t know what you don’t know when starting out.&lt;/p&gt;

&lt;p&gt;It is hard to build things when you don’t even know what your tools or raw materials can do. After reading your programming language’s documentation for a few hours, you’ll start to get an idea of how little you know.&lt;/p&gt;

&lt;p&gt;Don’t let this discourage you.&lt;/p&gt;

&lt;p&gt;Instead, write down all the questions you have when you come across things that are new or don’t make sense. Then go and Google those questions.&lt;/p&gt;

&lt;p&gt;Rinse and repeat. You’ll start to see all the new and useful ways you can use different technologies which will compound into more learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Learning to read documentation will help you write better documentation
&lt;/h3&gt;

&lt;p&gt;One day you will write a useful app or tool that others are going to want to use.&lt;/p&gt;

&lt;p&gt;How are you going to make sure they know how to use it?&lt;/p&gt;

&lt;p&gt;Whether you are working by yourself or in a team, being able to document code in a way that is succinct and easy to understand is critical.&lt;/p&gt;

&lt;p&gt;As you read more documentation you start to pick up on things that are helpful to you and things that are not. Remember the helpful stuff and duplicate it when writing your own documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The more advanced you get, the fewer tutorials there are to learn from
&lt;/h3&gt;

&lt;p&gt;When you start learning to code it seems like there are YouTube videos for everything you need to know.&lt;/p&gt;

&lt;p&gt;This is true up until a point. The more advanced you get with your programming, the more complicated some of the problems become. You reach a point where there isn’t any other way to solve your problem but to scour various documentation websites for any possible hints.&lt;/p&gt;

&lt;p&gt;One way or another, if you program long enough, you will get comfortable with documentation. So it is best to start early.&lt;/p&gt;

&lt;p&gt;Learn to read documentation and watch your programming abilities grow fast.&lt;/p&gt;

</description>
      <category>newbie</category>
      <category>documentation</category>
      <category>learning</category>
    </item>
    <item>
      <title>Automate Lighthouse Reports For Max App Performance</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Mon, 22 Jul 2024 21:57:36 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/automate-lighthouse-reports-for-max-app-performance-4o4l</link>
      <guid>https://dev.to/jeffbuildstech/automate-lighthouse-reports-for-max-app-performance-4o4l</guid>
      <description>&lt;p&gt;Imagine you build an incredible app that solves all your user’s problems. Your entrepreneur dreams are coming true. Then, one day, a line of code finds a way into your codebase that plummets page speeds. Users are waiting several seconds for each page. Users start to leave. And never come back. Your dreams are dead.&lt;/p&gt;

&lt;p&gt;That sounds dramatic. But no one likes a poor-performing web page.&lt;/p&gt;

&lt;p&gt;Good thing I have a solution for you.&lt;/p&gt;

&lt;p&gt;I’m not a huge fan of setting up needless systems or automations on projects before you know whether the project has legs. But there is always one automation I add to every single repo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated Lighthouse reports.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is a Lighthouse report?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://developer.chrome.com/docs/lighthouse/overview" rel="noopener noreferrer"&gt;Lighthouse report is an automated tool&lt;/a&gt; to help you improve your web pages. You give it a URL, and it gives you a report on key metrics that affect the quality of your web pages. The high-level report looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbcq4ybs5l147laoitpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbcq4ybs5l147laoitpv.png" alt="Image description" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each section also has more details with links to articles by Google to help you fix things that might be wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnsak7sthtopwnp1usbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnsak7sthtopwnp1usbe.png" alt="Image description" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can run this report as many times as you want. But it can get annoying to run the report too often. The good news is there is a way you can automate the reports and make sure no code gets added to your repo that hurts your scores.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to run a Lighthouse report from the command line&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Install the Lighthouse CI package.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In your project, install &lt;a href="https://www.npmjs.com/package/@lhci/cli" rel="noopener noreferrer"&gt;the NPM package that helps you run the Lighthouse reports&lt;/a&gt; from your terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @lhci/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Create the NPM script.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now, create a script in your &lt;code&gt;package.json&lt;/code&gt; to run the reports locally and in the GitHub action.&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="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"ci:lighthouse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lhci autorun"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Create the file that gives Lighthouse the instructions for what to run.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is where all the &lt;a href="https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/getting-started.md" rel="noopener noreferrer"&gt;customizations happen&lt;/a&gt;. You can tell Lighthouse where you want the reports to live, what type of reports to run, assertions to avoid or pay attention to, and even set benchmarks.&lt;/p&gt;

&lt;p&gt;I’ll show you how to add all the above later, but let’s get a basic version working first.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;lighthouserc.js&lt;/code&gt; file at the root of your project.&lt;/p&gt;

&lt;p&gt;Copy the following contents into the file. One thing to note is that the Lighthouse report will always run on the local build. So, if you haven’t built anything yet, you will need to do that first. And if you made changes, you’ll want to build again before running the reports. We will automate all this later, but it is good to be aware.&lt;/p&gt;

&lt;p&gt;I’m using Next.js, so in the “collect” section, I set it up so Lighthouse runs the production build of my app on &lt;code&gt;localhost:3000&lt;/code&gt;, and it runs 3 times. Then I have Lighthouse dump the reports in a directory on my root.&lt;/p&gt;

&lt;p&gt;Depending on your tech stack and preferences, feel free to change any of these settings.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// change to however you build and run a production version of your app locally&lt;/span&gt;
      &lt;span class="na"&gt;startServerCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startServerReadyPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready on&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;http://localhost:3000&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;numberOfRuns&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="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desktop&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="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporary-public-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change to where you want the reports to live&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;h3&gt;
  
  
  &lt;strong&gt;Make sure everything is working locally.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At this point, you should have enough set up to test everything locally. Make sure to build your project, then call your NPM script we created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run ci: Lighthouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your terminal should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70iy1b3fg0a2oddfseu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70iy1b3fg0a2oddfseu8.png" alt="Image description" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should notice a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can see the names of any errors. You can click the link next to the name to read more about the errors so you can fix it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you have errors (you most likely will), your code should exit with a status code 1. When we customize our assertions next, we can make sure specific assertions don’t force our script to fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The final thing the report will tell you is where these reports live. You can take the HTML file and copy it into your URL, and you should be able to see the full report. All reports should live in a directory called &lt;code&gt;.lighthouseci&lt;/code&gt; if you used the same outputs command I used when setting up the Lighthouse instructions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxr4as54thbw3qd5cz6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxr4as54thbw3qd5cz6m.png" alt="Image description" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Optional: make sure the Lighthouse reports don’t get added to your repo&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If you want to keep these reports in your repo for any reason, feel free to skip this. Usually, these reports are short-term checks, so I never want to add them to my repo. To make sure they don’t get added to your commit, go ahead and add the &lt;code&gt;.lighthouseci&lt;/code&gt; directory to your &lt;code&gt;.gitignore&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Customize the assertions you want in your report.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that we have the reports working locally, there is one more thing we can customize: assertions.&lt;/p&gt;

&lt;p&gt;When starting a project, I primarily use Lighthouse to ensure I’m not doing anything dumb. Especially things that would cause massive performance or UX issues for the users.&lt;/p&gt;

&lt;p&gt;This means I don’t need to address everything Lighthouse finds wrong with my web pages. But I also want to remember them.&lt;/p&gt;

&lt;p&gt;This is where I set any assertions that need more work or more learning to &lt;code&gt;warn&lt;/code&gt; so they don't stop my progress in building the app. Then, when things are more stable, I can come back to address any deeper issues.&lt;/p&gt;

&lt;p&gt;To do this, review all the errors you received when running the report locally. If any of them seem like something you’d want to address later, take the name of the error, add it to your &lt;code&gt;lighthouserc.js&lt;/code&gt;, and set it to &lt;code&gt;warn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Take, for example, this error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9012agrwc802r5ve79t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9012agrwc802r5ve79t.png" alt="Image description" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I should pay attention to the &lt;code&gt;total-byte-weight&lt;/code&gt; but I'll need to investigate further. I'd rather move on for now so I can launch my project, but I also want to remember this error. So setting it to &lt;code&gt;warn&lt;/code&gt; allows the report to succeed while still reminding me every run.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;startServerCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startServerReadyPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready on&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;http://localhost:3000&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;numberOfRuns&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="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desktop&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="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporary-public-storage&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;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lighthouse:recommended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// add your customizations for assertions here&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;total-byte-weight&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;warn&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="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;h3&gt;
  
  
  &lt;strong&gt;Set benchmarks to check against future changes.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the last step needed to set up your Lighthouse reports, and then we will automate everything.&lt;/p&gt;

&lt;p&gt;By adding benchmarks to our setup, you can set minimum scores that must always pass. Otherwise, the Lighthouse report will give us an error.&lt;/p&gt;

&lt;p&gt;This is critical to ensuring new code doesn’t affect the quality of your web pages.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;startServerCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startServerReadyPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready on&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;http://localhost:3000&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;numberOfRuns&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="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;desktop&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="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporary-public-storage&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;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lighthouse:recommended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// add your customizations for assertions here&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;total-byte-weight&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;warn&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;categories:performance&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;minScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.93&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:accessibility&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;minScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.93&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:best-practices&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;minScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:seo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;minScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Run the Lighthouse report in GitHub actions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I’m assuming you already know what &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub actions&lt;/a&gt; are. So, let’s get right into setting them up.&lt;/p&gt;

&lt;p&gt;First, make a &lt;code&gt;.github&lt;/code&gt; directory in your root folder with a &lt;code&gt;workflows&lt;/code&gt; directory inside. Then, add a &lt;code&gt;lighthouse.yml&lt;/code&gt; file to the &lt;code&gt;workflows&lt;/code&gt; directory. It should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f9kx02d8ijlx2es70si.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f9kx02d8ijlx2es70si.png" alt="Image description" width="690" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;lighthouse.yml&lt;/code&gt; file, create an action that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lighthouse CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lighthouseci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Lighthouse report&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bundle and build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run ci:lighthouse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main jobs of the action are to set up the Lighthouse report, build the code, and run the report every time you push code to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;The great thing about the benchmarks is they act as gatekeepers. If you change the trigger of the action to PRs, you can ensure any code meets the benchmarks before approval.&lt;/p&gt;

&lt;p&gt;The output of the action should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfka7oalzb158omeckey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfka7oalzb158omeckey.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Lighthouse report looks like it failed. That is because my benchmarks for the main categories aren’t high enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7a632klr1ecrjbixvkhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7a632klr1ecrjbixvkhp.png" alt="Image description" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since it was only triggered on pushing to my &lt;code&gt;main&lt;/code&gt; branch, it doesn't stop any code from merging, but at least I know what the issues are, so I can address them at some point. If the trigger was on PR, then this PR wouldn't allow the code to merge into the branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Wrap up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Setting all this up once makes it super easy to repeat for every project you start. It offers tons of upsides with minimal downsides or time investments.&lt;/p&gt;

&lt;p&gt;Happy coding! 🤙&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>github</category>
      <category>githubactions</category>
      <category>performance</category>
    </item>
    <item>
      <title>From Idea to Impact: How To Validate SaaS Product Ideas</title>
      <dc:creator>Jeff Jakinovich</dc:creator>
      <pubDate>Mon, 22 Jul 2024 21:48:22 +0000</pubDate>
      <link>https://dev.to/jeffbuildstech/from-idea-to-impact-how-to-validate-saas-product-ideas-1fok</link>
      <guid>https://dev.to/jeffbuildstech/from-idea-to-impact-how-to-validate-saas-product-ideas-1fok</guid>
      <description>&lt;p&gt;When it comes to validating product ideas, there isn’t a one-size-fits-all approach. You can’t automate it. You can’t set it and forget it.&lt;/p&gt;

&lt;p&gt;But you can still create systems and principles to speed up the process.&lt;/p&gt;

&lt;p&gt;Like a trail of breadcrumbs, you can create shortcuts so you don’t start from scratch every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What does a validation process look like?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd28axgn3a3x5r2i3kp6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd28axgn3a3x5r2i3kp6.png" alt="Image description" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process of validating a product idea follows predictable steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Identify a specific problem&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Package the problem into a solution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distribute the solution to an audience&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collect feedback and repeat&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step has the potential for full automation or tedious manual work. It is a spectrum.&lt;/p&gt;

&lt;p&gt;While my system won’t look like your system (and probably shouldn’t), there are principles you can use to customize it for your situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What and who is validation for?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before we get to the systems, I want to highlight an assumption and some key points.&lt;/p&gt;

&lt;p&gt;The point of validation is to save time and money by making sure you build something people want.&lt;/p&gt;

&lt;p&gt;Nothing more. Nothing less.&lt;/p&gt;

&lt;p&gt;But this leads to an important distinction.&lt;/p&gt;

&lt;p&gt;On the surface, validation is a one-way street. You create a product that gets validated in the market to figure out if it can make money.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F126trfj2z1gi82e64xcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F126trfj2z1gi82e64xcz.png" alt="Image description" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it is a two-way street. You may not realize it, but you are also validating whether you want to work on the product with potential customers or with the people you’d need to collaborate with to build the product for the customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64p21rqpk37es2a9911f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F64p21rqpk37es2a9911f.png" alt="Image description" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The list of potential validations is endless. But the more time I spend on building products, the more I’ve realized how important it is to validate a product from both sides of the street.&lt;/p&gt;

&lt;p&gt;The majority of the systems I’ve built address this framing.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: Identify a specific problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s get the bad news out of the way. Finding problems is a game of pattern recognition.&lt;/p&gt;

&lt;p&gt;To find patterns in high-leverage problems, you must get your reps in. Have conversations, Scour Reddit, read blogs, and attend conferences and other networking events.&lt;/p&gt;

&lt;p&gt;In short, go where your audience is and listen.&lt;/p&gt;

&lt;p&gt;The work is simple but requires a lot of effort.&lt;/p&gt;

&lt;p&gt;The good news is once you get your reps in, there are some helpful frameworks for making the most of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create a quality problem statement
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One of the most important things is how you craft your problem statement.
&lt;/h3&gt;

&lt;p&gt;Going from identifying a problem in your head to writing a clear problem statement a customer can understand can be a giant leap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best framework I know for how to do that is using a mix of:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you know both things, you can concisely describe the problem.&lt;/p&gt;

&lt;p&gt;Here’s an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Job Title:&lt;/strong&gt; Director of Customer Success&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jobs-To-Be-Done:&lt;/strong&gt; Needs to reduce customer churn&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biggest pain point:&lt;/strong&gt; Struggles with identifying at-risk customers early due to lack of data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a problem statement:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Directors of Customer Success need to reduce customer churn but struggle identifying at-risk customers early due to lack of data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Forcing the problem into a single statement means your scoping is concise enough to make meaningful progress on the rest of the steps. And it makes it a super easy thing to share with potential customers to kick off the validation process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pain killer vs. vitamins&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The final thing to think about is the leverage of a problem. Some things an audience complains about are nice-to-haves. Other things are so painful they will throw their money at you. We want the latter.&lt;/p&gt;

&lt;p&gt;You’ll be able to pick up on cues over time, and the more you look for problems, vitamins generally sound like “it would be nice if…” and pain pills sound like “a new compliance rule is forcing us to…”.&lt;/p&gt;

&lt;p&gt;A key distinction is that vitamins are usually requests triggered by a user, while pain pills are requests triggered by the environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Package the problem into a solution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the most important step.&lt;/p&gt;

&lt;p&gt;Most of the “hidden” validation will happen here because it will force you to go from vague solutions to packaged realities.&lt;/p&gt;

&lt;p&gt;I don’t complete everything in the list below for each idea, but I always do the first two items and then pick and choose others depending on the idea.&lt;/p&gt;

&lt;p&gt;Things you can do to create a packaged solution&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Run the solution you have through a series of copywriting frameworks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To keep the piece short, I won’t &lt;a href="https://www.chasedimond.com/10-proven-copywriting-frameworks" rel="noopener noreferrer"&gt;dive into the details of the frameworks&lt;/a&gt;. But these frameworks are the first step to figuring out if your solution is enticing to another human.&lt;/p&gt;

&lt;p&gt;If you struggle running your solution through these frameworks. Or if you produce copy that no other human finds interesting, it is time to move on or change your solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Create an offer the user can’t say no to&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://www.acquisition.com/bio-alex" rel="noopener noreferrer"&gt;Alex Hormozi&lt;/a&gt; says, “The key to sales is making an offer so good people will feel stupid saying no.”&lt;/p&gt;

&lt;p&gt;That is all I focus on here. Can I create an offer (or several) that would put one person in my persona in a position to feel stupid saying no?&lt;/p&gt;

&lt;p&gt;I’ll focus on packaging up unique features, different pricing models, and adding incentives from different growth strategies.&lt;/p&gt;

&lt;p&gt;If I can’t make an offer that guarantees an imaginary person says yes to my offer, why would a real person say yes once I launch it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Create your most emotive assets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyone can complete the first two exercises regardless of skill, but this is the first one that is dependent on your skillset.&lt;/p&gt;

&lt;p&gt;I’m a developer with plenty of experience using Figma and other Adobe tools. For me to put together a quick UI flow in those tools takes almost no time. So I always start creating wireframes with a technical eye towards what is possible.&lt;/p&gt;

&lt;p&gt;But I struggle sketching things. So, someone with more drawing ability can create sketches that could be useful to potential customers.&lt;/p&gt;

&lt;p&gt;Or, a graphic designer can create ads to test the idea on Meta, Google, or other platforms.&lt;/p&gt;

&lt;p&gt;Either way, the goal is simple. Producing assets is another filter to figure out if the idea has legs. And those assets can live on their own to do some of the validating for you in later steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 4: Create a landing page with a CTA for your offer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If all the other steps look promising, I will move on to this step. I understand this could sound daunting, but think loosely about the term “landing page.”&lt;/p&gt;

&lt;p&gt;You can use services like Carrd to build legit landing pages but I’ve seen Notion pages served up and get the job done just fine.&lt;/p&gt;

&lt;p&gt;Don’t overthink it.&lt;/p&gt;

&lt;p&gt;The goal is to take make a place where you can take everything you completed in the earlier steps and place them all under one roof. Then have one ask where you are either asking for a sign up to a waitlist or to complete a survey about the problem. Both of which offer great signals for validation.&lt;/p&gt;

&lt;p&gt;Now, in the distribution step, you have something you can distribute, scale, and sell for yourself. This is where the automation part of the system can start to take off.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3: Distribute the solution to an audience&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You need to get your copy, assets, or landing page in front of as many people in your audience as possible.&lt;/p&gt;

&lt;p&gt;Feel free to email, text, or call anyone you know who can react to what you have but there are tools that help to speed this process up.&lt;/p&gt;

&lt;p&gt;Here are some that I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;LinkedIn sales navigator&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database tools with leads like Apollo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reddit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use advanced Google searches like: “Name of job title” forum or “Name of job title” community&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever tool you use, remember your problem statement in step 1. You want to search for job titles and only reach out to those job titles.&lt;/p&gt;

&lt;p&gt;Find as many emails as possible and present your assets or problem statement. When you reach out, it’s important to be upfront with where you are in the process. Please don’t promise full-fledged platforms when you only have a logo and some copy.&lt;/p&gt;

&lt;p&gt;If the pain is strong enough, they will be happy to build with you, which is a great sign for idea validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Collect feedback and repeat&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you create a simple feedback form for the solution or the problem, it is the perfect pairing to the assets you already have.&lt;/p&gt;

&lt;p&gt;Your CTA can be asking people to complete the form to get feedback on how you defined the problem and the solution.&lt;/p&gt;

&lt;p&gt;Plus, like some of the other steps, writing the survey is a form of validation. If you can’t think of the most potent questions to ask about the problem or your proposed solution, it might be time to revisit step 1.&lt;/p&gt;

&lt;p&gt;The simplest way to set this up is to create a Google Form and connect it to your assets or landing page. You can also use Zapier to connect form submissions to other tools you use to track all the responses.&lt;/p&gt;

&lt;p&gt;I will update these tools and systems as I come across new ideas.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
