<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Build SaaS with Ethan]]></title><description><![CDATA[Learn to build SaaS. Learn the best technologies and techniques to launch your idea.]]></description><link>https://ethanmick.com/</link><image><url>https://ethanmick.com/favicon.png</url><title>Build SaaS with Ethan</title><link>https://ethanmick.com/</link></image><generator>Ghost 5.79</generator><lastBuildDate>Tue, 20 Feb 2024 09:01:47 GMT</lastBuildDate><atom:link href="https://ethanmick.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[A Bit of SaaS Weekly: One Last Time]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p><strong>This will be the last edition of the SaaS Weekly newsletter.</strong></p><p>Thank you everyone for your support, comments, thoughts, and time. If you need me, you can always reply to</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-one-last-time/</link><guid isPermaLink="false">652d4b39d60f350001f40058</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 20 Oct 2023 15:36:38 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/10/time.webp" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/10/time.webp" alt="A Bit of SaaS Weekly: One Last Time"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p><strong>This will be the last edition of the SaaS Weekly newsletter.</strong></p><p>Thank you everyone for your support, comments, thoughts, and time. If you need me, you can always reply to any of these emails.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://ntietz.com/blog/write-more-useless-software/?ref=ethanmick.com" rel="noreferrer">Write</a> more useless software.</li><li><a href="https://grumpy.website/1389?ref=ethanmick.com" rel="noreferrer">Some</a> thoughts on relative dates.</li><li><a href="https://www.shadcn-vue.com/?ref=ethanmick.com" rel="noreferrer">Shadcn/ui</a> for Vue.</li></ul><hr><h2 id="finding-your-happiness">Finding your Happiness</h2><p>As I wrote above, this will be the last edition.</p><p>I recently read <em>The Subtle Art of Not Giving a F*ck</em> by <a href="https://markmanson.net/?ref=ethanmick.com" rel="noreferrer">Mark Manson</a>. I&apos;m a sucker for self-help books. I love a book that&apos;ll tell me how to un-f*ck my relationship or make me tons of money. Preferably in my pajamas in the Bahamas. All at once.</p><p>But truthfully, I was looking for a way to understand the feeling I was having. This reluctance to put out more content.</p><p>What I thought I wanted for a long time was an audience. I&apos;ve seen people do incredible things with their audience on Twitter, YouTube, or whatever. Once you have a large audience, you can launch products and get feedback easily.</p><p>Having a large audience is a superpower. It still is. The audience was the goal.</p><p>But <em>growing</em> an audience is a grind. Lots of content, solving small problems, and time. And that wasn&apos;t bringing me a lot of happiness. The content I was creating felt uninspired and a chore. I didn&apos;t relish writing. I didn&apos;t love making YouTube videos. It wasn&apos;t terrible! I enjoyed helping people. I&apos;m proud and happy of those who I&apos;ve helped. But the act of doing all the work wasn&apos;t that enjoyable. It wasn&apos;t bringing happiness.</p><p>And if the journey wasn&apos;t making me happy, the goal wasn&apos;t going to magically make me happy.</p><p>I have 4,000+ subscribers on YouTube. That&apos;s a lot more than the zero I had when I started. I&apos;m a part of the partner program. These are fantastic accomplishments. But the 4,000 subscribers themselves didn&apos;t magically make me happy.</p><p>So what? Will I magically be happy at 10,000? 50k? 100k?</p><p>Happiness isn&apos;t magically reaching an arbitrary number.</p><p>Happiness comes from solving problems along the way and enjoying the process. </p><p>And again, I don&apos;t hate making YouTube videos or writing content. But when I tied it to my business, consulting, and audience growth, the fun of it all was weeded out. It wasn&apos;t bringing me happiness.</p><p>And it&apos;s time I&apos;m honest with myself. To listen to the emotions telling me it&apos;s time to change. It&apos;s time I find new problems that I enjoy solving.</p><p>I want to find interesting problems that I love working on. A team I can bond with and tackle hard things together. These things I miss. It&apos;s what I want.</p><p>And I want for you, dear reader, to find what makes you happy as well. And jump in and tackle it with your passion and gusto.</p><p>So keep it up. Keep learning and trying things. Find what makes you happy. And toss aside those things that don&apos;t. Life is too short.</p><p>-- Ethan</p>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Chill and Vibe]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p><strong>Correction: </strong>An astute reader wrote in and pointed out that the code and output of the tech tip section didn&apos;t match. I had originally written the code for</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-chill-and-vibe/</link><guid isPermaLink="false">65284313d60f350001f3fb8e</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 13 Oct 2023 10:00:48 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/10/hobbit.webp" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/10/hobbit.webp" alt="A Bit of SaaS Weekly: Chill and Vibe"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p><strong>Correction: </strong>An astute reader wrote in and pointed out that the code and output of the tech tip section didn&apos;t match. I had originally written the code for light backgrounds, but the output didn&apos;t look good on dark backgrounds. When I updated the code, I forgot to put the new version in the newsletter. Thanks, Brian! You get a  &#x2B50;&#xFE0F;.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://nerdyarticles.com/a-clutter-free-life-with-paperless-ngx/?ref=ethanmick.com" rel="noreferrer">Going paperless</a>.</li><li><a href="https://tailspark.co/?ref=ethanmick.com" rel="noreferrer">300+ Tailwind components</a> for your site.</li><li><a href="https://engineercodex.substack.com/p/how-instagram-scaled-to-14-million?ref=ethanmick.com" rel="noreferrer">How Instagram scaled</a> to 14 million users with only 3 engineers.</li><li><a href="https://autometrics.dev/?ref=ethanmick.com" rel="noreferrer">An open-source</a> micro framework for observability.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/10/output-type.webp" class="kg-image" alt="A Bit of SaaS Weekly: Chill and Vibe" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/10/output-type.webp 600w, https://ethanmick.com/content/images/size/w1000/2023/10/output-type.webp 1000w, https://ethanmick.com/content/images/2023/10/output-type.webp 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="whats-in-a-uuidv7">What&apos;s in a UUIDv7</h2><p>UUID Version 7 (UUIDv7) is a time-based identifier that encodes a Unix timestamp with millisecond precision in its first 48 bits. Of the entire UUID structure, 6 bits signify its version and variant, while the remaining 74 bits are randomized.</p><figure class="kg-card kg-code-card"><pre><code class="language-text">    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                           unix_ts_ms                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          unix_ts_ms           |  ver  |       rand_a          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |var|                        rand_b                             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                            rand_b                             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</code></pre><figcaption><p dir="ltr"><a href="https://buildkite.com/blog/goodbye-integers-hello-uuids?ref=ethanmick.com" rel="noreferrer"><span style="white-space: pre-wrap;">Source</span></a></p></figcaption></figure><p>Due to its time-ordered design, UUIDv7 values are essentially sequential, resolving the index locality issue prevalent in databases. This sequential nature of UUIDv7 offers better database performance compared to the random design of UUIDv4. A study from the 2nd quadrant blog demonstrated that sequential UUIDs outperform random UUIDs in both writing and reading tasks.</p><p>Importantly, UUIDv7 maintains the standard UUID structure, ensuring it&apos;s compatible with systems currently using other UUID versions. This makes transitioning from UUIDv4 to UUIDv7 in systems like Postgres straightforward.</p><p>In my applications, I&apos;ve been creating primary keys with the format:</p><pre><code>prefix_UUIDv4</code></pre><p>Where the prefix is a ~3-character identifier that identifies the resource. This makes it very easy to see what resource a key is for just by looking at it. I often take out the dashes as well to make it easier to copy.</p><p>The downside with my strategy is that the only column that works with this is <code>text</code> in Postgres. I can&apos;t use <code>serial</code> or <code>uuid</code> because it&apos;s neither of those things. This can lead to the same problem mentioned above, where the sequential writes result in poor index formation.</p><p>I&apos;m excited to try the new UUIDv7 format for primary keys gaining the advantage of a time stamp as well as the uniqueness of a UUID. I will lose out on an easy identifier in that transition. We&apos;ll see if it&apos;s worth it.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Chill and Vibe" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Midjourney</span></figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>Some sites will use the favicon as an icon that can change depending on the site status (such as an alert or dashboard). You can do this easily with React:</p><pre><code class="language-ts">import React, { useState, useEffect } from &apos;react&apos;;

function FaviconChanger() {
  const [faviconState, setFaviconState] = useState(&apos;default&apos;);

  const faviconMap = {
    default: &apos;/path-to-default-favicon.ico&apos;,
    alternate: &apos;/path-to-alternate-favicon.ico&apos;,
    // ... you can add more states and associated favicon paths here
  };

  useEffect(() =&gt; {
    let linkElement = document.querySelector(&quot;link[rel*=&apos;icon&apos;]&quot;);

    if (!linkElement) {
      linkElement = document.createElement(&apos;link&apos;);
      linkElement.type = &apos;image/x-icon&apos;;
      linkElement.rel = &apos;shortcut icon&apos;;
      document.getElementsByTagName(&apos;head&apos;)[0].appendChild(linkElement);
    }

    linkElement.href = faviconMap[faviconState];
  }, [faviconState]);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setFaviconState(&apos;default&apos;)}&gt;Set Default Favicon&lt;/button&gt;
      &lt;button onClick={() =&gt; setFaviconState(&apos;alternate&apos;)}&gt;Set Alternate Favicon&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default FaviconChanger;</code></pre><p>You can add this logic to another component instead of making it a separate component.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>4,205 (+131 in the last 7 days)</strong></li><li>Newsletter Members: <strong>781 (+27 in the last seven days)</strong></li></ul><p>My contract is going well! I&apos;m ending week three, and I feel like I have my feet under me. I&apos;m starting to impact some change at the company, and that feels good. I&apos;ve also been reading <a href="https://www.amazon.com/Subtle-Art-Not-Giving-Counterintuitive/dp/0062457713?ref=ethanmick.com" rel="noreferrer"><em>The Subtle Art of Not Giving a F*ck</em></a><em> </em>to see if that imparts any wisdom with my approach to running my business.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>Making hard things <a href="https://jvns.ca/blog/2023/10/06/new-talk--making-hard-things-easy/?ref=ethanmick.com" rel="noreferrer">easy</a>.</li><li>A modern Wine wrapper for macOS to run <a href="https://github.com/Whisky-App/Whisky?ref=ethanmick.com" rel="noreferrer">Windows games on Mac</a>.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: The bigger they are...]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Up here in the northern hemisphere, the leaves are starting to change, and the air is getting colder. We&apos;ve entered the 4th quarter of the year, and there</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-the-bigger-they-are/</link><guid isPermaLink="false">651f4ea4a5af050001e4081e</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 06 Oct 2023 10:00:55 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/10/Monster_Manual_5e_-_Beholder_-_p28.webp" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/10/Monster_Manual_5e_-_Beholder_-_p28.webp" alt="A Bit of SaaS Weekly: The bigger they are..."><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Up here in the northern hemisphere, the leaves are starting to change, and the air is getting colder. We&apos;ve entered the 4th quarter of the year, and there will be a lot of business done in the next three months. Go get &apos;em!</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://sre.google/sre-book/table-of-contents/?ref=ethanmick.com">Site Reliability Engineering</a>.</li><li><a href="https://store.app/?ref=ethanmick.com">Store.app</a>, an app store just for web apps.</li><li><a href="https://www.radix-ui.com/themes/docs/overview/releases?ref=ethanmick.com#200">Radix Themes</a> released version 2.0 with many component improvements.</li><li><a href="https://contentlayer.dev/?ref=ethanmick.com">Contentlayer</a> makes content easier to manage in your app.</li><li><a href="	HTTP/3 adoption is growing rapidly">HTTP/3</a> adoption is growing rapidly.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/10/output1.webp" class="kg-image" alt="A Bit of SaaS Weekly: The bigger they are..." loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/10/output1.webp 600w, https://ethanmick.com/content/images/size/w1000/2023/10/output1.webp 1000w, https://ethanmick.com/content/images/2023/10/output1.webp 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="service-level-objective">Service Level Objective</h2><p>I have done an absolute ton of reading and understanding in the past two weeks. While I have done a lot of work building and running SaaS apps, I haven&apos;t done a deep dive into doing it at scale. Well, scale where people actually use your app.</p><p>When you&apos;re talking about this level of scale, you immediately need to answer questions such as:</p><ul><li>How much can your service be offline?</li><li>How quickly does it need to respond to incoming requests?</li><li>How many concurrent users does it need to handle?</li></ul><p>The answer to these questions is nuanced and should be thoughtfully answered.</p><p>The answer will depend on your users, application, and competitors. It should not use the current state of the software as a starting point. Instead, understand what your users are expecting and meet them there.</p><p>The answer is never, &quot;We must be up 100% of the time&quot;. With a few exceptions... antilock breaks are one of them. Otherwise, that level of availability is actively bad. Users won&apos;t notice the difference between 99.9% and 100%. The network they use is less reliable than 100%, so there is no way it will work for them all the time anyway. To achieve that level of reliability will require enormous effort and money.</p><p>It makes much more sense to pick a smaller number, often around 99%, that you want to aim for.</p><p>The difference between 100% (perfect) and 99% is your error budget. In this case, that&apos;s 1%. That&apos;s about 7 hours a month you can be unavailable.</p><p>Now that you have a budget, you can start to spend it. It&apos;s actually better to spend the budget than to try and shoot for perfect. Remember, the SLO represents what the user finds acceptable. That means it&apos;s okay to be down for an hour here or there. But when your budget is all spent, you can no longer risk being down.</p><p>This attitude shifts how you think about risk. Instead of defaulting to &quot;I can&apos;t do anything risky,&quot; you can shift the team to consider doing risky events when you have the budget to spend. When your budget is tight, work on tasks that will improve reliability.</p><p>This gentle back and forth will prioritize the work for you. Features when you can be risky, stability when you need to be careful.</p><p>And it all comes from the SLO.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: The bigger they are..." loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Midjourney</span></figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>Colorizing console logs can enhance debugging in web development.</p><p>Or not. But it can look cool!</p><p>By using different colors, developers can categorize, prioritize, and quickly scan through logs, identifying relevant information at a glance. This not only makes the console more visually appealing but also aids in faster identification of issues or important events.</p><p>The browser&apos;s console allows the styling of log messages with CSS properties, offering a versatile tool to customize log output.</p><pre><code class="language-js">// Simple colored text
console.log(&apos;%c Light Blue text&apos;, &apos;color: #87CEEB;&apos;);

// Bold text with a subtle background color
console.log(&apos;%c Important log&apos;, &apos;color: #FFD700; background-color: #555; font-weight: bold;&apos;);

// Using multiple styles in one log
console.log(&apos;%c Error: &apos;, &apos;color: #FF6347; font-weight: bold;&apos;, &apos;Something went wrong.&apos;);

// Text with a shadow
console.log(&apos;%c Glowing text&apos;, &apos;color: #32CD32; text-shadow: 0px 0px 5px #7FFF00;&apos;);

// Mixed styles in a single statement
console.log(&apos;This is %cStyled Text%c and this is not.&apos;, &apos;color: #FFC0CB; font-style: italic;&apos;, &apos;color: #B0C4DE; font-style: normal;&apos;);</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/10/image.png" class="kg-image" alt="A Bit of SaaS Weekly: The bigger they are..." loading="lazy" width="257" height="110"><figcaption><span style="white-space: pre-wrap;">Output</span></figcaption></figure><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>4,074 (+129 in the last 7 days)</strong></li><li>Newsletter Members: <strong>754</strong> <strong>(+24 in the last 7 days)</strong></li></ul><p>Another week of being super busy with my new contract. I think the insanity is slowing down though, so my evenings are a little freer. I need to figure out a new schedule for content and then execute it.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>An interactive intro to <a href="https://jakelazaroff.com/words/an-interactive-intro-to-crdts/?ref=ethanmick.com">CRDTs</a>.</li><li>Goodbye integers, hello <a href="https://buildkite.com/blog/goodbye-integers-hello-uuids?ref=ethanmick.com">UUIDv7</a>.</li><li>Tamagui is a UI framework for the web and <a href="https://tamagui.dev/?ref=ethanmick.com">React Native</a>. </li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Onboarding]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Starting anything new is both exhilarating and daunting. There is so much promise and uncertainty, a tantalizing promise of what might be and if you will be able to achieve</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-onboarding/</link><guid isPermaLink="false">6515cc6af5ada60001d013c0</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 29 Sep 2023 10:00:29 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/09/ethan_mick_an_adventurer_getting_onto_a_medieval_galley_wide_sh_84c73bbb-207a-4c84-9134-ddf47879898f.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/09/ethan_mick_an_adventurer_getting_onto_a_medieval_galley_wide_sh_84c73bbb-207a-4c84-9134-ddf47879898f.png" alt="A Bit of SaaS Weekly: Onboarding"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Starting anything new is both exhilarating and daunting. There is so much promise and uncertainty, a tantalizing promise of what might be and if you will be able to achieve it. Only one way to find out.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://kagi.com/?ref=ethanmick.com">Kagi</a> is a paid search engine that is better than Google. They now offer <a href="https://blog.kagi.com/unlimited-searches-for-10?ref=ethanmick.com">unlimited searches</a> for $10 a month.</li><li><a href="https://openai.com/blog/chatgpt-can-now-see-hear-and-speak?ref=ethanmick.com">ChatGPT</a> can now understand images, listen, and speak. Integrate this with your SaaS for impressive features.</li><li><a href="https://adriano.fyi/posts/2023-09-24-choose-postgres-queue-technology/?ref=ethanmick.com">Postgres</a> can be used as a queue (Like SQS, Kafka, or RabbitMQ). And it&apos;s not even bad at it!</li><li><a href="https://www.subdomain.center/?ref=ethanmick.com">Subdomain Center</a>, A SaaS that finds all the subdomains of a given domain.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/09/ethan_mick_An_old_wizened_senior_software_engineer_sending_magi_319080f5-7e60-48ee-a74e-a156d1d7e174-1.png" class="kg-image" alt="A Bit of SaaS Weekly: Onboarding" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/09/ethan_mick_An_old_wizened_senior_software_engineer_sending_magi_319080f5-7e60-48ee-a74e-a156d1d7e174-1.png 600w, https://ethanmick.com/content/images/size/w1000/2023/09/ethan_mick_An_old_wizened_senior_software_engineer_sending_magi_319080f5-7e60-48ee-a74e-a156d1d7e174-1.png 1000w, https://ethanmick.com/content/images/2023/09/ethan_mick_An_old_wizened_senior_software_engineer_sending_magi_319080f5-7e60-48ee-a74e-a156d1d7e174-1.png 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="how-i-learn-when-joining-a-new-team">How I learn when joining a new team</h2><p>I recently started a new contract with a midsized software company. This company makes over $100 million a year in software revenue and has a large (200-ish) software development team.</p><p>My job is to help them increase their observability throughout their infrastructure and application stacks.</p><p>It&apos;s taken me about 24 hours (spread over 4 days) to understand everything I need to understand to accomplish that job. I&apos;ve worked about 6 hours a day.</p><p>At this point, I understand:</p><ul><li>How they organize their projects, write feature requests, review pull requests, and organize their Git projects.</li><li>How their build pipeline works, where the shared libraries are, and where they store build artifacts.</li><li>What their infrastructure looks like, how it functions, how data flows in and out of the apps.</li><li>How the apps function, what they do, how they do it, and how the company makes money.</li><li>How they observe, monitor, fix issues, run their DevOps, handle rotations, pagers, and where the team responsibilities are.</li><li>Lots of nitty-gritty details on the GitOps flow, software used, packages, and politics.</li></ul><p>It&apos;s a lot.</p><p>Here&apos;s my strategy for joining a new team and getting up to speed quickly.</p><ol><li>When you show up on day 1, you&apos;ve already been hired. You&apos;ve been vetted. You&apos;ve gone through the challenging interview process. No one wants to see you fail. Everyone is on your side. You need to leave the mentality of &quot;Oh my god, what if people don&apos;t like me or think I&apos;m good&quot; behind and step into the mentality of &quot;I&apos;ve been hired to do a job and do it well. Help me get there as quickly as possible.&quot; Mindset is everything.</li><li>Start with what you know. It doesn&apos;t matter if you are a junior developer or a senior architect. Every place does things differently, but there are lots of commonalities. Start there. Find the things you know and start branching out from there. For me, I know how infrastructures and apps work. What I didn&apos;t know was how exactly this company applied those techniques.</li><li>Ask questions. Say upfront, &quot;I&apos;m going to ask a million questions.&quot; And then ask a million questions. Be smart about what you are asking. If it&apos;s something you can Google, do that. If you can ask ChatGPT what something is, do that. Don&apos;t drop proprietary code or data into ChatGPT if it&apos;s against company policies. But you can ask how things work generally or remove data to make a question more general.</li><li>When learning something new, bring it back to what you know. I&apos;ve used <a href="https://argo-cd.readthedocs.io/en/stable/?ref=ethanmick.com">ArgoCD</a> a lot for Kubernetes continuous deployments. This company uses <a href="https://fluxcd.io/?ref=ethanmick.com">FluxCD</a> instead. I specifically asked ChatGPT to compare and contrast those two things and give full examples of how both work end-to-end. I read the docs on Flux and set up an example app that&apos;s deployed with it. I now feel comfortable enough to know how it works. But I started from what I already knew.</li><li>Learn the stack, either bottom-up (infrastructure first, then application layer) or top-down (how do the apps work, how do they communicate, how do they run?). Depending on your skill set, you will be happier starting at the top or bottom of the stack. As an architect, I like going bottom-up. Show me the infrastructure because that defines what can communicate and how they do so. Then tell me what the apps do. At the same time, I talk to the product organization to learn how the company makes money and builds a product so when I get to the code, I can check... is this really doing the right thing?</li><li>To learn all these, ask who can give you brain dumps via meetings and schedule as many as you need. Drop a meeting on their calendar, ask important questions you can&apos;t learn from Googling/ChatGPT, and understand the pain points. At the end of each meeting, I like to ask, &quot;What am I not asking? What do you have on your mind that I might not know about?&quot; It helps uncover my blind spots and leads me to new things to investigate.</li></ol><p>That&apos;s how I&apos;ve onboarded to a new company and team in 24 working hours. I&apos;ll be starting to work on the implementation in the next day, knowing how the product works at a high and low level.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Onboarding" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/99designs/aws-vault?ref=ethanmick.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - 99designs/aws-vault: A vault for securely storing and accessing AWS credentials in development environments</div><div class="kg-bookmark-description">A vault for securely storing and accessing AWS credentials in development environments - GitHub - 99designs/aws-vault: A vault for securely storing and accessing AWS credentials in development envi&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/pinned-octocat.svg" alt="A Bit of SaaS Weekly: Onboarding"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">99designs</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/843bf36853646d8fd6d166054c29859d6cc826f26817488e403a583aaf882383/99designs/aws-vault" alt="A Bit of SaaS Weekly: Onboarding"></div></a></figure><p>A common setup in AWS is to have roles with elevated permission that a user assumes when they need to take an action. Assuming this role, especially when protected with MFA, is... annoying. </p><p>Luckily, AWS Vault is a great command line tool that lets you assume a role on the command line so you can execute commands with it. You need to set your AWS profile configuration to have the role you want to assume:</p><pre><code class="language-config">[default]
region = us-east-1

[profile admin]
mfa_serial = arn:aws:iam::111111111111:mfa/admin</code></pre><p>And then you can run the following:</p><pre><code class="language-bash">aws-vault exec admin -- aws s3 ls</code></pre><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,945 (+166 in the last 7 days)</strong></li><li>Newsletter Members: <strong>730</strong> <strong>(+34 in the last 7 days)</strong></li></ul><p>This week, I&apos;ve been super busy onboarding at my new contract, and my kid was sick for a few days, which took a lot out of me. I&apos;m hoping to get a video up soon, though, so stay tuned.</p><p>I played around a little bit with ChatGPT&apos;s image recognition, and it&apos;s... quite impressive. It was able to read a receipt and transcribe it perfectly. Lots of fun opportunities for building around this new tech.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>A TypeScript Bun web framework, <a href="https://elysiajs.com/?ref=ethanmick.com">Elysia.js</a>.</li><li>Raspberry Pi 5 is <a href="https://www.raspberrypi.com/products/raspberry-pi-5/?ref=ethanmick.com">coming out</a>.</li><li>Paisa, an open-source <a href="https://paisa.fyi/?ref=ethanmick.com">finance manager</a>. Not a SaaS, but you could take this and build a similar thing as a SaaS.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Build your world]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>The best side project is the one you work on. It&apos;s also the one you use. The perfect place to start building something is to find something you</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-build-your-world/</link><guid isPermaLink="false">650c5590be42070001f3d490</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 22 Sep 2023 10:00:54 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/09/ethan_mick_Planet_earth_being_created_by_a_software_engineer_go_72d183fc-f921-4737-b930-20c3dbf7002f.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/09/ethan_mick_Planet_earth_being_created_by_a_software_engineer_go_72d183fc-f921-4737-b930-20c3dbf7002f.png" alt="A Bit of SaaS Weekly: Build your world"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>The best side project is the one you work on. It&apos;s also the one you use. The perfect place to start building something is to find something you do every day and replace it with your own version.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://openai.com/dall-e-3?ref=ethanmick.com">DALL&#xB7;E 3</a> is out. While we can argue over how good it is, the fact that it has an API means it can plug into every SaaS out there.</li><li><a href="https://nextjs.org/blog/next-13-5?ref=ethanmick.com">Next.js 13.5</a> has been released with faster local dev, less memory usage, icon library improvements, and <em>lots</em> of bugs fixed. There is also a <a href="https://nextjs.org/conf?ref=ethanmick.com">Next.js conf</a> on October 26th.</li><li><a href="https://github.com/hyperdxio/hyperdx?ref=ethanmick.com">HyperDX</a> is an open-source observability platform unifying session replays, logs, metrics, traces, and errors.</li><li><a href="https://v0.dev/?ref=ethanmick.com">V0</a>, a generative user interface system by Vercel. It generates copy-and-paste React code based on <a href="https://ui.shadcn.com/?ref=ethanmick.com">shadcn/ui</a> and <a href="https://tailwindcss.com/?ref=ethanmick.com">Tailwind CSS</a>.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/09/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Build your world" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/09/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/09/image-4.png 1000w, https://ethanmick.com/content/images/2023/09/image-4.png 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="what-is-bun">What is Bun?</h2><p>Bun is an all-in-one toolkit for JavaScript and TypeScript apps.</p><p>Okay, what does that really mean?</p><p>Bun was created to answer the question, &quot;If you were to build a JavaScript runtime today, what would you do differently?&quot;</p><p>You see, running JavaScript on the server was very hard for a long time. There wasn&apos;t a good way to do it, and when you could, it was like running a limited browser session on your computer. There was no way to interact with the file system, open sockets, or do anything useful.</p><p>When Node.js came out in 2009, it had a revolutionary idea: A standard library that allowed your JavaScript code to interact with the host running it. Suddenly JavaScript <em>could</em> be run on the server and could do anything other languages had the ability to do.</p><p>We&apos;ve learned a lot since 2009.</p><p>These days there are multiple tools web developers need to use to get anything done. We rarely think about it being a problem because... well, it&apos;s always been that way. Things like <code>npm</code>, <code>yarn</code>, <code>lerna</code>, <code>pnpm</code>, <code>tsc</code>, <a href="https://webpack.js.org/?ref=ethanmick.com">Webpack</a>, <a href="https://babeljs.io/?ref=ethanmick.com">Babel</a>, and more. Each of these is a separate tool but needs to work in harmony to build, test, and deploy a modern web app.</p><p>Bun does all of that.</p><p>And it does it in a single binary: <code>bun</code>.</p><p>It&apos;s hard to overstate how powerful that is. Bun is the runtime for your code. It&apos;s also the package manager that will manage your dependencies. It&apos;s a weird artifact of time that has caused <code>npm</code> and <code>node</code> to be separate binaries and projects.</p><p>Bun looked at all that and said: &quot;Wait. This makes no sense!&quot;</p><p> Bun goes further, though. It can run TypeScript and JSX code without needing external code. Yes, it still compiles TS to JS internally, but that&apos;s abstracted away from the user. You can just do:</p><pre><code class="language-bash">bun script.ts</code></pre><p>And it just... works. In fact, it works so well that they recommend not necessarily transpiling your code for production. Leave it all in TypeScript. Let Bun do the work for you.</p><p>The goal is to have Bun be perfectly compatible with Node.js and for it to be a drop-in replacement. There are lots of <a href="https://bun.sh/guides?ref=ethanmick.com">guides</a> you can take a look at to see how you can use Bun with our favorite framework.</p><p>I&apos;m going to try it out for my next projects and hopefully switch over for good. Let me know your experience! </p><hr><h2 id="learn-to-build-saas"> Learn to Build SaaS</h2><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/ot9yuKg15iA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Set up Google OAuth with Next.js using Next-Auth!"></iframe></figure><p>In my <a href="https://youtube.com/live/3lAsw0vNuDo?feature=share&amp;ref=ethanmick.com">Time Tracker series</a>, I set up the app to use OAuth with Google, but I never came back and created a video for it. Well, here it is! This walks through the entire Google Cloud Console part as well as the Next-Auth part. It pairs well with my <a href="https://youtu.be/2kgqPvs0j_I?ref=ethanmick.com">Next-Auth guide</a> if you want to customize it.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Build your world" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>When using the AWS command line, you can quickly sync a directory to an S3 bucket with a single command:</p><pre><code class="language-bash">aws s3 sync ./local-folder/ s3://bucket-name-here/</code></pre><p>This is useful for backing up files or hosting a website.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,779 (+175 in the last 7 days)</strong></li><li>Newsletter Members: <strong>696 (+47 in the last 7 days)</strong></li></ul><p>This wraps up week two of successfully publishing videos on time! Big win in my book.</p><p>In other exciting news, I signed up my next big client! This contract will be worth about $75,000 and extend until the end of the year. I&apos;ll be helping the company with their DevOps layer (Yes, I can do DevOps, thanks for asking), as well as other work they want me to tackle. I think this will turn into a great series of how to run your apps in production so that when things break, you know how to fix them.</p><p>I&apos;m hoping to still land one more deal in the next few weeks, but it&apos;s been a slow process. </p><hr><h2 id="last-byte">Last Byte</h2><ul><li>A fantastic story on debugging a problem: My car <a href="http://www.cs.cmu.edu/~wkw/humour/carproblems.txt?ref=ethanmick.com">is allergic to vanilla</a> ice cream.</li><li>No sacred <a href="https://basta.substack.com/p/no-sacred-masterpieces?ref=ethanmick.com">masterpieces</a>.</li><li><a href="https://github.com/premieroctet/next-admin?ref=ethanmick.com">Full-featured admin</a> for Next.js and Prisma.</li></ul>]]></content:encoded></item><item><title><![CDATA[How to set up Google OAuth with Next.js using Next-Auth]]></title><description><![CDATA[<figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/ot9yuKg15iA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Set up Google OAuth with Next.js using Next-Auth!"></iframe></figure><p>This guide runs through the full setup of OAuth with Google as the identity provider. This will let your users log in with Google.</p><h2 id="google-cloud-console">Google Cloud Console</h2><p>The best way to get through the console is to watch the video above.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://console.cloud.google.com/?ref=ethanmick.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Google Cloud Platform</div><div class="kg-bookmark-description">Google Cloud Platform lets you build,</div></div></a></figure>]]></description><link>https://ethanmick.com/how-to-set-up-google-oauth-with-next-js-using-next-auth/</link><guid isPermaLink="false">650b36f4be42070001f3d431</guid><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Wed, 20 Sep 2023 18:23:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1529612700005-e35377bf1415?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdvb2dsZXxlbnwwfHx8fDE2OTUyMzQwMzF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/ot9yuKg15iA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Set up Google OAuth with Next.js using Next-Auth!"></iframe></figure><img src="https://images.unsplash.com/photo-1529612700005-e35377bf1415?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGdvb2dsZXxlbnwwfHx8fDE2OTUyMzQwMzF8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="How to set up Google OAuth with Next.js using Next-Auth"><p>This guide runs through the full setup of OAuth with Google as the identity provider. This will let your users log in with Google.</p><h2 id="google-cloud-console">Google Cloud Console</h2><p>The best way to get through the console is to watch the video above.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://console.cloud.google.com/?ref=ethanmick.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Google Cloud Platform</div><div class="kg-bookmark-description">Google Cloud Platform lets you build, deploy, and scale applications, websites, and services on the same infrastructure as Google.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://console.cloud.google.com/favicon.ico" alt="How to set up Google OAuth with Next.js using Next-Auth"></div></div><div class="kg-bookmark-thumbnail"><img src="https://ssl.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_74x24dp.png" alt="How to set up Google OAuth with Next.js using Next-Auth"></div></a></figure><p>You will need to create your OAuth consent screen and then create the OAuth Credentials. With those, you can setup Next-Auth.</p><h3 id="session-helper">Session Helper</h3><p>I like to create a helper function that will return the user in a session from the server.</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { User, getServerSession } from &apos;next-auth&apos;

export const getUserSession = async (): Promise&lt;User&gt; =&gt; {
  const authUserSession = await getServerSession()
  return authUserSession?.user
}</code></pre><figcaption>lib/session.ts</figcaption></figure><p>If you need to pass through additional data, you&apos;ll need to use the same <code>session</code> method to ensure that data is correctly passed through:</p><pre><code>import { User, getServerSession } from &apos;next-auth&apos;

export const session = async ({ session, token }: any) =&gt; {
  session.user.id = token.id
  session.user.tenant = token.tenant
  return session
}

export const getUserSession = async (): Promise&lt;User&gt; =&gt; {
  const authUserSession = await getServerSession({
    callbacks: {
      session
    }
  })
  if (!authUserSession) throw new Error(&apos;unauthorized&apos;)
  return authUserSession.user
}</code></pre><p>The full example makes it a little more clear.</p><h2 id="code">Code</h2><p>Become a free member to get the complete source code below.</p><!--members-only--><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/a-bit-of-saas/examples/tree/main/next/oauth-google?ref=ethanmick.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">examples/next/oauth-google at main &#xB7; a-bit-of-saas/examples</div><div class="kg-bookmark-description">Example code for learning. Contribute to a-bit-of-saas/examples development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/pinned-octocat.svg" alt="How to set up Google OAuth with Next.js using Next-Auth"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">a-bit-of-saas</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/2f9974638e4fd33756b4b239c2725479087daa27aa5373e0cc390b055d1693f8/a-bit-of-saas/examples" alt="How to set up Google OAuth with Next.js using Next-Auth"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Find the Fit]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>The biggest productivity hack is simply finishing what you started. Once you train yourself to be ruthless about that, you are quite cautious about flippantly starting new things. Only start</blockquote>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-find-the-fit/</link><guid isPermaLink="false">65025698cf16fc000171239d</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 15 Sep 2023 10:00:12 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/09/ethan_mick_two_small_cute_happy_computers_holding_hands_dc781a55-79a0-4b25-b2cf-c4a0e4cc47eb.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/09/ethan_mick_two_small_cute_happy_computers_holding_hands_dc781a55-79a0-4b25-b2cf-c4a0e4cc47eb.png" alt="A Bit of SaaS Weekly: Find the Fit"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>The biggest productivity hack is simply finishing what you started. Once you train yourself to be ruthless about that, you are quite cautious about flippantly starting new things. Only start what you truly want to finish.</blockquote><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>For the adventurous, <a href="https://bun.sh/blog/bun-v1.0?ref=ethanmick.com">Bun 1.0</a> is out.</li><li><a href="https://github.com/nginx/unit?ref=ethanmick.com">NGINX Unit</a> is a universal web app server. A lightweight and versatile open-source server that simplifies the application stack.</li><li><a href="https://news.ycombinator.com/item?id=37427127&amp;ref=ethanmick.com">AI Summaries of the top stories.</a> A good idea for product ideas seems to be using AI to summarize high-value content.</li><li>React Aria Components are now <a href="https://react-spectrum.adobe.com/releases/2023-09-07.html?ref=ethanmick.com">in beta</a>.</li></ul><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/09/image-3.png" class="kg-image" alt="A Bit of SaaS Weekly: Find the Fit" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/09/image-3.png 600w, https://ethanmick.com/content/images/size/w1000/2023/09/image-3.png 1000w, https://ethanmick.com/content/images/2023/09/image-3.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="product-founder-fit">Product Founder Fit</h2><p>You hear a lot about product <em>market</em> fit. The idea that in order for a product to be successful, it needs to correctly fit the needs of a market. If your product isn&apos;t successful, either the product was wrong or the market was wrong.</p><p>Or both. Hopefully, you don&apos;t get it that wrong.</p><p>However, there is another fit that is very important when building a product. And that is product <em>founder </em>fit. The idea is that the product and the <strong>founder</strong> also need to be compatible, or else it won&apos;t be successful.</p><p>In 2016, I started working at a startup that was building a process engine. That&apos;s a fancy way to say the product would execute tasks in order, and sometimes there would be a branch in logic. You could write a process to do anything, which was very powerful but also very vague.</p><p>Think: lines of code with if statements. But instead of code, a user would drag and drop pre-made tasks in an order.</p><p>Just like how code can solve anything, so could this.</p><p>It&apos;s hard to sell a vague solution.</p><p>So, we made it more specific over time. If we could solve something more specific with our vague solution, users might see how they could use it to solve more problems.</p><p>Or, that was the idea.</p><p>What happened next was interesting, though. The specific solution we started implementing was around monitoring basic metrics to ensure a website was running.</p><p>Think, Uptime Robot.</p><p>But remember, we weren&apos;t Uptime Robot! We were a vague solution that could solve anything, and we just so <em>happened</em> to pick keeping a website up. But of course, if that&apos;s what we actually wanted to be doing, there were a lot better, faster, and cheaper ways to check if a website is running.</p><p>And so, it failed to gain traction.</p><p>When it failed, we pivoted.</p><p>But when we pivoted, we ended up pivoting <em>into </em>the area we had been doing a lot of work already. The area of monitoring. And so we went deeper down this rabbit hole.</p><p>At the time, it had made sense. We already had code written for this. We already had some infrastructure. It&apos;s not that hard to change what we had, really.</p><p>The problem wasn&apos;t the code. The problem was we had no business in the monitoring space. I wasn&apos;t a DevOps engineer, I was a full-stack engineer. My coworkers weren&apos;t ops, either. While we all knew how to keep apps running, we didn&apos;t have 10, 20, or 30 years of experience doing it.</p><p>We didn&apos;t know what the gaps were in the industry. We didn&apos;t know what the problems were.</p><p>We just kinda stumbled in and pretended like we could build something people wanted in a market we had little business in.</p><p>A place where even though there is a big market and lots of products... we didn&apos;t have a good fit. The market <em>founder</em> fit wasn&apos;t there.</p><p>And so, as you build your products and explore markets to jump into... make sure you are a good match for them.</p><hr><h2 id="learn-to-build-saas">Learn to Build SaaS</h2><p>I&apos;m back to making videos! The live streams will happen again (don&apos;t worry), but I need to master my process for getting videos out the door as well. This week I finish my round-the-world view of authentication by building out a password reset flow.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/vu78olWoV0I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Build a password reset flow for your SaaS app! Next.js | Radix | Tailwind | Source Code"></iframe></figure><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Find the Fit" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>When submitting a form, you will want to ensure the submit button is disabled the moment the form is submitted. This will stop the user from pressing the button multiple times and accidentally submitting twice.</p><p>Before, you would store that information in some state, with <code>useState</code>. But with server actions, you can actually get the status of a form with a new experimental hook called <code>experimental_useFormStatus</code>. This will expose a <code>pending</code> attribute to know when a form has been submitted and is pending.</p><p>To create a dynamic button, you can do this:</p><figure class="kg-card kg-code-card"><pre><code class="language-tsx">&apos;use client&apos;

import { LoadingIcon } from &apos;any-icon-lib&apos;
import { Button } from &apos;./button&apos;
import { ReactNode } from &apos;react&apos;
import { experimental_useFormStatus } from &apos;react-dom&apos;

export const SubmitButton = ({ children }: { children: ReactNode }) =&gt; {
  const { pending } = experimental_useFormStatus()
  return (
    &lt;Button type=&quot;submit&quot; disabled={pending}&gt;
      {pending ? &lt;LoadingIcon className=&quot;animate-spin&quot; /&gt; : children}
    &lt;/Button&gt;
  )
}</code></pre><figcaption>Submit Button</figcaption></figure><p>Note that the button needs<em> </em>to be a client component.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,604 (+132 in the last 7 days)</strong></li><li>Newsletter Members: <strong>649 (+34 in the last 7 days)</strong></li></ul><p>You guys are great.</p><p>I love helping and talking with you on YouTube, Twitter, and email. I really do. When things get hard, I think about all the people I&apos;ve helped on this journey and the ones I still have yet to help.</p><p>I&apos;ve reset some internal expectations to get back on the right track. I&apos;m releasing one video a week for the next four weeks. Even when things get hard, and my contract work is busy, I will get these videos out. That is forcing me to get things done, evaluate what can be better, and ensure my time is being spent well.</p><p>The first video dropped on September 14th, and the link is above. Thanks for your support!</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>For all of us who enjoyed LAN parties growing up, here are <a href="https://thomask.sdf.org/blog/2023/09/09/memories-from-old-lan-parties.html?ref=ethanmick.com">some memories</a>.</li><li>Redesigned Next.js <a href="https://twitter.com/leeerob/status/1701775526459756620?ref=ethanmick.com">console output</a> coming soon.</li><li>In case you want more exciting shadows, here&apos;s <a href="https://boxshadows.xyz/?ref=ethanmick.com">another generator</a> to make them.</li></ul>]]></content:encoded></item><item><title><![CDATA[How to create a password reset flow for your app.]]></title><description><![CDATA[<p>Resetting a password is important to your application&apos;s security and usability. Without a doubt, your app users will sign up and then forget their password.</p><p>Nothing is worse than returning to the login page and getting rejected for a wrong password.</p><blockquote>What password did I use again? Was</blockquote>]]></description><link>https://ethanmick.com/how-to-create-a-password-reset-flow/</link><guid isPermaLink="false">64ff26e85b84b50001ad56b2</guid><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Mon, 11 Sep 2023 15:10:51 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHBhc3N3b3JkfGVufDB8fHx8MTY5NDQ0MzI0NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHBhc3N3b3JkfGVufDB8fHx8MTY5NDQ0MzI0NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="How to create a password reset flow for your app."><p>Resetting a password is important to your application&apos;s security and usability. Without a doubt, your app users will sign up and then forget their password.</p><p>Nothing is worse than returning to the login page and getting rejected for a wrong password.</p><blockquote>What password did I use again? Was there a 1 at the end? Or maybe an exclamation mark?</blockquote><p>Well, forgetting your password and being unable to reset it is even worse.</p><p>Luckily for the builders out there, creating this flow isn&apos;t complicated and will ensure your users can self-serve and get back into your app. And what&apos;s better than that?</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4BB;</div><div class="kg-callout-text">The full source is at the <a href="https://ethanmick.com/how-to-create-a-password-reset-flow/#source-code">bottom of this post</a>.</div></div><h3 id="a-note-about-oauth">A note about OAuth</h3><p>Before we get started, this entire article is a good reason for not asking users to sign up with their email and a password. If a user signs in with an identity provider like Google, Apple, or GitHub, you don&apos;t need to worry about these flows. If a user forgets their password, they reset it with the provider, not you. And you can still ask for the email during the signup flow.</p><p>Another option is to use a third-party provider like Auth0, which has these flows built in.</p><p>However, this isn&apos;t always practical or ideal. So, in those situations, you&apos;ll need to build this.</p><h2 id="overview">Overview</h2><p>The flow isn&apos;t complicated for a password reset. You&apos;ll need the user&apos;s email and trust they have access to it. If they don&apos;t, or you didn&apos;t collect an email during signup (for example, just a username), then you have no way to send them a secure message.</p><p>Password reset works by assuming the user can access the email they signed up with. That email should be secure, and only that user can access it. Therefore, to reset the account with <em>you</em>, you confirm their access by sending a secure, one-time code to their email. The user proves they received that code, and therefore, they are who they say they are.</p><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/09/image-2.png" class="kg-image" alt="How to create a password reset flow for your app." loading="lazy" width="2000" height="1540" srcset="https://ethanmick.com/content/images/size/w600/2023/09/image-2.png 600w, https://ethanmick.com/content/images/size/w1000/2023/09/image-2.png 1000w, https://ethanmick.com/content/images/size/w1600/2023/09/image-2.png 1600w, https://ethanmick.com/content/images/2023/09/image-2.png 2050w" sizes="(min-width: 720px) 720px"></figure><p></p><p>With that, let&apos;s run through an example using Next.js.</p><h2 id="example">Example</h2><p>This example was written with Next.js 13 using the app directory. It used Radix themes, Tailwind CSS, and Mailgun. You can find a link to the source at the bottom of the page.</p><h3 id="schema">Schema</h3><p>When a user requests a password reset, save that token in your database. You can save it on the user record itself, but I suggest creating a new table instead. This will create an automatic audit trail of how many times a user has requested and reset their password.</p><figure class="kg-card kg-code-card"><pre><code class="language-prisma">model User {
  id                  Int                  @id @default(autoincrement())
  email               String               @unique
  password            String
  name                String?
  passwordResetTokens PasswordResetToken[]
}

model PasswordResetToken {
  id        Int       @id @default(autoincrement())
  token     String    @unique
  createdAt DateTime  @default(now())
  resetAt   DateTime?

  user   User @relation(fields: [userId], references: [id])
  userId Int
}
</code></pre><figcaption><p><span style="white-space: pre-wrap;">Prisma Schema Example</span></p></figcaption></figure><h3 id="request-reset-page">Request Reset Page</h3><p>Create a page like <code>/forgot-password</code> in your app and have a form that asks the user to submit their email. This is the email you will send the password reset code to.</p><pre><code class="language-ts">&lt;Card className=&quot;gap-4 flex flex-col&quot;&gt;
  &lt;Flex gap=&quot;4&quot; direction=&quot;column&quot; asChild&gt;
    &lt;form action={submit}&gt;
      &lt;h1 className=&quot;text-2xl font-light&quot;&gt;Reset password&lt;/h1&gt;
      &lt;p&gt;
        Enter your email address to get instructions for resetting your
        password.
      &lt;/p&gt;
      &lt;TextField
        name=&quot;email&quot;
        type=&quot;email&quot;
        size=&quot;3&quot;
        placeholder=&quot;Your email...&quot;
      /&gt;
      &lt;SubmitButton&gt;Reset Password&lt;/SubmitButton&gt;
      &lt;Link href=&quot;/&quot; className=&quot;text-sm text-neutral-700/80 flex items-center&quot;&gt;
        &lt;CaretLeftIcon /&gt;
        &lt;span&gt;Return to Login&lt;/span&gt;
      &lt;/Link&gt;
    &lt;/form&gt;
  &lt;/Flex&gt;
&lt;/Card&gt;</code></pre><p>When the user submits their request, you need to:</p><ol><li>Find the user in your database</li><li>Create a record of the password reset in the database</li><li>Create and send an email to the user with the unique code.</li></ol><pre><code class="language-tsx">export async function resetPassword(data: FormData) {
  const email = data.get(&apos;email&apos;)
  if (!email || typeof email !== &apos;string&apos;) {
    return {
      error: &apos;Invalid email&apos;,
    }
  }

  const user = await prisma.user.findUnique({
    where: { email },
  })

  if (!user) {
    return {
      error: &apos;This email is not registered&apos;,
    }
  }

  const token = await prisma.passwordResetToken.create({
    data: {
      userId: user.id,
      token: `${randomUUID()}${randomUUID()}`.replace(/-/g, &apos;&apos;),
    },
  })

  const mailgun = new Mailgun(formData)
  const client = mailgun.client({ username: &apos;api&apos;, key: API_KEY })

  const messageData = {
    from: `Password Reset &lt;security@${MAILGUN_DOMAIN}&gt;`,
    to: user.email,
    subject: &apos;Reset Password Request&apos;,
    text: `Hello ${user.name}, someone (hopefully you) requested a password reset for this account. If you did want to reset your password, please click here: ${PROTOCOL}://${DOMAIN}/password-reset/${token.token}

For security reasons, this link is only valid for four hours.
    
If you did not request this reset, please ignore this email.`,
  }

  await client.messages.create(MAILGUN_DOMAIN, messageData)
  redirect(&apos;/forgot-password/success&apos;)
}</code></pre><p>This example uses <a href="https://www.mailgun.com/?ref=ethanmick.com">Mailgun</a> to send the email, but any transaction service will do.</p><h3 id="handle-reset-request">Handle Reset Request</h3><p>The above email sends the <code>token</code> to the user&apos;s email as a link. When the user clicks on it, you show them a page where they can enter a new password. When submitted, validate everything is correct, and only then reset the password.</p><p>This form was located at <code>/password-reset</code>.</p><pre><code class="language-tsx">&lt;Card className=&quot;gap-4 flex flex-col&quot;&gt;
  &lt;Flex gap=&quot;4&quot; direction=&quot;column&quot; asChild&gt;
    &lt;form action={submit}&gt;
      &lt;h1 className=&quot;text-2xl font-light&quot;&gt;Choose a new password&lt;/h1&gt;
      &lt;p&gt;You can reset your password here.&lt;/p&gt;
      &lt;TextField
        name=&quot;password&quot;
        type=&quot;password&quot;
        size=&quot;3&quot;
        placeholder=&quot;Password&quot;
      /&gt;
      &lt;TextField
        name=&quot;confirm&quot;
        type=&quot;password&quot;
        size=&quot;3&quot;
        placeholder=&quot;Confirm password&quot;
      /&gt;
      {error &amp;&amp; &lt;p className=&quot;text-red-500 text-sm&quot;&gt;{error}&lt;/p&gt;}
      &lt;SubmitButton&gt;Reset Password&lt;/SubmitButton&gt;
      &lt;Link href=&quot;/&quot; className=&quot;text-sm text-neutral-700/80 flex items-center&quot;&gt;
        &lt;CaretLeftIcon /&gt;
        &lt;span&gt;Return to Login&lt;/span&gt;
      &lt;/Link&gt;
    &lt;/form&gt;
  &lt;/Flex&gt;
&lt;/Card&gt;</code></pre><p>On the server, the validation needs to look at the token and the new password and ensure the token is valid, unused, and timely. The passwords need to match as well.</p><pre><code class="language-ts">export async function resetPassword(token: string, data: FormData) {
  const password = data.get(&apos;password&apos;)
  const confirmPassword = data.get(&apos;confirm&apos;)
  if (
    !password ||
    typeof password !== &apos;string&apos; ||
    password !== confirmPassword
  ) {
    return {
      error:
        &apos;The passwords did not match. Please try retyping them and submitting again.&apos;,
    }
  }

  const passwordResetToken = await prisma.passwordResetToken.findUnique({
    where: {
      token,
      createdAt: { gt: new Date(Date.now() - 1000 * 60 * 60 * 4) },
      resetAt: null,
    },
  })

  if (!passwordResetToken) {
    return {
      error:
        &apos;Invalid token reset request. Please try resetting your password again.&apos;,
    }
  }

  const encrypted = await hash(password, 12)

  const updateUser = prisma.user.update({
    where: { id: passwordResetToken.userId },
    data: {
      password: encrypted,
    },
  })

  const updateToken = prisma.passwordResetToken.update({
    where: {
      id: passwordResetToken.id,
    },
    data: {
      resetAt: new Date(),
    },
  })

  try {
    await prisma.$transaction([updateUser, updateToken])
  } catch (err) {
    console.error(err)
    return {
      error: `An unexpected error occured. Please try again and if the problem persists, contact support.`,
    }
  }
  redirect(&apos;/&apos;)
}</code></pre><p>And now you have successfully reset the user&apos;s password!</p><h2 id="source-code">Source Code</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/a-bit-of-saas/examples/tree/main/next/password-reset?ref=ethanmick.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">examples/next/password-reset at main &#xB7; a-bit-of-saas/examples</div><div class="kg-bookmark-description">Example code for learning. Contribute to a-bit-of-saas/examples development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/pinned-octocat.svg" alt="How to create a password reset flow for your app."><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">a-bit-of-saas</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/2f9974638e4fd33756b4b239c2725479087daa27aa5373e0cc390b055d1693f8/a-bit-of-saas/examples" alt="How to create a password reset flow for your app."></div></a></figure>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Heat Wave]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>The end of summer has come with a vicious heat wave in the northeast US. When it&apos;s this hot, all I can think of is how nice it</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-heat-wave/</link><guid isPermaLink="false">64fa2ad75b84b50001ad5272</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 08 Sep 2023 10:00:52 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/09/ethan_mick_the_hot_sun_beating_down_over_a_dry_dessert_with_cra_693100da-1b01-4681-af19-a45cd1881c24.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/09/ethan_mick_the_hot_sun_beating_down_over_a_dry_dessert_with_cra_693100da-1b01-4681-af19-a45cd1881c24.png" alt="A Bit of SaaS Weekly: Heat Wave"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>The end of summer has come with a vicious heat wave in the northeast US. When it&apos;s this hot, all I can think of is how nice it would be to have some water to swim in! The best friends are the ones with pools.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://astro.build/blog/astro-3/?ref=ethanmick.com">Astro 3.0</a> was released.</li><li>Making sense of <a href="https://www.joshwcomeau.com/react/server-components/?ref=ethanmick.com">React Server Components</a>.</li><li>Open-source <a href="https://github.com/measuredco/puck?ref=ethanmick.com">visual editor</a> for React.</li><li>React component library to build <a href="https://github.com/frolicorg/frolic-react?ref=ethanmick.com">analytics dashboard</a>.</li><li>An open-graph meta tag <a href="https://pika.style/tool/open-graph-test?ref=ethanmick.com">test and validate</a> site.</li></ul><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/09/image.png" class="kg-image" alt="A Bit of SaaS Weekly: Heat Wave" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/09/image.png 600w, https://ethanmick.com/content/images/size/w1000/2023/09/image.png 1000w, https://ethanmick.com/content/images/2023/09/image.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="just-launch-it">Just launch it</h2><p>Too many individuals grapple with the idea of launching a project that isn&apos;t polished to perfection. They ponder, second-guess, and tweak details endlessly in pursuit of a flawless product. However, you should launch as soon as you can, even with something imperfect.</p><p>Here are some clear wins when you launch as soon as you&apos;ve solved the core problem.</p><p><strong>Perfection is Illusive</strong>: First and foremost, perfection is a moving target. What appears perfect today might be outdated or irrelevant tomorrow. Instead of chasing an ever-evolving ideal, it&apos;s more pragmatic to start with a minimum viable product (MVP) and refine it as you learn more.</p><p><strong>Rapid Feedback</strong>: By launching earlier, you allow yourself to collect invaluable feedback from real users or stakeholders. This real-world data is often more beneficial than any number of brainstorming sessions or theoretical models. You learn what genuinely works, what doesn&#x2019;t, and what users truly want. If you launched too soon, you&apos;ll know immediately. Fix it with rapid improvement.</p><p><strong>Efficiency</strong>: Perfecting a product or project often requires a considerable investment of time, money, and energy. If the &apos;perfected&apos; version doesn&#x2019;t resonate with the market, these resources are wasted. Starting with a simpler version allows for a more efficient allocation of resources based on actual needs and feedback. Your users will tell you what they want you to do next.</p><p><strong>Avoiding Analysis Paralysis</strong>: Spending too much time in the planning phase can lead to indecision and stagnation. The fear of making mistakes or not achieving perfection can be paralyzing. By pushing to start and then iterate, you sidestep this potential pitfall and maintain forward momentum.</p><p>While the desire for perfection is natural, it&#x2019;s essential to recognize that in many scenarios, launching and refining as you go is a more strategic approach. Embracing the cycle of learning, pivoting, and iterating can lead to better products, more satisfied users, and more innovation.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Heat Wave" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>It&apos;s not hard to pass a component as a property in React. This is a great tip from <a href="https://www.totaltypescript.com/pass-component-as-prop-react?ref=ethanmick.com">Matt</a>.</p><pre><code class="language-tsx">const Row = (props: {
  icon: React.ComponentType&lt;{
    className?: string;
  }&gt;;
}) =&gt; {
  return (
    &lt;div&gt;
      &lt;props.icon className=&quot;h-8 w-8&quot; /&gt;
    &lt;/div&gt;
  );
};
 
&lt;Row icon={UserIcon} /&gt;;</code></pre><p>In the above the <code>icon</code> proper is <code>React.ComponentType</code>. You can pass in the props <em>that</em> the component takes by passing <code>{ className?: string }</code> to <code>React.ComponentType</code>, indicating that this is a component that can receive a <code>className</code> prop.</p><p>This basically says <code>icon</code> can be any component that can receive a <code>className</code> prop. This is a very flexible type, and it&apos;s easy to use.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,472 (+173 in the last 7 days)</strong></li><li>Newsletter Members: <strong>615</strong> (<strong>+26 in the last 7 days)</strong></li></ul><p>I&apos;m still working on updating my process to get content out the door. The most consistent thing I do is this newsletter, so I&apos;m trying to expand what makes it successful to other parts of my content.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>The <a href="https://dannorth.net/2023/09/02/the-worst-programmer/?ref=ethanmick.com">Worst</a> Programmer I Know.</li><li>A peer-to-peer <a href="https://transfer.zip/?ref=ethanmick.com">file transfer</a> web app.</li><li>Another <a href="https://github.com/480-Design/Solar-Icon-Set?ref=ethanmick.com">icon</a> set.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: End of Summer]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>It&apos;s Labor Day weekend in the United States, the unofficial end of summer. I hope you all had a fantastic summer and are looking forward to the end</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-end-of-summer/</link><guid isPermaLink="false">64f0b67d44686400019c9ead</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 01 Sep 2023 10:00:39 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/08/ethan_mick_a_beautiful_new_england_fall_day_high_def_hyper_real_23a5b07c-584b-4810-ac92-f710084cd898.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/08/ethan_mick_a_beautiful_new_england_fall_day_high_def_hyper_real_23a5b07c-584b-4810-ac92-f710084cd898.png" alt="A Bit of SaaS Weekly: End of Summer"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>It&apos;s Labor Day weekend in the United States, the unofficial end of summer. I hope you all had a fantastic summer and are looking forward to the end of the year. Let&apos;s get it!</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>Vercel&apos;s <a href="https://demo.vercel.pub/platforms-starter-kit?ref=ethanmick.com">Platform Demo Kit</a> for multi-tenant Next.js projects.</li><li>Learn more about SEO&apos;s <a href="https://csswizardry.com/2023/07/core-web-vitals-for-search-engine-optimisation/?ref=ethanmick.com">interaction with</a> core web vitals.</li><li>A fun tale on why I highly recommend SaaS development <a href="https://solutional.ee/blog/2023-08-26-Prisoners-of-Google-Android-Development.html?ref=ethanmick.com">over mobile app</a> development.</li><li>Minimal snippets for <a href="https://smolcss.dev/?ref=ethanmick.com">modern CSS layouts</a> and components.</li></ul><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/08/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: End of Summer" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/08/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/08/image-4.png 1000w, https://ethanmick.com/content/images/2023/08/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>(Which one is which?)</figcaption></figure><h2 id="security-vs-usability">Security vs. Usability</h2><p>There was an <a href="https://www.sjoerdlangkemper.nl/2023/08/16/session-timeout/?ref=ethanmick.com">article</a> recently talking about how short session length does not increase security. There are a lot of <a href="https://news.ycombinator.com/item?id=37173339&amp;ref=ethanmick.com">good comments</a> on the article, which are worth a look if you&apos;re interested.</p><p>This is, of course, one of those topics that is incredibly nuanced. And for almost any service, there will be tradeoffs that need to be thought through and the best path implemented. And once you&apos;ve implemented it, measure the impact and adjust as needed.</p><p>There&apos;s often no shortcut.</p><p>Session length is just one of these debates. Banks tend to have very short sessions. GitHub has kept me logged in literally forever. Which one is more secure?</p><p>Personally, I lean toward convenience on this one (infinite sessions!) because the services I build myself don&apos;t have a lot of valuable information. Needing to log in frequently causes friction that doesn&apos;t help <em>early-stage</em> products that are still finding product market fit. For more enterprise customers, they&apos;ll often opt for shorter sessions.</p><p>Another good example of this debate is a password reset feature. When a user inputs their email, and that email does <strong>not </strong>exist, you can either:</p><ol><li>Inform the user this email does not exist. This often means the user mistyped their email or they used a different email, and they are happy to understand why they are not getting a password reset email.</li><li>Informing the user if their email exists or not is <em>leaking</em> information about which emails are in your database, so you <em>always</em> return a success message no matter what. More secure but a worse experience.</li></ol><p>This is one of those small things that I enjoy seeing how different services handle. I&apos;ve generally seen most services do option 2, but lately, I&apos;ve seen more services do it the first way. Other protections put in place, such as rate limiting, can decrease the attacker&apos;s ability to abuse the feature.</p><p>It&apos;s always a tradeoff, though.</p><p>What side do you fall on? </p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: End of Summer" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>I&apos;ve been... hacking on a side project lately. It&apos;s an online multiplayer implementation of <a href="https://www.riograndegames.com/games/dominion/?ref=ethanmick.com">Dominion</a>. Yes, yes, I know, you can already play <a href="https://www.dominion.games/?ref=ethanmick.com">online</a>. The point isn&apos;t to play; the point is to learn how to create a game server implementation that handles asynchronous client input.</p><p><em>Anyways</em>, the server is a Socket.io web socket server, and the frontend is pure React. This is one of those moments in life where Next.js is just <em>really bad</em> and would not be a good option.</p><p>Of course, now that I&apos;m outside the Next.js world, I need to rely on... other tooling. So I have <code>nodemon</code> set it to hot restart the server and <a href="https://vitejs.dev/?ref=ethanmick.com">Vite</a> for the frontend tooling. It&apos;s fine. The Nodemon setup was a little tricky since it has a different TypeScript configuration than the client. So when you&apos;re doing this, here&apos;s what you can do.</p><p>Create a <code>nodemon.json</code> file:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;watch&quot;: [&quot;src/&quot;],
  &quot;ext&quot;: &quot;.ts,.js,.tsx&quot;,
  &quot;ignore&quot;: [&quot;src/**/*.spec.ts&quot;],
  &quot;exec&quot;: &quot;ts-node --project tsconfig.server.json src/server/index.ts&quot;
}
</code></pre><figcaption><code>nodemon.json</code></figcaption></figure><p>This is read by default, so your dev command can just be <code>nodemon</code>. And then, to configure a backend server with TypeScript, your <code>tsconfig.server.json</code> file should have the following:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;dist&quot;,
    &quot;sourceMap&quot;: true,
    &quot;target&quot;: &quot;esnext&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;esModuleInterop&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noImplicitAny&quot;: true,
    &quot;strict&quot;: true,
    &quot;skipLibCheck&quot;: true
  },
  &quot;include&quot;: [&quot;src/**/*.ts&quot;]
}
</code></pre><figcaption><code>tsconfig.server.json</code></figcaption></figure><p>That configuration is set up for a regular Node.js project! Now you can run your own server without interfering with Vite.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,299 (+281 in the last 7 days)</strong></li><li>Newsletter Members: <strong>589</strong> (<strong>+31 in the last 7 days)</strong></li></ul><p>I need to figure out my YouTube schedule. Also, I should be starting a new freelance project soon (Hurray!) that will take up quite a bit of my time. Sooo, I really need to do some time management and ensure I have my priorities figured out.</p><p>That will probably mean less streaming, so I can break up the content process a little more. There are plenty of videos to be made, so no worries there. I just need to tighten up my process for making them.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>Of course, once I learned Terraform, there was a massive break in the community around Hashicorp&apos;s new license. Introducing: <a href="https://opentf.org/announcement?ref=ethanmick.com">OpenTF</a>.</li><li>Tell your boss about <a href="https://openai.com/blog/introducing-chatgpt-enterprise?ref=ethanmick.com">ChatGPT Enterprise</a> so you can automate yourself out of a job faster.</li><li>An oldie but goodie - when your coworker does great work, <a href="https://jvns.ca/blog/2020/07/14/when-your-coworker-does-great-work-tell-their-manager/?ref=ethanmick.com">tell their manager</a>.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Squeeze Simplicity]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>Friends don&#x2019;t let friends pick us-east-1.<br><br>- AWS Proverbs</blockquote><p>That is all. Continue along your US East 2 day.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://ai.meta.com/blog/code-llama-large-language-model-coding/?ref=ethanmick.com">Code Llama 2</a>, an open-source LLM</li></ul>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-squeeze-simplicity/</link><guid isPermaLink="false">64e78725ad19aa000191979d</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 25 Aug 2023 10:00:19 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/08/ethan_mick_a_hand_squeezing_a_lemon_super_hard_and_juice_coming_db1448c3-9aec-4303-9ce6-378ee977d073.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/08/ethan_mick_a_hand_squeezing_a_lemon_super_hard_and_juice_coming_db1448c3-9aec-4303-9ce6-378ee977d073.png" alt="A Bit of SaaS Weekly: Squeeze Simplicity"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>Friends don&#x2019;t let friends pick us-east-1.<br><br>- AWS Proverbs</blockquote><p>That is all. Continue along your US East 2 day.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li><a href="https://ai.meta.com/blog/code-llama-large-language-model-coding/?ref=ethanmick.com">Code Llama 2</a>, an open-source LLM for coding.</li><li>Add <code>dir=auto</code> to your inputs for <a href="psa: Add dir=" auto" to your inputs and textareas.">automatic</a> right-to-left orientation.</li><li>useMemo <a href="https://edvins.io/usememo-overdose?ref=ethanmick.com">overdose</a> (You know who you are).</li><li>Do <a href="Short session expiration does not help security">short sessions</a> help security?</li></ul><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/08/image-3.png" class="kg-image" alt="A Bit of SaaS Weekly: Squeeze Simplicity" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/08/image-3.png 600w, https://ethanmick.com/content/images/size/w1000/2023/08/image-3.png 1000w, https://ethanmick.com/content/images/2023/08/image-3.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="the-price-of-complexity">The Price of Complexity</h2><p>It&apos;s hard to keep things simple.</p><p>Complexity is the natural state of software, and without care and maintenance, the code will devolve.</p><p>The hard part is knowing what is complex vs. simple and fighting against that complexity before it rears its head.</p><p>The first part comes with experience. Certain solutions are simpler than others. For example, monolith software is simpler than microservices. A single process is simpler than multiple processes. One thread is simpler than concurrent threads.</p><p>Over time, you&apos;ll get better and better at knowing which solutions are complicated. Sometimes, that will just require building the solution to get a feel for it. And sometimes, that complexity is worth it!</p><p>For example, setting up a GraphQL endpoint is more complex than setting up individual (perhaps RESTful) APIs. You need a GraphQL Schema, resolvers, and a server, all on top of the API endpoint. However, in return, you get type safety and the ability to request more or less data on the fly from the client. That tradeoff is often worth it.</p><p>This <a href="https://blog.danslimmon.com/2023/08/11/squeeze-the-hell-out-of-the-system-you-have/?ref=ethanmick.com#like-2777">article</a> (which inspired this post) talks about a team debating how to resolve their database being put under too heavy load. The options presented would resolve it but would also increase complexity by a lot. The simple solution (but not easy) was simply to track down performance issues in the code, optimize queries, and rewrite code to avoid expensive tasks.</p><p>This is a great example of fighting back against complexity. While other solutions might sound fun and exciting, they offered an irrevocable increase in complexity. Avoid that for as long as you can.</p><p>For Recast, I&apos;m already annoyed that my converter process is a different service. But building out a more robust pipeline will increase the complexity even more, so I&apos;ll push what I have until it can&apos;t take it anymore.</p><hr><h2 id="learn-to-build-saas">Learn to Build SaaS</h2><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/VvvcL_hHb5c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Converters. Converters Everywhere. | Session 6 | Learn to build SaaS End-to-End | Node.js, Graphs"></iframe></figure><p>Just a single stream this week as I refactored the converters for <a href="https://recastfile.com/?ref=ethanmick.com">Recast</a>, the ultimate file conversion service. I&apos;ve been doing some research into some of the most challenging converters, and I think there are some good solutions. I&apos;ll be looking to implement them next week at the latest.</p><p>Besides making subpages for specific conversions, I think I&apos;ll branch out shortly and build some auxiliary projects that&apos;ll help Recast. Things like user reviews and contact forms are prime targets for their own SaaS, and I can open source a lot of that.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Squeeze Simplicity" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>I&apos;ve started writing tests for the converters to isolate problems earlier in the dev cycle. This is tricky because the converters rely on local executables that are installed. In production, that will be Debian. Locally, it was macOS. However, some of these programs are... really challenging to install. I simply don&apos;t want them locally.</p><p>This is the perfect use case for Docker! I&apos;ve created a Docker image that contains all the executables that the tests need:</p><pre><code class="language-docker">FROM node:18 as builder

WORKDIR /usr/src/app

RUN apt update \
    &amp;&amp; apt install -y build-essential libjpeg-dev libpng-dev libtiff-dev potrace imagemagick ffmpeg pandoc \
    &amp;&amp; npm install -g pnpm
    
COPY pnpm-lock.yaml package.json ./
RUN pnpm install</code></pre><p>After you build this, the trick is to run the tests in the container <em>without</em> needing to rebuild it all the time. This is easily achieved with a volume mount, but that&apos;ll also copy in the <code>node_modules</code>. This is a problem since the docker image and host are different OS&apos;s.</p><p>The fix is to add node modules to the <code>.gitignore</code> file:</p><figure class="kg-card kg-code-card"><pre><code>node_modules/</code></pre><figcaption>.gitignore</figcaption></figure><p>So they aren&apos;t copied into the image at all.</p><p><em>Then</em> you need to shadow mount a directory when using the container so the <code>node_modules</code> within the container is not overridden:</p><pre><code>docker run --rm -v $(pwd):/usr/src/app -v /usr/src/app/node_modules -w /usr/src/app imagename:latest pnpm test</code></pre><p>And now you can easily run tests in Docker without needing to rebuild the image all the time!</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>3,018 (+391 &#x1F389;&#x1F389; in the last 7 days)</strong></li><li>Newsletter Members: <strong>558</strong> <strong>+41 in the last 7 days)</strong></li></ul><p>YouTube has been showing more people my &quot;Buy and Sell SaaS&quot; series, which accounted for a lot of new subscribers. Clearly, there is quite a bit of demand for really making money from building SaaS. The tricky thing is, of course, it&apos;s still not easy. Building a product that&apos;s worth money takes time, and it&apos;s impossible to do that in a single 4-hour video.</p><p>That being said, hopefully, there will be some more opportunities to intertwine building SaaS and cashing out.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li><a href="https://jeanhsu.substack.com/p/ask-vs-guess-culture?ref=ethanmick.com">Ask vs Guess</a> culture.</li><li>Debugging <a href="https://chirag-gupta.hashnode.dev/how-to-solve-hydration-error-in-nextjs?ref=ethanmick.com">hydration errors</a> in Next.js.</li><li>How a startup <a href="https://blog.johnqian.com/startup-spark?ref=ethanmick.com">loses its</a> spark.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Value Pricing]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>A small note and a humble victory &#x2013; today, I had to pay for the larger plan on <a href="https://ghost.org/?ref=ethanmick.com">Ghost</a> because my newsletter has too many members. I totally forgot this</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-value-pricing/</link><guid isPermaLink="false">64de86da1773230001a997a4</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 18 Aug 2023 10:00:01 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/08/ethan_mick_A_womans_hand_holding_a_platinum_bar_engraved_with_a_b9aaf27b-6d4c-494f-ac0a-00348973014c.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/08/ethan_mick_A_womans_hand_holding_a_platinum_bar_engraved_with_a_b9aaf27b-6d4c-494f-ac0a-00348973014c.png" alt="A Bit of SaaS Weekly: Value Pricing"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>A small note and a humble victory &#x2013; today, I had to pay for the larger plan on <a href="https://ghost.org/?ref=ethanmick.com">Ghost</a> because my newsletter has too many members. I totally forgot this was even a thing when I set it up originally, I never expected to have over 500 people subscribed.</p><p>Thank you, each and every one of you. It means the world to me.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>Supabase had its launch week and released some goodies, including an <a href="https://supabase.com/blog/using-supabase-with-vercel?ref=ethanmick.com">updated integration with Vercel</a> and <a href="https://supabase.com/blog/supabase-local-dev?ref=ethanmick.com#branching-and-preview-environments">database branching</a>.</li><li><a href="https://atlasicons.vectopus.com/?ref=ethanmick.com">Atlas Icons</a>, another icon library. And you thought I was done.</li><li><a href="https://backstage.io/?ref=ethanmick.com">Backstage.io</a> is a SaaS product that helps internal teams catalog and manage their services. There&apos;s literally a SaaS for everything.</li><li><a href="https://htmx.org/?ref=ethanmick.com">HTMX</a> is a small library that lets you code interactivity right in your HTML. I wouldn&apos;t recommend it for serious projects yet, but it is cool.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/08/image-1.png" class="kg-image" alt="A Bit of SaaS Weekly: Value Pricing" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/08/image-1.png 600w, https://ethanmick.com/content/images/size/w1000/2023/08/image-1.png 1000w, https://ethanmick.com/content/images/2023/08/image-1.png 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="thoughts-on-pricing-your-product">Thoughts on Pricing Your Product</h2><p>It came up on stream on how I was planning on charging for Recast, a flat subscription vs a usage-based system. This brought up a couple of good points that I thought were important.</p><h3 id="the-price-you-charge-is-related-to-the-value-you-provide-not-the-cost">The price you charge is related to the value you provide, not the cost.</h3><p>Well, at least it should be. Usage-based systems can easily be turned into charging just enough money to cover how much the service costs to run. For example, charging per GB of storage or per conversion a user runs.</p><p>The problem with this is that with optimizations, you can get this cost pretty low, so you won&apos;t be charging a lot of money. AWS&apos; S3 storage is incredibly cheap, allowing you to store vast amounts of data. But <em>what</em> you are storing might be incredibly valuable, such that if the service were to disappear, you would suffer greatly.</p><p>What you are paying for isn&apos;t the storage; it&apos;s the service. It&apos;s the value you provide.</p><p>So if you are going to consider usage-based pricing, ensure you are not just covering your costs. Price accordingly to the value your service is delivering.</p><p><strong>Usage-based pricing is complicated.</strong></p><p>Stay away from complexity as long as you can. And usage-based pricing is more complex than a simple subscription. There is more to keep track of to ensure the billing is accurate.</p><p>Not only is it complicated for the developer, but it&apos;s complicated for the user. They don&apos;t know how much they are going to pay month to month. And having a consistent number is very helpful for accounting and selling into the enterprise. Imagine this conversation both ways:</p><blockquote>Them: How much does this cost?<br>Recast: It&apos;s $1,000/mo for the enterprise plan.</blockquote><p>Or</p><blockquote>Them: How much does this cost?<br>Recast: Well, it&apos;s a flat fee of $200 a month, and then depending on how many conversions you do it&apos;ll cost $0.05 per conversion, unless they are videos, then it&apos;s $0.1 per minute of footage, or if you use a &quot;premium&quot; API which costs $1 per usage...</blockquote><p>You get the point. Wrap it all up, give them an upper limit, and call it a day. It&apos;s a win-win.</p><hr><h2 id="learn-to-build-saas">Learn to Build SaaS</h2><p>This week I&apos;ve run two live streams as I build out <a href="https://recastfile.com/?ref=ethanmick.com">Recast</a>. There are always a few people who show up, so it&apos;s a lot of fun!</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/Zog4WeKPruc?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Recast Blog &amp; Content Generation | Session 4 | Learn to build SaaS End-to-End | UI, UX"></iframe></figure><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/DttImAoxcLE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Recast RSS &amp; Automation | Session 5 | Learn to build SaaS End-to-End | UI, UX, GPT, Zapier"></iframe></figure><p>Recast is a file conversion SaaS that lets you convert quickly and easily. As I&apos;ve been working through the project, I&apos;ve been talking and thinking about the bigger play. Instead of just converting files, the service will start letting you change and mutate the data within the files. Users will be able to build complex flows that can be shared that&apos;ll transform their data with no code.</p><p>I think there&apos;s something in there that could be worth something. It&apos;s worth exploring.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Value Pricing" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>I was playing around with <code>gm</code>, which is a Node.js library that uses Image Magick to manipulate images. It has an easy-to-use API which is nice. If you&apos;re looking to manipulate images from Node, this is a good way to do it. I didn&apos;t end up using the library in Recast, so here&apos;s what I did:</p><pre><code class="language-js">import GM from &apos;gm&apos;

const gm = GM.subClass({ imageMagick: &apos;7+&apos; })

gm(&apos;example.png&apos;)
  .stroke(&apos;#171717&apos;)
  .fill(&apos;#171717&apos;)
  .font(&apos;inter.ttf&apos;, 100)
  .gravity(&apos;Center&apos;)
  .drawText(0, 0, &apos;magick!&apos;)
  .write(&apos;drawing.png&apos;, function (err) {
    console.log(&apos;DONE&apos;, err)
    if (!err) console.log(&apos;done&apos;)
  })</code></pre><p>This snippet reads in an image and then writes &quot;magick!&quot; in the center of it in the Inter font.</p><hr><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>2,627 (+488 &#x1F389;&#x1F389;&#x1F389; in the last 7 days)</strong></li><li>Newsletter Members: <strong>517 (+38 in the last 7 days)</strong></li></ul><p>Okay, this is getting ridiculous. I mean, great! I mean, what. In the last 7 days, I&apos;ve almost gotten 500 new subscribers. YouTube appears to have rewarded my consistent live stream, even if my video posting is... less than consistent. A majority of my new subs came from the live streams rather than videos.</p><p>The algorithm is also showing my content to more people, almost twice as many. That&apos;s helping to bring in more people.</p><p>It&apos;s awesome! And fun. And wild. You all are great; thanks for sticking around.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>Firefox Finally <a href="https://www.phoronix.com/news/Firefox-Faster-SunSpider?ref=ethanmick.com">Outperforming</a> Google Chrome In SunSpider. I guess it&apos;s time to switch.</li><li>A video game where you are the <a href="https://plbrault.com/blog-posts/i-created-the-nerdierst-game-ever-en/?ref=ethanmick.com">operating system</a>. </li><li>How to bypass YouTube video <a href="https://blog.0x7d0.dev/history/how-they-bypass-youtube-video-download-throttling/?ref=ethanmick.com">download throttling</a>.</li></ul>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Rise of the UI Libraries]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>Good design isn&#x2019;t just beautiful and incredible and boundary-pushing, it also remembers what it means to be human.</blockquote><p>- <a href="https://cabel.com/2023/07/30/fantasy-meets-reality/?ref=ethanmick.com">Cabel Sasser</a></p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>Shadcn/ui released <a href="https://ui.shadcn.com/themes?ref=ethanmick.com">themes</a></li></ul>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-rise-of-the-ui-libraries/</link><guid isPermaLink="false">64d5490ccc75a0000100fae8</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 11 Aug 2023 10:00:19 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/08/ethan_mick_the_react.js_logo_rising_off_the_ground_in_a_cloud_o_18f98035-790d-4292-b5e9-e8de2b0a62c3.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/08/ethan_mick_the_react.js_logo_rising_off_the_ground_in_a_cloud_o_18f98035-790d-4292-b5e9-e8de2b0a62c3.png" alt="A Bit of SaaS Weekly: Rise of the UI Libraries"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><blockquote>Good design isn&#x2019;t just beautiful and incredible and boundary-pushing, it also remembers what it means to be human.</blockquote><p>- <a href="https://cabel.com/2023/07/30/fantasy-meets-reality/?ref=ethanmick.com">Cabel Sasser</a></p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>Shadcn/ui released <a href="https://ui.shadcn.com/themes?ref=ethanmick.com">themes</a>!</li><li>Radix UI released <a href="https://www.radix-ui.com/?ref=ethanmick.com">themes</a> (I&apos;m not even kidding).</li><li>Create a <a href="https://georgefrancis.dev/writing/create-a-generative-landing-page-and-webgl-powered-background/?ref=ethanmick.com">Generative</a> Landing Page (I did this for <a href="https://recastfile.com/?ref=ethanmick.com">Recast</a>)</li><li><a href="https://oimo.io/works/water/?ref=ethanmick.com">Water</a> (go on, I&apos;ll wait).</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/08/ui-3.png" class="kg-image" alt="A Bit of SaaS Weekly: Rise of the UI Libraries" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/08/ui-3.png 600w, https://ethanmick.com/content/images/size/w1000/2023/08/ui-3.png 1000w, https://ethanmick.com/content/images/2023/08/ui-3.png 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="which-ui-library-are-you-using-now">Which UI Library are you using now?</h2><p>It feels like in the past few weeks, we&apos;ve had a bunch of fantastic releases of UI libraries, with more to come. In case you haven&apos;t been hacking on the frontend much, I&apos;ve seen the following:</p><ul><li><a href="https://react-spectrum.adobe.com/react-aria/react-aria-components.html?ref=ethanmick.com">React Aria Components</a></li><li><a href="https://www.radix-ui.com/?ref=ethanmick.com">Radix UI themes</a></li><li><a href="https://ui.shadcn.com/themes?ref=ethanmick.com">Shadcn/ui themes</a></li><li><a href="https://www.youtube.com/watch?v=CLkxRnRQtDE&amp;t=3512s&amp;ref=ethanmick.com">Tailwindcss Catalyst</a> (coming soon)</li><li><a href="https://nextui.org/?ref=ethanmick.com">NextUI v2</a></li></ul><p>These libraries are a new kind of UI library. At their core, they are powered by headless code; either components or hooks. These low-level primitives can be used to build a UI that looks like anything.</p><p>And then, on top of those primitives, the creators are now layering a full UI library that includes all the standard components you would want to use. Some of these libraries can be copied into your code; others are installed.</p><p>The benefit here, though, is because they are built on the primitives, if you want to make changes to the components, you have an easy way to &quot;drop down&quot; a level and get to a lower level API. This amounts to a lot of flexibility and power. You can get started quickly and then customize as you go.</p><p>The biggest question now is which one do you choose?</p><p>That&apos;s a personal choice. I like the Radix JSX component approach, but they don&apos;t have anything for handling some elements, such as buttons. So I often reach for React-Aria and then sprinkle in some Radix as I need to.</p><p>Probably not the best, but I do like the best of both worlds.</p><p>What are you using?</p><hr><h2 id="learn-to-build-saas">Learn to Build SaaS</h2><p>This week I&apos;ve run two live streams as I build out <a href="https://recastfile.com/?ref=ethanmick.com">Recast</a>. There are always a few people who show up, so it&apos;s a lot of fun!</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/8EqSIm4HkjI?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="The name of the SaaS is what?? | Session 2 | Learn to build SaaS End-to-End | UI, UX, SEO, Content"></iframe></figure><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/0JOaGIC5Vyc?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Recast - File Conversion | Session 3 | Learn to build SaaS End-to-End | UI, UX"></iframe></figure><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/07/partner.jpg" class="kg-image" alt="A Bit of SaaS Weekly: Rise of the UI Libraries" loading="lazy" width="862" height="696" srcset="https://ethanmick.com/content/images/size/w600/2023/07/partner.jpg 600w, https://ethanmick.com/content/images/2023/07/partner.jpg 862w" sizes="(min-width: 720px) 720px"></figure><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>2,099 (+226 &#x1F389; in the last 7 days)</strong></li><li>Newsletter Members: <strong>479 (+28 in the last 7 days)</strong></li></ul><p>Look at those YouTube subscribers! Holy cow! I&apos;m not sure what exactly I did to make the algorithm like me, but I added over 200 subs in the past week. Looking at the analytics, I think posting the videos really helped &#x2013; which means, of course, I should post more... but I need to jump on that and do them. </p><p>I&apos;m going to try and make some more videos that are less tutorial focused and instead focused on other aspects of building out SaaS.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>Temptations of an <a href="https://github.com/extesy/hoverzoom/discussions/670?ref=ethanmick.com">open-source browser extension</a> developer.</li><li><a href="https://jrhawley.ca/2023/08/07/blocked-by-cloudflare?ref=ethanmick.com">Blocked</a> by Cloudflare.</li><li><a href="https://www.alvar.dev/blog/creating-devtools-for-react-server-components?ref=ethanmick.com">Devtools</a> for React Server Components.</li></ul>]]></content:encoded></item><item><title><![CDATA[How to install AWS CLI with Homebrew]]></title><description><![CDATA[Here are the instructions for installing the AWS CLI with Homebrew on MacOS.]]></description><link>https://ethanmick.com/how-to-install-aws-cli-with-homebrew/</link><guid isPermaLink="false">64d191c6579f6e000179eafc</guid><category><![CDATA[aws]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Tue, 08 Aug 2023 01:16:44 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1629654291663-b91ad427698f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGJhc2h8ZW58MHx8fHwxNjkxNDU3Mzc5fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1629654291663-b91ad427698f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fGJhc2h8ZW58MHx8fHwxNjkxNDU3Mzc5fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="How to install AWS CLI with Homebrew"><p>The official AWS docs for installing the AWS CLI <a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html?ref=ethanmick.com">does not have instructions</a> for Homebrew, which is mind-boggling.</p><p>So, instead, here are the steps you can take to install the latest version of AWS CLI on your Mac with Homebrew.</p><pre><code class="language-bash">brew update
brew install awscli</code></pre><p>Once it&apos;s installed, you can verify it works with:</p><pre><code>aws --version
</code></pre><p>And the output should be something like the following:</p><pre><code>aws-cli/2.11.2 Python/3.11.2 Darwin/22.6.0 source/arm64 prompt/off</code></pre>]]></content:encoded></item><item><title><![CDATA[A Bit of SaaS Weekly: Prerevenue MicroSaaS]]></title><description><![CDATA[<p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Exploring some ideas this past week led me to Acquire.com, where I read over and over the phrase <em>Pre-revenue MicroSaaS</em>, which to me, translated to &quot;I built this</p>]]></description><link>https://ethanmick.com/a-bit-of-saas-weekly-prerevenue-microsaas/</link><guid isPermaLink="false">64cc0f57579f6e000179e768</guid><category><![CDATA[A Bit of SaaS]]></category><dc:creator><![CDATA[Ethan Mick]]></dc:creator><pubDate>Fri, 04 Aug 2023 10:00:54 GMT</pubDate><media:content url="https://ethanmick.com/content/images/2023/08/ethan_mick_A_very_small_and_cute_robot_macro_shot_zoomed_out_ci_fbd4decb-1545-4ce4-a561-246e707163aa.png" medium="image"/><content:encoded><![CDATA[<img src="https://ethanmick.com/content/images/2023/08/ethan_mick_A_very_small_and_cute_robot_macro_shot_zoomed_out_ci_fbd4decb-1545-4ce4-a561-246e707163aa.png" alt="A Bit of SaaS Weekly: Prerevenue MicroSaaS"><p>This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by <a href="https://ethanmick.com/">Ethan Mick</a>.</p><p>Exploring some ideas this past week led me to Acquire.com, where I read over and over the phrase <em>Pre-revenue MicroSaaS</em>, which to me, translated to &quot;I built this in a weekend and think I can sell it.</p><hr><h2 id="the-best-bits">The Best Bits</h2><ul><li>NextUI <a href="https://nextui.org/blog/nextui-v2?ref=ethanmick.com">v2 is out</a>, rewritten in Tailwind and React Aria!</li><li>Learn more about <a href="https://vercel.com/blog/understanding-react-server-components?ref=ethanmick.com">React Server Components</a>.</li><li><a href="https://lucia-auth.com/blog/lucia-2?ref=ethanmick.com">Lucia</a> 2, an authentication library.</li><li>Run Llama 2 <a href="https://ollama.ai/blog/run-llama2-uncensored-locally?ref=ethanmick.com">uncensored</a>, locally.</li></ul><hr><figure class="kg-card kg-image-card"><img src="https://media.tenor.com/uvLRWbLbnj0AAAAC/poor-help-me-im-poor.gif" class="kg-image" alt="A Bit of SaaS Weekly: Prerevenue MicroSaaS" loading="lazy" width="498" height="357"></figure><h2 id="please-buy-my-pre-revenue-microsaas">Please Buy my Pre-revenue MicroSaaS</h2><p>It&apos;s a dream of many people to build a product and sell it, reaping the rewards and fame of exiting a business. I&apos;ve had those thoughts a lot too.</p><p>There&apos;s just a catch.</p><p>You need to sell a <em>business</em>, not a weekend hackathon project. And selling a business requires actually having a business, not just some code.</p><p>And that&apos;s hard.</p><p>A business needs users, usage, and a brand. </p><p>The first aspect that prospective buyers will look at is the user base and usage of your app. These metrics are often considered more important than the technology itself, as they provide a snapshot of the business&apos;s existing reach and the extent to which its software is utilized.</p><p>Even a pre-revenue MicroSaaS business with a burgeoning user base should show promise because a large number of users typically indicates a significant market need or interest. Moreover, high user engagement and usage statistics signal the utility and popularity of the software.</p><p>Of course, if the user base shows promise... why are you selling it?</p><p>Most of the time, these apps are not growing and do not show promise. They often were launched on Product Hunt or Hacker News and got some initial interest, only for that to fade quickly. In the graph below, these apps are in the trough of sorrow.</p><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/08/image.png" class="kg-image" alt="A Bit of SaaS Weekly: Prerevenue MicroSaaS" loading="lazy" width="496" height="319"></figure><p>When you get into the promised land, you no longer have a hackathon project. You have a business. And a business requires a brand.</p><p>The brand of a SaaS business often plays a pivotal role in its potential success and saleability. A strong brand helps a business to stand out from the competition, promotes customer loyalty, and can serve as a significant draw for potential buyers.</p><p>Branding isn&apos;t just about having a catchy name or an attractive logo. It&apos;s about creating a unique identity that encompasses everything from the company&apos;s mission and values to its customer service. A strong brand story can influence how the market perceives the company and the value it provides.</p><p>These are things that will get someone to buy your business. But just having some code is the equivalent of having an idea. And ideas aren&apos;t worth anything.</p><p>So what business are you building?</p><hr><h2 id="learn-to-build-saas">Learn to Build SaaS</h2><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/-_bhH4MLq1Y?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="File Upload in Next.js 13 App Directory with NO libraries! Client and React Server Components!"></iframe></figure><p>This doesn&apos;t let you upload files to Vercel btw, just how to get the data without a third-party library.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/jEa7xJCqCxk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Build and Sell a SaaS Live | Session 1 | Learn to build SaaS End-to-End | Setup, About, Target, $$$$"></iframe></figure><p>This one is either going to be lots of fun or the death of me. Take a look; the idea is to build a SaaS app with the idea of selling it. That means focusing a bit on the <em>business</em> side of things, not just the tech.</p><p>And lastly, no preview for this one because it&apos;s scheduled for Friday morning: <a href="https://youtu.be/5_JT9H8j1Us?ref=ethanmick.com">https://youtu.be/5_JT9H8j1Us</a></p><p>I&apos;ve been on a roll! I came back from vacation, itching to get some content done. I&apos;m trying to do more content that is less perfect but still good. I&apos;ve left some &quot;ums&quot; in and cut down a lot on the editing. Let me know what you think!</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ethanmick.com/content/images/2023/07/image-4.png" class="kg-image" alt="A Bit of SaaS Weekly: Prerevenue MicroSaaS" loading="lazy" width="1024" height="1024" srcset="https://ethanmick.com/content/images/size/w600/2023/07/image-4.png 600w, https://ethanmick.com/content/images/size/w1000/2023/07/image-4.png 1000w, https://ethanmick.com/content/images/2023/07/image-4.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Midjourney</figcaption></figure><h2 id="tech-tip">Tech Tip</h2><p>Today during the live stream, I found out that Amazon has a V3 version of their JavaScript SDK: <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/?ref=ethanmick.com">https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/</a></p><p>If you don&apos;t use it, it shows a big nasty warning in your project, so they clearly want people to upgrade.</p><p>Their docs, as usual, are awful, but to get started with a service, you now do:</p><pre><code class="language-bash">npm install @aws-sdk/client-s3</code></pre><p>Instead of installing <code>aws-sdk</code>.</p><p>Happy coding!</p><hr><figure class="kg-card kg-image-card"><img src="https://ethanmick.com/content/images/2023/07/partner.jpg" class="kg-image" alt="A Bit of SaaS Weekly: Prerevenue MicroSaaS" loading="lazy" width="862" height="696" srcset="https://ethanmick.com/content/images/size/w600/2023/07/partner.jpg 600w, https://ethanmick.com/content/images/2023/07/partner.jpg 862w" sizes="(min-width: 720px) 720px"></figure><h2 id="cloud-chronicles">Cloud Chronicles</h2><ul><li>YouTube Subscribers: <strong>1,873</strong> <strong>(+82 in the last 7 days)</strong></li><li>Newsletter Members: <strong>451</strong> <strong>(+25 in the last 7 days)</strong></li></ul><p>Estimated revenue from YouTube ads? $6.54. Niiiiicccee.</p><p>Still, it&apos;s a step in the right direction. At this rate, it&apos;ll take me 4 months to get my first check for $100 (the lower limit of what they pay out). I guess I&apos;ll keep my day job.</p><hr><h2 id="last-byte">Last Byte</h2><ul><li>Optimize your <a href="https://techreactlearning.blogspot.com/2023/07/10-essential-react-performance.html?ref=ethanmick.com">React</a> code.</li><li>How to get ChatGPT to <a href="https://genai.stackexchange.com/questions/177/how-can-i-get-chatgpt-to-stop-apologizing?ref=ethanmick.com">stop apologizing</a>?</li><li>Make your mouse <a href="https://mousefix.org/?ref=ethanmick.com">work better</a> on Macs (some good suggestions <a href="https://news.ycombinator.com/item?id=36937593&amp;ref=ethanmick.com">in the comments too</a>).</li></ul>]]></content:encoded></item></channel></rss>