<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Minh-Phuc Tran</title>
    <description>The latest articles on DEV Community by Minh-Phuc Tran (@phuctm97).</description>
    <link>https://dev.to/phuctm97</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F371442%2F10622df2-e60f-47a6-9da4-80eed4582ac6.jpeg</url>
      <title>DEV Community: Minh-Phuc Tran</title>
      <link>https://dev.to/phuctm97</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phuctm97"/>
    <language>en</language>
    <item>
      <title>Make Copyable Code Block For Your Blog</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Sun, 31 Jan 2021 04:46:28 +0000</pubDate>
      <link>https://dev.to/phuctm97/make-copyable-code-block-for-your-blog-1hf5</link>
      <guid>https://dev.to/phuctm97/make-copyable-code-block-for-your-blog-1hf5</guid>
      <description>&lt;p&gt;A couple of days ago I implemented a feature to allow viewers on &lt;a href="https://phuctm97.com"&gt;my website&lt;/a&gt; to easily copy code blocks on my website. Personally, it is quite a useful feature&lt;br&gt;
for a technical blog.&lt;/p&gt;

&lt;p&gt;In this article, I'm going to share how you can implement the same for your sites - applicable to all React-based sites.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;useCopyableRef&lt;/code&gt; hook
&lt;/h2&gt;

&lt;p&gt;Thanks to React hook functionality, I've encapsulated the logic into an easily understandable and reusable hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;copyToClipboard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy-to-clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// You'll need this package: `yarn add copy-to-clipboard`.&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useCopyableRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="na"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(
  delay: number = 4 * SECONDS // You may want to change this to 4000, or define SECONDS somewhere in your application.
) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isCopied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCopied&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCopied&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ref is nil.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;copyToClipboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;setCopied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setCopied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isCopied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;

export default useCopyableRef;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's simple, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage in UI components
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useCopyableRef&lt;/code&gt; is similar to &lt;code&gt;useRef&lt;/code&gt;, additionally, it returns &lt;code&gt;isCopied&lt;/code&gt; and &lt;code&gt;copy&lt;/code&gt; props, which you'd need to implement your UI components.&lt;/p&gt;

&lt;p&gt;Implementing your UI components can be as simple as the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useCopyableRef&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/hooks/useCopyableRef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CodeBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTMLProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLPreElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isCopied&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCopyableRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLPreElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isCopied&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isCopied&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Copied!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Copy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it, don't forget to style your components however you want to!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>blog</category>
    </item>
    <item>
      <title>MDX (Unified) Mutating Options Object Cost Me 2 Hours</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Thu, 28 Jan 2021 14:28:59 +0000</pubDate>
      <link>https://dev.to/phuctm97/mdx-unified-mutating-options-object-cost-me-2-hours-3l5k</link>
      <guid>https://dev.to/phuctm97/mdx-unified-mutating-options-object-cost-me-2-hours-3l5k</guid>
      <description>&lt;p&gt;A couple of days ago, I got into a very annoying issue while using &lt;a href="https://mdxjs.com"&gt;MDX&lt;/a&gt; in &lt;a href="https://phuctm97.com"&gt;my Next.js website&lt;/a&gt;. It cost me almost 2 hours to resolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;First, let's quickly go through some technical concepts in case you didn't work with MDX and Next.js a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MDX is essentially a set of &lt;a href="https://unifiedjs.com"&gt;unified&lt;/a&gt; plugins. &lt;a href="https://unifiedjs.com"&gt;unified&lt;/a&gt; is a generic interface for processing content as structured data. Thanks to this, I was able to write granular plugins to customize how I use MDX quite extensively.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js is built on top of Webpack and loads MDX from a Webpack loader (&lt;code&gt;@mdx-js/loader&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I have different plugins and configurations for different MDX documents based on their file paths so that I can have custom syntaxes for different types of documents.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to achieve that I have a custom Next.js plugin that will resolve into different MDX options for different documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configureMDX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;realResource&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cheatsheet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cheatsheet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;md|mdx&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;appOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultLoaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mdx-js/loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;configureMDX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;configs.base&lt;/code&gt;, &lt;code&gt;configs.blog&lt;/code&gt;, and &lt;code&gt;configs.cheatsheet&lt;/code&gt; are just typical MDX options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// configs.base&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;remarkPlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;extractFrontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;unwrapTexts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;titleFromContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;descriptionFromContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pageURLElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;namedExports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;folder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rehypePlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prism&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a11yEmojis&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// configs.blog&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;remarkPlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;extractFrontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;uniqueItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;icons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="na"&gt;uniqueItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;unwrapTexts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;titleFromContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;descriptionFromContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pageURLElements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;generatedCover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;namedExports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;folder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;defaultExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/layouts/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rehypePlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prism&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a11yEmojis&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's quite natural, right? Indeed, it worked just fine with Next.js dev server. It only failed when building for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The issue
&lt;/h2&gt;

&lt;p&gt;Basically, I used the plugin &lt;code&gt;extractFrontmatter&lt;/code&gt; to both validate and expose&lt;br&gt;
attributes from frontmatter as props to my layout component. Only blog documents&lt;br&gt;
required &lt;code&gt;date&lt;/code&gt; attribute. Nonetheless, when I built for production, all documents required all attributes from different configurations combined! It was as if someone merged all the configurations together before executing the build process, despite the fact that the configuration code I wrote is completely side-effect free - all functions are pure and just return values without modifying anything.&lt;/p&gt;

&lt;p&gt;I started to look into &lt;code&gt;@mdx-js/loader&lt;/code&gt; code, then &lt;code&gt;@mdx-js/mdx&lt;/code&gt; code, and they all looked just fine.&lt;/p&gt;

&lt;p&gt;So, I had to debug further to see when the options got modified (I actually just&lt;br&gt;
did &lt;code&gt;console.log&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;All values returned from my &lt;code&gt;configureMDX&lt;/code&gt; are correct, so there was nothing wrong here. These values will then be sent to &lt;code&gt;@mdx-js/loader&lt;/code&gt; invocations and it was magically modified somehow right at the beginning of &lt;code&gt;@mdx-js/loader&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I really had no idea how it worked this time and just did tons of different guesses, made changes upon, and saw how it turned out 😥.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Thank god! After ~2 hours, I had (probably) a correct guess and managed to fix the issue.&lt;/p&gt;

&lt;p&gt;Webpack code didn't look like modifying anything (although the logs showed changes happened right at the beginning of a Webpack loader), MDX code didn't look like modifying anything either, so I guessed Unified did it. I jumped into &lt;a href="https://github.com/unifiedjs/unified/blob/8f135d052c9d9e5b2c4c2217815c672012e71707/index.js#L228-L240"&gt;unified repository&lt;/a&gt;, and... yeah, it mutated the plugin options 🥶.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;addPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// this equals Object.assign(...)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attachers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But really? All returned values from my &lt;code&gt;configureMDX&lt;/code&gt; are correct, when this mutation takes place? I'm still not sure, at this time, I really just want to fix the issue and get rid of it.&lt;/p&gt;

&lt;p&gt;So, to avoid the mutation, I simply changed my configuration code from objects to functions returning the object, this way all mutations will be discarded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configureMDX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;realResource&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;realResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cheatsheet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cheatsheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My guess was that Next.js or Webpack resolves configurations for every file before invoking loaders, this way all values returned by &lt;code&gt;configureMDX&lt;/code&gt; are correct before going into loaders, then right after the first loader execution, it got mutated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;This post isn't to blame anyone, I really enjoy using the Unified and MDX so far, and I appreciate the authors' works a lot. This post is just a rare story that I think other developer folks may find interesting. The lesson from this is to &lt;strong&gt;implement your code in a way that as side-effect free as possible&lt;/strong&gt;, because it makes the flow crystal clear and intuitive, side effects make debugging very hard! When you can't avoid side effects, make sure to document and highlight it!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mdx</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How-to Integrate Plausible Analytics With Next.js and Vercel</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Mon, 25 Jan 2021 13:07:28 +0000</pubDate>
      <link>https://dev.to/phuctm97/how-to-integrate-plausible-analytics-with-next-js-and-vercel-4a75</link>
      <guid>https://dev.to/phuctm97/how-to-integrate-plausible-analytics-with-next-js-and-vercel-4a75</guid>
      <description>&lt;p&gt;&lt;a href="https://plausible.io"&gt;Plausible&lt;/a&gt; is a privacy-focused analytics solution for modern websites. It and &lt;a href="https://usefathom.com"&gt;Fathom&lt;/a&gt; are probably 2 biggest names in the space. I chose Plausible because its pricing is more beginner-friendly and it is also open-source.&lt;/p&gt;

&lt;p&gt;This article is a quick snippnet that I wrote to properly enable Plausible for &lt;a href="https://phuctm97.com"&gt;my Next.js + Vercel website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plausible Script component
&lt;/h2&gt;

&lt;p&gt;To enable Plausible, you'll need place a &lt;code&gt;script&lt;/code&gt; tag in &lt;code&gt;head&lt;/code&gt; of your HTML document. To achieve that in Next.js, I create a component for easier maintainance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/components/plausbile-script.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/head&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PKG&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/package.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Load configuration from package.json.&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PlausibleScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"plausible-script"&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PKG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plausible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scriptURL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;async&lt;/span&gt;
      &lt;span class="na"&gt;defer&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PKG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;PlausibleScript&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then render it in my &lt;code&gt;pages/_app.(jsx|tsx)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/pages/_app.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PlausibleScript&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/components/plausible-script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AppProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Other head elements */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PlausibleScript&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Put configuration in &lt;code&gt;package.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You may realize that my &lt;code&gt;PlausibleScript&lt;/code&gt; doesn't directly define script &lt;code&gt;src&lt;/code&gt;, instead it references values from &lt;code&gt;package.json&lt;/code&gt;. Personally, &lt;code&gt;package.json&lt;/code&gt; is a great place to put configuration for a site or package &lt;em&gt;(the name said it, right?)&lt;/em&gt;, &lt;code&gt;package.json&lt;/code&gt; isn't just for npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;~/package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🏚 Home on the Web — Everything I learned and created: software dev, programming tutorials, career, startups, and open-source."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"homepage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://phuctm97.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Minh-Phuc Tran - Software Engineer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"plausible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scriptURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://plausible.phuctm97.com/js/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;values...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;great&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;way&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;separate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;implementation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;details&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configurations.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing this way I can also reuse standard npm values , in this case, it is &lt;code&gt;homepage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable only in production
&lt;/h2&gt;

&lt;p&gt;Finally, I don't want views on my local and preview environment to be counted, Plausible should only be enabled in production. To achieve that, I simply update my &lt;code&gt;_app.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/pages/_app.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PlausibleScript&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/components/plausible-script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IS_PRODUCTION&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/constants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AppProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Other head elements */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;IS_PRODUCTION&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PlausibleScript&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because I'm hosting my site on &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;, &lt;code&gt;IS_PRODUCTION&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ~/constants.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IS_PRODUCTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VERCEL_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;VERCEL_ENV&lt;/code&gt; is a system environment variable defined by Vercel while building and running your applications for specific environments (&lt;code&gt;preview&lt;/code&gt; and &lt;code&gt;production&lt;/code&gt;). To be able to reference it client-side in a Next.js application, you'll need to modify your &lt;code&gt;next.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Other configurations.&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;VERCEL_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VERCEL_ENV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it 🤟🏻!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>analytics</category>
      <category>vercel</category>
    </item>
    <item>
      <title>How-to Keep Your Packages Always Up-to-date with Dependabot</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Fri, 15 Jan 2021 12:27:27 +0000</pubDate>
      <link>https://dev.to/phuctm97/how-to-keep-your-packages-always-up-to-date-with-dependabot-1obb</link>
      <guid>https://dev.to/phuctm97/how-to-keep-your-packages-always-up-to-date-with-dependabot-1obb</guid>
      <description>&lt;p&gt;One of the ways that I do to keep myself up-to-date with the latest technologies is to configure automatic dependencies upgrade, which helps in 2 ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep my products/packages always up-to-date, especially avoid potential vulnerability as soon as possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remind me of the technologies I'm using that have introduced new features or bug fixes. I can quickly look at it and learn what is newly possible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I'll walk you through how I did it very easily and conveniently.&lt;/p&gt;

&lt;p&gt;Last Sunday, there were quite a few packages updated in my products.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tAShWBcv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4s9n7o969jm7z6ada4r2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tAShWBcv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4s9n7o969jm7z6ada4r2.png" alt="Screen Shot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependabot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/"&gt;Dependabot&lt;/a&gt; is a Github bot that automatically tracks and opens PRs to update dependencies for you. It used to be a separate product but was acquired by Github in 2020 and became available completely for free 🤯.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic configuration
&lt;/h3&gt;

&lt;p&gt;To configure it, you need just a couple of lines of code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a file &lt;code&gt;.github/dependabot.yml&lt;/code&gt; in your repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add some configuration to instruct Dependabot what to update:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;weekly&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-actions&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;weekly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependabot supports a lot of different package ecosystem including npm, Python, Go, Rust, Maven, Docker, etc, and also Github Actions recently. &lt;a href="https://dependabot.com/#languages"&gt;Check out more here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interval
&lt;/h3&gt;

&lt;p&gt;You can configure Dependabot to update daily, weekly, or at another specific interval. My experience with weekly updates is the best so far.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;After commiting your &lt;code&gt;dependabot.yml&lt;/code&gt; to your repository, Dependabot will help you update packages by opening PRs, which you can accept or reject. By receiving updates via PRs, you get a chance to make sure that all your tests/checks are passed before promoting to your live distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YLo24tMO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tzufrzpqwpjf5q05bdtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YLo24tMO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tzufrzpqwpjf5q05bdtl.png" alt="Screen Shot"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Dependabot is also intelligent enough to automatically rebase PRs when you pushed new changes to &lt;code&gt;master&lt;/code&gt; (or whatever branch you configured), so you don't always have to review and merge it right away. In fact, I often ignore it until late in the day, when my energy level is the lowest, then I review one or several of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Badge
&lt;/h2&gt;

&lt;p&gt;One little tip that I often do especially for an open-source package is to put a badge letting the users know that this package is configured to be automatically up-to-date.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;dependabot status&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/badge/dependabot-enabled-025e8c?logo=Dependabot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NfVZwaBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/badge/dependabot-enabled-025e8c%3Flogo%3DDependabot" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NfVZwaBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/badge/dependabot-enabled-025e8c%3Flogo%3DDependabot" alt="dependabot status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Dependabot has been super convenient for me, especially for open-source packages that I've done implementing and don't want to think about maintaining its dependencies. I hope it will benefit you, too!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>node</category>
      <category>devtool</category>
      <category>github</category>
    </item>
    <item>
      <title>Simple Script Cut 50% Size Of My React Website</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Tue, 12 Jan 2021 13:12:08 +0000</pubDate>
      <link>https://dev.to/phuctm97/simple-script-cut-50-size-of-my-react-website-dm3</link>
      <guid>https://dev.to/phuctm97/simple-script-cut-50-size-of-my-react-website-dm3</guid>
      <description>&lt;p&gt;Hi friends, it's &lt;a href="https://twitter.com/phuctm97" rel="noopener noreferrer"&gt;@phuctm97&lt;/a&gt; again. It has been 3 days since my last post, it's my first week at my new job so things have been a little hectic 🤓. Anyway, today article is a very simple but kinda mindblowing script that I did a couple&lt;br&gt;
of days ago in &lt;a href="https://phuctm97.com" rel="noopener noreferrer"&gt;my Next.js/React website&lt;/a&gt; that cut my site's bundle size from 90kb to a little less than 50kb.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7skdtej3amtzbimmuj3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7skdtej3amtzbimmuj3g.png" alt="Before"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F15ab8c079t3tevajg159.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F15ab8c079t3tevajg159.png" alt="After"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So here is how.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: my site is built on top of Next.js (and React).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Preact
&lt;/h2&gt;

&lt;p&gt;It's all possible thanks to &lt;a href="https://preactjs.com" rel="noopener noreferrer"&gt;Preact&lt;/a&gt;. Preact is a 3kb alternative implementation of React that provides 100% compatible APIs to React, what it means is that Preact and React are interchangeable but Preact is &lt;a href="https://bundlephobia.com/result?p=preact@10.5.9" rel="noopener noreferrer"&gt;4kb&lt;/a&gt; whereas React is &lt;a href="https://bundlephobia.com/result?p=react-dom@17.0.1" rel="noopener noreferrer"&gt;40kb&lt;/a&gt;. You may be skeptical about whether it's stable, see this &lt;a href="https://preactjs.com/about/we-are-using" rel="noopener noreferrer"&gt;list of companies&lt;/a&gt;, you'll be confident using it.&lt;/p&gt;

&lt;p&gt;So, it's kinda no-brainer to use Preact. However, do keep in mind that the team implementing Preact and React are separate and we aren't sure about what will happen in the future. Also, Preact devtools support is currently quite limited in comparison to React.&lt;/p&gt;

&lt;p&gt;So, I figure the best way to use it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dev with React and simply swap React and Preact in production bundle, no changes to my dependency tree - I got the best of both worlds, amazing devtools and ecosystem of React and smallest bundle size of Preact for my users 🔥.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Next.js plugin
&lt;/h2&gt;

&lt;p&gt;My website is powered by &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, however the same concept is appliable to any other React-based project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withPreact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Use Preact only in client production bundle.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;react&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create-react-class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact-compat/lib/create-react-class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom-factories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact-compat/lib/react-dom-factories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webpack&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withMDX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@next/mdx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withPreact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// Any other Next.js config. MDX is used as an example for compatibility here.&lt;/span&gt;
  &lt;span class="nf"&gt;withMDX&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;pageExtensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mdx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that I'm able to use other React-based technologies, too, (&lt;a href="https://mdxjs.com" rel="noopener noreferrer"&gt;MDX&lt;/a&gt; in this case).&lt;/p&gt;

&lt;p&gt;Also, you'll need to have Preact installed in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add preact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, that is it! I'm using it in &lt;a href="https://phuctm97.com" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, it's really a no-brainer using Preact this way. Don't worry, I'll let you know whenever my website is not functioning correctly 😉.&lt;/p&gt;

&lt;p&gt;Hope it is simple and useful enough to help you save half of your site tomorrow, too!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Write My First Tailwind CSS Plugin</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Sat, 09 Jan 2021 13:53:42 +0000</pubDate>
      <link>https://dev.to/phuctm97/write-my-first-tailwind-css-plugin-1j2h</link>
      <guid>https://dev.to/phuctm97/write-my-first-tailwind-css-plugin-1j2h</guid>
      <description>&lt;p&gt;Building &lt;a href="https://phuctm97.com"&gt;my website&lt;/a&gt; for a while using &lt;a href="https://tailwindcss.com"&gt;Tailwind CSS&lt;/a&gt;, I started to have more and more specific needs like &lt;code&gt;backdrop-filter&lt;/code&gt; to create blur overlay effects, &lt;code&gt;autofill&lt;/code&gt; pseudo-class to style form fields when they have been autocompleted, etc, and these use cases weren't covered by TailwindCSS core packages. So I had to leave my HTML and write separate CSS files to be able to use custom CSS properties. Although writing separate CSS is fine, I just don't like having ad&lt;br&gt;
hoc CSS files here and there, which feels like an anti-pattern using Tailwind CSS for me, especially when your theming system starts to appear everywhere. So, I asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why don't I just create a TailwindCSS plugin, I'll get to see how it works more specifically, too!".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, here I am.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR: It turned out pretty fun!&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  &lt;a href="https://github.com/phuctm97/tailwindcss-autofill"&gt;tailwindcss-autofill&lt;/a&gt; variant
&lt;/h2&gt;

&lt;p&gt;The first plugin adds a variant, which is to add styles when an element entered a special state like &lt;code&gt;hover&lt;/code&gt;, &lt;code&gt;focus&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The state I needed is when a form field was autocompleted.&lt;/p&gt;

&lt;p&gt;By default, when a form field has been autocompleted, its foreground and background will be changed to some browser defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RFXuIRbc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://phuctm97.com/static/blog/write-my-first-tailwindcss-plugin/autocomplete1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RFXuIRbc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://phuctm97.com/static/blog/write-my-first-tailwindcss-plugin/autocomplete1.gif" alt="autocomplete"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the defaults clearly don't work with my design. The perfect way to style it with TailwindCSS is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"autofill:bg-white autofill:text-gray-700"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, I wrote a variant to support it. It turned out to be quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tailwindcss/plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;autofill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;addVariant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;addVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autofill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;modifySelectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;separator&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;modifySelectors&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`autofill&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;`.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:-webkit-autofill`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:-webkit-autofill:hover`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:-webkit-autofill:focus`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;autofill&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I did is calling &lt;code&gt;addVariant&lt;/code&gt; in a &lt;code&gt;tailwindcss/plugin&lt;/code&gt; invocation with 2 essential things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A name or indicator: &lt;code&gt;autofill&lt;/code&gt;. This will enable the syntax &lt;code&gt;autofill:text-gray-100&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A function that will modify the suffixed class. For example, when I use &lt;code&gt;autofill:text-gray-100&lt;/code&gt;, this function will receive a &lt;code&gt;className&lt;/code&gt; = &lt;code&gt;text-gray-100&lt;/code&gt; and its job is to modify this class into something that will properly handle the targeted state, which is &lt;code&gt;-webkit-autofill&lt;/code&gt; in this case.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I released this as an NPM package, &lt;a href="https://github.com/phuctm97/tailwindcss-autofill"&gt;check out its repo to see more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, what I needed to do is to just add this plugin into &lt;code&gt;plugins&lt;/code&gt; in my &lt;strong&gt;tailwind.config.js&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tailwindcss-autofill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// Other plugins.&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Enable `autofill` variant for essential plugins.&lt;/span&gt;
      &lt;span class="na"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autofill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autofill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autofill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin works fine with &lt;code&gt;borderColor&lt;/code&gt;, &lt;code&gt;focusRing&lt;/code&gt;, and most others, unfortunately, &lt;code&gt;backgroundColor&lt;/code&gt; and &lt;code&gt;textColor&lt;/code&gt; won't work because the browsers use special properties that got prioritized (it's weird). So, I had to hack it further, and the solution is to use &lt;code&gt;-webkit-text-fill-color&lt;/code&gt; for the foreground and a special &lt;code&gt;box-shadow&lt;/code&gt; value to override the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/phuctm97/tailwindcss-text-fill"&gt;tailwindcss-text-fill&lt;/a&gt; and &lt;a href="https://github.com/phuctm97/tailwindcss-shadow-fill"&gt;tailwindcss-shadow-fill&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;-webkit-text-fill-color&lt;/code&gt; and background fill using &lt;code&gt;box-shadow&lt;/code&gt; are again special properties that weren't supported by TailwindCSS core packages. So, I wrote 2 other plugins, &lt;a href="https://github.com/phuctm97/tailwindcss-text-fill"&gt;tailwindcss-text-fill&lt;/a&gt; and &lt;a href="https://github.com/phuctm97/tailwindcss-shadow-fill"&gt;tailwindcss-shadow-fill&lt;/a&gt; respectively.&lt;/p&gt;

&lt;p&gt;This time, the two new plugins add utilities instead of variants. TailwindCSS utilities are classes to conveniently style elements like &lt;code&gt;text-gray-100&lt;/code&gt;, &lt;code&gt;bg-red-50&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Here is the code of one of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tailwindcss/plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flatten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flatten-tailwindcss-theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;addUtilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;utils&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`text-fill-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;addUtilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textFill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;textFill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textFill&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin will generate multiple &lt;code&gt;text-fill-{color}&lt;/code&gt; classes for you, like what &lt;code&gt;text-{color}&lt;/code&gt; or &lt;code&gt;bg-{color}&lt;/code&gt; did. The cool thing about this is it dynamically respects your theme and generate classes for only colors you are used to (colors in your current design system) and all Intellisense features on VS Code are automatically integrated (same for the &lt;a href="https://github.com/phuctm97/tailwindcss-autofill"&gt;tailwindcss-autofill&lt;/a&gt; plugin):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L4uN6lGP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://phuctm97.com/static/blog/write-my-first-tailwindcss-plugin/intelli.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L4uN6lGP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://phuctm97.com/static/blog/write-my-first-tailwindcss-plugin/intelli.gif" alt="Intellisense"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final result
&lt;/h2&gt;

&lt;p&gt;Finally, it worked 🔥!&lt;/p&gt;

&lt;p&gt;Here is how I styled my component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autofill:text-fill-gray-900 autofill:shadow-fill-white dark:autofill:shadow-fill-gray-800 dark:autofill:text-fill-gray-100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the final result in real life, &lt;a href="https://phuctm97.com"&gt;checkout my website&lt;/a&gt; and test the &lt;em&gt;Subscribe to the newsletter&lt;/em&gt; form input yourself to see the result now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I know what you're thinking now&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A couple of different CSS properties and pseudo-classes turned into 3 JS plugins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yeah, it is true. But the fact is these plugins didn't take a lot of time or LoCs either, especially now I've already known how to do it, it took literally the same energy compared to creating separate CSS, class names, and maintaining them along with other Tailwind-based configurations. On the other hand, I can reuse these plugins for different purposes in the future, too. And also remember&lt;br&gt;
that it is rare to write extra CSS when using Tailwind, the more plugins I (and others) added, the less likely I had to write extra CSS in future projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All the plugins are released and open-source&lt;/strong&gt;, check out if you want to see more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/phuctm97/tailwindcss-autofill"&gt;tailwindcss-autofill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/phuctm97/tailwindcss-text-fill"&gt;tailwindcss-text-fill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/phuctm97/tailwindcss-shadow-fill"&gt;tailwindcss-shadow-fill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/phuctm97/phuctm97.com"&gt;My website is open-source, too.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tailwindcss</category>
      <category>css</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Upgrade My Blog From Plain HTML to "Well-Designed" In 2 Weeks. I Suck.</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Thu, 07 Jan 2021 14:12:48 +0000</pubDate>
      <link>https://dev.to/phuctm97/upgrade-my-blog-from-plain-html-to-well-designed-in-2-weeks-i-suck-29gc</link>
      <guid>https://dev.to/phuctm97/upgrade-my-blog-from-plain-html-to-well-designed-in-2-weeks-i-suck-29gc</guid>
      <description>&lt;p&gt;About 3 weeks ago, I started &lt;a href="https://phuctm97.com/blog/hello-world-start-blog-in-html"&gt;my blog in plain HTML&lt;/a&gt;, then gradually tried to make it look better and more modern-ish. It has been 2 weeks so far and I've never liked how my website looked only until today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://phuctm97.com"&gt;Checkout my website&lt;/a&gt; if you want to see how it looks first.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 weeks was too long...
&lt;/h2&gt;

&lt;p&gt;2 weeks ago, if anyone said to me that it's going to take 2 weeks to make a blog look good, I'd definitely say back &lt;em&gt;"It's way too long man, it shouldn't take that long!"&lt;/em&gt;. And yeah, it took me 2 weeks and I've been saying that to myself every single day in the last 2 weeks. It didn't feel good at all, it felt like years.&lt;/p&gt;

&lt;p&gt;So what's wrong?&lt;/p&gt;

&lt;h2&gt;
  
  
  It always "looks" better in my imagination
&lt;/h2&gt;

&lt;p&gt;When I first talked to myself that I'm going to make my website look better, I &lt;strong&gt;could immediately have an imagination&lt;/strong&gt; inside my head about how it were going to look amazing and how easy it were to implement it - just put some CSS and that's it.&lt;/p&gt;

&lt;p&gt;The problem with my imagination was that things looked good because I believed they were gonna look good, not because every single detail in it actually looked good. There were no detail in my imagination, literally zero detail.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Try this yourself, remember the last time that you imagined you were going to make something beautiful, try to go into details of that "beautiful" thingy and see if you know what every single detail really looked like)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I started to sit down and write some code, I had to face the details that never existed in my mind. There was no surprise, I had no idea how each element should look, &lt;em&gt;especially when sitting next to each other&lt;/em&gt;. I ended up just trying lots of different combinations and they all just looked bad!&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for inspiration
&lt;/h2&gt;

&lt;p&gt;Being stuck and feeling like I'm the worst developer in the world, I decided to look for some inspiration and then maybe just copy-paste to save my life.&lt;/p&gt;

&lt;p&gt;Then, I realized that my imagination was really just the style of how I want my website to look. I ended up with the following list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.taniarascia.com"&gt;Tanica Rascia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leerob.io"&gt;Lee Robinson&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These websites roughly describe my imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap of inspiration
&lt;/h2&gt;

&lt;p&gt;Having an inspiration helped to sharpen the details of my imagination, like a button, a paragraph, a title, or a form.&lt;/p&gt;

&lt;p&gt;I thought that I were going to make it this time but I fell into another trap:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't want my website to look exactly the same as any of these websites, but it should look similar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, I put pieces from this and pieces from that into my website, and guess what, my website became a mess.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;F*ck! It was all because of these websites, I shouldn't use in the first place, I should have come up with my design system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(LoL, I'm laughing at myself writing this now, my thinking process was just so weird)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A way out
&lt;/h2&gt;

&lt;p&gt;You may have thought that I was just kidding about dropping the inspiration sites, but I actually did it. However, I didn't start any design system, it was just an overkill doing so, and you've probably realized that there's no way I could build a design system myself now.&lt;/p&gt;

&lt;p&gt;What I did was just to think about my website's design systematically. What it means is to start with the layout first, what sections my site should have, then&lt;br&gt;
what content each section should display, and why. Then for each section, I implement it separately, if a section is too big, I break it down into multiple elements again and repeat the same process. The key point of this method is to not allow you to overthink about the details, just imagine the site as a set of black boxes and your design job is to put these black boxes together, you'll go into each box later. Once you've done, you don't go back.&lt;/p&gt;

&lt;p&gt;Alright, that's it, there are more specific knowledge into building the actual site, but it was late now and I wanted to focus on the design part in this article. Hope you enjoy the story and learn something from it!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Auto Shrink Header On Scroll in React</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Mon, 04 Jan 2021 13:54:07 +0000</pubDate>
      <link>https://dev.to/phuctm97/auto-shrink-header-on-scroll-in-react-12a3</link>
      <guid>https://dev.to/phuctm97/auto-shrink-header-on-scroll-in-react-12a3</guid>
      <description>&lt;p&gt;I've always liked this effect: keep the header of your website sticky then auto-shrink and blur when users scroll down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fphuctm97.com%2Fimages%2Fb%2Fauto-shrink.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fphuctm97.com%2Fimages%2Fb%2Fauto-shrink.gif" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, I finally got some free time to implement it for &lt;a href="https://phuctm97.com/" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, so I'm writing this article hopefully to help you do the same for yours if you also like it 😉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivations
&lt;/h2&gt;

&lt;p&gt;There're 2 primary motivations that makes me love this effect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can put a couple of calls to action (CTAs), then as your users are reading content on your website, they keep seeing these CTAs, which can increase the odds that they'll click them. I like to keep links to &lt;a href="https://phuctm97.com/" rel="noopener noreferrer"&gt;my newsletter&lt;/a&gt; and &lt;a href="https://twitter.com/phuctm97" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; here. &lt;em&gt;(An extra tip that I like to do here is to make a little animation or transition that occasionally runs to remind the user's the CTAs)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I want to optimize for CTAs but I don't want users on my website to have bad experience, which is actually even more important. To avoid bad UX, the header shouldn't take too much space, especially when users are reading the main content.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How-to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic idea
&lt;/h3&gt;

&lt;p&gt;The basic idea is to subscribe to &lt;code&gt;onscroll&lt;/code&gt; event of the browser, then check if the user scrolls pass a certain offset and update CSS of the header component arcordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscribe to &lt;code&gt;onscroll&lt;/code&gt; using hook
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Check and update component here.&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// return &amp;lt;Component ... /&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;useEffect&lt;/code&gt; hook to subscribe to event &lt;code&gt;onscroll&lt;/code&gt; when the component is mounted (notice the last parameter &lt;code&gt;[]&lt;/code&gt;), also remember to return an unsubscribe function when the component is unmounted to avoid memory leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check for scroll position
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isShrunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShrunk&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setShrunk&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;isShrunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isShrunk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;isShrunk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isShrunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Previous logic.&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// return &amp;lt;Component isShrunk={isShrunk} /&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice &lt;code&gt;setShrunk&lt;/code&gt; is called with a function instead of just pure value, this pattern helps ensure we are checking against the lastest previous value. Also, there are a &lt;em&gt;gap&lt;/em&gt; between offsets to shrink and to expand (&lt;code&gt;20&lt;/code&gt; and &lt;code&gt;4&lt;/code&gt;), this helps avoid flashing of changed styles.&lt;/p&gt;

&lt;p&gt;That's it. I hope it helps. &lt;a href="https://github.com/phuctm97/phuctm97.com/blob/f10f3a558ae34528c8b9363a79e69a31c2e2b41c/layouts/header.tsx" rel="noopener noreferrer"&gt;Check out the full source code&lt;/a&gt; to see more details.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Misunderstanding @tailwindcss/typography costs me 2 hours</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Sat, 02 Jan 2021 12:41:50 +0000</pubDate>
      <link>https://dev.to/phuctm97/misunderstanding-tailwindcss-typography-costs-me-2-hours-mj7</link>
      <guid>https://dev.to/phuctm97/misunderstanding-tailwindcss-typography-costs-me-2-hours-mj7</guid>
      <description>&lt;p&gt;After &lt;a href="https://phuctm97.com/blog/i-change-my-mind-abt-tailwind-css"&gt;falling in love with Tailwind CSS&lt;/a&gt;, I incorporated it into &lt;a href="https://phuctm97.com/"&gt;my website&lt;/a&gt;. However, there's one thing that is both nice and annoying about Tailwind CSS is that it has a very opinionated CSS reset, which clears pretty much all default styles. I understand Tailwind creators' rationale for it, having everything reset to "no style" means all styles are consciously implemented by you, there's no surprise. So, I said "Okay, I guess I have to implement my own 'default style' then.".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why having default style?
&lt;/h2&gt;

&lt;p&gt;There's a reason why browsers had default styles:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine going back to the time when there was no CSS, only HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://phuctm97.com/blog/hello-world-start-blog-in-html"&gt;HTML was first created to write and share stories&lt;/a&gt;, that's why we had &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;h2&lt;/code&gt;, &lt;code&gt;h3&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;strong&lt;/code&gt;, &lt;code&gt;em&lt;/code&gt;, etc, and because of their meanings (heading, paragraphs, strong, emphasized, etc), there should visual way to express these meanings to readers (who don't know HTML tags).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This style of writing is still mainstream on the web, most contents on the web are still articles with headings, paragraphs, images, etc. Markdown was also created to make this type of writing easier, every Markdown token is basically a mapping to an HTML tag.&lt;/p&gt;

&lt;p&gt;So, for a blog, these default styles are a must.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the implementation look like?
&lt;/h2&gt;

&lt;p&gt;Implementing default styles isn't difficult, however it takes time to make things look good, especially for edge cases when these elements are stacked or nested together. Here is my very simple one that took me ~2 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;container&lt;/span&gt; &lt;span class="err"&gt;max-w-2xl&lt;/span&gt; &lt;span class="err"&gt;mx-auto&lt;/span&gt; &lt;span class="err"&gt;my-10&lt;/span&gt; &lt;span class="err"&gt;px-4&lt;/span&gt; &lt;span class="py"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;px-0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;h6&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;font-medium&lt;/span&gt; &lt;span class="err"&gt;pt-5&lt;/span&gt; &lt;span class="err"&gt;mb-2;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-5xl;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-4xl;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-3xl;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-2xl;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-xl;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;h6&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-lg;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;font-normal&lt;/span&gt; &lt;span class="err"&gt;text-base&lt;/span&gt; &lt;span class="err"&gt;mb-4;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;blockquote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-gray-100&lt;/span&gt; &lt;span class="err"&gt;my-6&lt;/span&gt; &lt;span class="err"&gt;pt-4&lt;/span&gt; &lt;span class="err"&gt;pb-px&lt;/span&gt; &lt;span class="err"&gt;px-3&lt;/span&gt; &lt;span class="err"&gt;border-l-8&lt;/span&gt; &lt;span class="err"&gt;border-gray-300;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;mb-4&lt;/span&gt; &lt;span class="err"&gt;pl-8;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;list-decimal;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;list-disc;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;mb-0;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;ol&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;list-style-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;mb-4&lt;/span&gt; &lt;span class="err"&gt;p-5&lt;/span&gt; &lt;span class="err"&gt;overflow-auto;&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f2f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* for consistency with Prismjs theme */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-gray-100&lt;/span&gt; &lt;span class="err"&gt;text-sm&lt;/span&gt; &lt;span class="err"&gt;font-bold&lt;/span&gt; &lt;span class="err"&gt;p-1&lt;/span&gt; &lt;span class="err"&gt;rounded-sm;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="nd"&gt;:before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;font-normal&lt;/span&gt; &lt;span class="err"&gt;text-xs;&lt;/span&gt;
    &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"`"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="nt"&gt;code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-transparent&lt;/span&gt; &lt;span class="err"&gt;font-normal&lt;/span&gt; &lt;span class="err"&gt;p-0;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;my-4;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-blue-500;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tailwind's team implemented these styles 🤦🏻‍♂️. Surprise?
&lt;/h2&gt;

&lt;p&gt;After having implemented the above styles, I was okay but wasn't really happy about how it looked, so I went around to see what others did for some inspiration.&lt;/p&gt;

&lt;p&gt;And, I found people suggesting the official plugin &lt;code&gt;@tailwindcss/typography&lt;/code&gt;. I'd already known that Tailwind CSS supported plugins and also knew it was the only official plugin. However, because of the name, I thought it was something for us to customize fonts, which wasn't my problem. But guess what, &lt;code&gt;@tailwindcss/typography&lt;/code&gt; was built exactly for the above reason - to provide "good" default styles for typical writing HTML tags 🤦🏻‍♂️. All you need to do is install &lt;code&gt;@tailwindcss/typography&lt;/code&gt;, put that into the &lt;code&gt;tailwindcss.config.js#plugins&lt;/code&gt;, and add class &lt;code&gt;prose&lt;/code&gt; to your document top-level tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"prose"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Heading 1&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Lorem ipsum&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... All tags inside will be styled properly--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin supports a good amount of common scenarios: nested lists, lists with paragraphs, code blocks, and even tables (which I didn't have time to implement). It also allows developers to extend and customize default styles quite easily.&lt;/p&gt;

&lt;p&gt;So, comparing &lt;code&gt;@tailwindcss/typography&lt;/code&gt; with my implementation was a no-brainer. And I guess I've wasted 2 hours of my time because of the name. Anyway, I learned a hard lesson that I'll remember for the rest of my life:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Typography is not just about font styles. Typography is about making written content legible, readable and appealing when displayed.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>tailwindcss</category>
      <category>css</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A Quick Exploration Into Deno</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Thu, 31 Dec 2020 13:21:53 +0000</pubDate>
      <link>https://dev.to/phuctm97/a-quick-exploration-into-deno-3jaa</link>
      <guid>https://dev.to/phuctm97/a-quick-exploration-into-deno-3jaa</guid>
      <description>&lt;p&gt;Deno v1 was released a couple of months ago and there were a lot of different opinions about it. Personally, I had a chance to quickly look at its documentation and did &lt;em&gt;kinda&lt;/em&gt; like it, I also trust &lt;em&gt;Ryan Dahl&lt;/em&gt; - the creator behind Deno (and Node.js).&lt;/p&gt;

&lt;p&gt;So, here a deeper look into Deno and what it can potentially do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deno's goals
&lt;/h2&gt;

&lt;p&gt;First, we should understand why Deno was created in the first place, what problems it is solving:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deno aims to be a productive and secure scripting environment for the modern programmer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Deno's documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This definitely sounds very general. I had to look further into its documentation to understand what problems Deno is solving.&lt;/p&gt;

&lt;p&gt;First of all, although Deno and Node.js can co-exist, being &lt;strong&gt;another&lt;/strong&gt; TypeScript/JavaScript runtime, Deno's ultimate end goal is definitely to replace Node.js, which means it was built to solve Node.js's problems. By describing Deno "a productive and secure scripting environment" Deno creator is saying that Deno is solving the problem of &lt;strong&gt;Node.js being not productive and insecure&lt;/strong&gt;. As I worked with Node.js and a few other modern languages (Go, Kotlin) before, I definitely can relate that Node.js has these two issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Not productive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Did you ever switch from TypeScript back to JavaScript then realize that was a wrong choice? The number of LOCs in JavaScript codebase although can be fewer than the same project in TypeScript and no restriction seems to be easier, writing and collaborating in JavaScript is just much slower nowadays.&lt;/li&gt;
&lt;li&gt;  The Node.js devtool ecosystem though is powerful, is very fragmented: npm, yarn, CDN, linter, formatter, TypeScript, Flow, etc. You see all the "starter" projects with tons of configuration files?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insecure: I don't know if you have this feeling but every time I installed a new Node.js package, I wish the developer didn't push a virus script into it. The &lt;code&gt;node_modules&lt;/code&gt; and installation process always feel cumbersome and insecure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Deno solve these problems?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;TypeScript out of the box, no configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ship only an executable file, no &lt;code&gt;node_modules&lt;/code&gt;, and run everywhere without an installation process (except for the executable itself).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure by default, you have to explicitly grant certain permissions before a script can do certain critical tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have builtin devtools (&lt;code&gt;deno fmt&lt;/code&gt;, &lt;code&gt;deno bundle&lt;/code&gt;, &lt;code&gt;deno lint&lt;/code&gt;, etc) and is a package manager itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ES Modules native. Imports via URLs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be browser-compatible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What does that mean?
&lt;/h3&gt;

&lt;p&gt;Looking at Deno's features set, I had to say its vision is more ambitious than I thought. All with a single executable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write secure-by-default system scripts and server-side applications in TypeScript with zero configuration. TypeScript compilation is also abstracted away, for scripting experience, it is like running TypeScript natively (what &lt;a href="https://www.npmjs.com/package/ts-node"&gt;ts-node&lt;/a&gt; does).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have builtin &lt;em&gt;fast&lt;/em&gt; (&lt;a href="https://swc.rs"&gt;swc&lt;/a&gt;) compiler and bundler for modern ES syntax supported by modern browsers, essentially replacing &lt;code&gt;webpack&lt;/code&gt;, &lt;code&gt;parcel&lt;/code&gt;, &lt;code&gt;rollup&lt;/code&gt;, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Have builtin &lt;code&gt;fmt&lt;/code&gt; and &lt;code&gt;lint&lt;/code&gt; tools, essentially replacing &lt;code&gt;prettier&lt;/code&gt;, &lt;code&gt;eslint&lt;/code&gt;, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write, compile, and deploy code for both servers and modern browsers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common misconception
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Import by URLs??
&lt;/h4&gt;

&lt;p&gt;A lot of people are skeptical about this and are afraid of unexpected changes upstream. However, this concept was originally designed in ES standard and is implemented in most modern browsers, Deno doesn't reinvent the wheel here.&lt;/p&gt;

&lt;p&gt;First of all, there shouldn't be concern about changes upstream, &lt;strong&gt;production software should always vendor 3rd packages&lt;/strong&gt;, we have always been doing that by bundling applications. Lock versions can also be done easily by keeping checksums of previously downloaded packages.&lt;/p&gt;

&lt;p&gt;This pattern also helps to have truly on-demand imports, you only load a package when your execution reach its import, while in Node.js everything is fetched no matter when and whether you'll use it.&lt;/p&gt;

&lt;p&gt;Web protocol also enables more advanced import features (by communicating metadata in HTTP headers, etc), allows to opt in interceptors doing complex tasks, for example &lt;a href="https://github.com/vitejs/vite#how-and-why"&gt;on-demand compilation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Top-level await
&lt;/h4&gt;

&lt;p&gt;This isn't just about allowing us to do "cool" &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; at top-level in our &lt;code&gt;index.ts&lt;/code&gt; or &lt;code&gt;app.ts&lt;/code&gt;. This is built in combination with native ESM to further enable async on-demand imports and secure-by-default features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A package is only loaded (downloaded) when a user did a certain action (went to a screen, used a feature), we can display a loading while importing the package.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A permission may be asked and granted only until an import is loaded.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I love it! I can picture a bright future with Deno, where you can write, compile, bundle, and deploy performance TypeScript applications to both server and client, with just a single Deno executable and little-to-zero configuration.&lt;/p&gt;

</description>
      <category>deno</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Imagegen as a Service (Free), All Bloggers Should Have One</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Wed, 30 Dec 2020 12:28:59 +0000</pubDate>
      <link>https://dev.to/phuctm97/imagegen-as-a-service-free-all-bloggers-should-have-one-1fad</link>
      <guid>https://dev.to/phuctm97/imagegen-as-a-service-free-all-bloggers-should-have-one-1fad</guid>
      <description>&lt;p&gt;Hi folks, I've just built an &lt;em&gt;Imagegen (Image Generator) as a Service&lt;/em&gt;. I originally built it as a feature of &lt;a href="https://phuctm97.com/"&gt;my website&lt;/a&gt;, then DigitalOcean hackathon was announced, so here it is an introduction, a submission, as well as a quick tutorial how I built it. One cool thing about this service is that it is &lt;strong&gt;open-sourced&lt;/strong&gt;, &lt;strong&gt;easily to customize&lt;/strong&gt; for your needs, and can be deployed &lt;strong&gt;completely free&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;🌌 &lt;strong&gt;Imagegen (Image Generator) as a Service&lt;/strong&gt;, which is a (REST) API service that can generate dynamic images for different purposes, is especially useful to generate cover images for content distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Blogging &amp;amp; writing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Videos' thumbnails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open-source repositories' social images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Markdown support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dark mode enabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easily display popular brands' icons by names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Twitter emojis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requires zero action if you publish posts to DEV.to using &lt;a href="https://docs.dev.to/api"&gt;DEV.to API&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easily share consistent styles between your content images, embed your name/trademark to all your content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update all images with a single code change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cached requests to improve performance and reduce loads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple API versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are 2 deployment models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://github.com/phuctm97/img-nodejs"&gt;Node.js + Docker&lt;/a&gt;, which can be deployed to every cloud platform that supports Docker deployment, e.g. DigitalOcean. The project is pre-configured to deploy to DigitalOcean App Platform with a single click, too.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/phuctm97/img"&gt;Next.js + Serverless functions&lt;/a&gt;, which can be deployed serverlessly to platforms that support Next.js API routes as Serverless functions (only Vercel currently supports).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Category Submission
&lt;/h3&gt;

&lt;p&gt;Well, frankly, it's &lt;em&gt;Random Roulette&lt;/em&gt;. Nevertheless, I believe it can be very useful for personal site/portfolio (I use it on my website) and business blogs.&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://img-nodejs-dwkvo.ondigitalocean.app"&gt;img-nodejs-dwkvo.ondigitalocean.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's an API service, so there's no UI, the API is served at &lt;code&gt;/api/v1&lt;/code&gt; and &lt;code&gt;/api/v2&lt;/code&gt;, the URL above will redirect you to &lt;a href="https://github.com/phuctm97/img-nodejs"&gt;the Github repository&lt;/a&gt; with details documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;Following are some examples of using this service's API, you can copy and paste each URL on your browser to see it yourself.&lt;/p&gt;

&lt;h4&gt;
  
  
  With title and brands' logos
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://img-nodejs-dwkvo.ondigitalocean.app/api/v2/%F0%9F%97%BE%20**Imagegen**%20as%20a%20Service?&amp;amp;icons=Node.js&amp;amp;icons=Docker&amp;amp;icons=DigitalOcean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ndhMvx0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kgm5cuzhlw8ut82dmuan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ndhMvx0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kgm5cuzhlw8ut82dmuan.png" alt="🗾 Imagegen as a Service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  With Markdown and brands' logos
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://img-nodejs-dwkvo.ondigitalocean.app/api/v2/%F0%9F%91%8B%F0%9F%8F%BB**Hi**%20This%20is%20a%20_generated%20image_%20with%20**Markdown**%20support?icons=dev.to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PLmNLNDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b26irx8y5qtox297txpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PLmNLNDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b26irx8y5qtox297txpz.png" alt="👋🏻Hi! This is a generated image with Markdown support"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  With Dark mode
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://img-nodejs-dwkvo.ondigitalocean.app/api/v2/%F0%9F%8C%99%20**Dark%20mode**%20is%20supported,%20too!?theme=dark&amp;amp;icons=dev.to&amp;amp;colors=invert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eJ9mHgAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hrvux7rjt0udbp21wgva.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eJ9mHgAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hrvux7rjt0udbp21wgva.png" alt="🌙 Dark mode is supported, too!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/phuctm97/img-nodejs"&gt;Node.js + DigitalOcean repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/phuctm97/img"&gt;Next.js + Serverless repository&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;The project is released under the &lt;em&gt;MIT license&lt;/em&gt; and is completely free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I'd actually still build this project and release it open-source even if DigitalOcean didn't organize the hackathon because it was a part of &lt;a href="https://phuctm97.com/"&gt;my website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My website is where I'll blog very often (currently one post per ~2 days) and document everything I did and learned as a software engineer. One thing I want to achieve building my website is to automatically distribute my content as much as possible, in order to maximize distribution in long term and at scale. It currently supported auto distribution to DEV.to and Hashnode (you're probably reading a distributed copy of &lt;a href="https://phuctm97.com/blog/imagegen-as-a-service-introduction"&gt;the original post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;One part of this automation is to generate cover images for posts and let people know where to find me. ➡️ This service 🤓&lt;/p&gt;

&lt;p&gt;I believe automating all that is the a scalable way to grow an audience in the long term, while I can focus my energy on high leverage tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;This article is already fairly long (compared to typical dev posts), so I'll write a quick concept here and write complete tutorials in separate posts for both Node.js based and Next.js based versions. There are also more details in &lt;a href="https://github.com/phuctm97/img-nodejs"&gt;the project's repository&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Concept
&lt;/h4&gt;

&lt;p&gt;The purpose of this service is to &lt;strong&gt;dynamically turn structured data into images&lt;/strong&gt;. An easy way to think about it is that it is like a React component that receives &lt;code&gt;props&lt;/code&gt; then renders &lt;code&gt;visualization&lt;/code&gt;, which is an &lt;code&gt;image&lt;/code&gt; in this case.&lt;/p&gt;

&lt;p&gt;Each API is a HTTP &lt;code&gt;GET&lt;/code&gt; at &lt;code&gt;/api/{version}/{...props}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;version&lt;/code&gt; represents a component that requires a certain type of &lt;code&gt;props&lt;/code&gt; and defines how an image should look.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;props&lt;/code&gt; are constructed using HTTP path and query params.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, &lt;code&gt;/api/v2/Hello, World!?theme=dark&lt;/code&gt; will render an image in Dark mode with title &lt;code&gt;Hello, World!&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  API spec
&lt;/h4&gt;

&lt;p&gt;See &lt;a href="https://github.com/phuctm97/img-nodejs#api"&gt;img-nodejs#API&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implementation
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Generate images
&lt;/h5&gt;

&lt;p&gt;To be able to render dynamic images, I used &lt;a href="https://github.com/puppeteer/puppeteer"&gt;Puppeteer&lt;/a&gt; to render HTML in a headless Chrome, then take screenshots and render them back to clients.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parseRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isHTMLDebug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;screenshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getScreenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;renderScreenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Configure Docker
&lt;/h5&gt;

&lt;p&gt;There're one tricky part with deployment of this service, especially to DigitalOcean. When developing on a laptop, it's easy because Puppeteer will simply use your installed Chrome instance with admin access and resources attached to it. When deploying to a cloud platform like DigitalOcean, you'll need to install and configure a Chrome instance yourself. So, to make it easier for others, I made a Docker image to prepare everything for you 🥳. Security was also taken care of.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.12&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies to run Puppeteer in Alpine.&lt;/span&gt;
&lt;span class="c"&gt;# See https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-on-alpine.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        chromium &lt;span class="se"&gt;\
&lt;/span&gt;        nss &lt;span class="se"&gt;\
&lt;/span&gt;        freetype &lt;span class="se"&gt;\
&lt;/span&gt;        freetype-dev &lt;span class="se"&gt;\
&lt;/span&gt;        harfbuzz &lt;span class="se"&gt;\
&lt;/span&gt;        ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;        ttf-freefont &lt;span class="se"&gt;\
&lt;/span&gt;        nodejs &lt;span class="se"&gt;\
&lt;/span&gt;        yarn

&lt;span class="c"&gt;# Setup app's dependencies and configure envs.&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CONTAINERIZED_BROWSER=/usr/bin/chromium-browser&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . /app/&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn

&lt;span class="c"&gt;# Run everything as non-root user for security.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; appuser appuser &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/appuser/Downloads /app/ &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appuser /home/appuser/ &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appuser /app/
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["yarn", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Deploy to DigitalOcean
&lt;/h5&gt;

&lt;p&gt;To make deployment even easier, I also created a &lt;code&gt;Deploy to DO&lt;/code&gt; button, which enable you to deploy the service to your account with a single click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.digitalocean.com/apps/new?repo=https://github.com/phuctm97/img-nodejs/tree/master"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CNMgVEM---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mp-assets1.sfo2.digitaloceanspaces.com/deploy-to-do/do-btn-blue.svg" alt="Deploy to DO"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One cool thing about deploying to DigitalOcean App Platform is that it will take care of setting up proxies, port binding, and SSL (HTTPS) certificates for you. It will also redeploy the service when you push changes to &lt;code&gt;master&lt;/code&gt; branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;Special thanks to Vercel's &lt;a href="https://github.com/vercel/og-image."&gt;og-image&lt;/a&gt;, this project is heavily inspired by &lt;a href="https://github.com/vercel/og-image."&gt;og-image&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alright, I hope you like it and deploy an instance successfully for you, feel free to &lt;a href="https://twitter.com/phuctm97"&gt;DM me on Twitter&lt;/a&gt; if you have any questions. Also, if you're interested in my journey building my blog, open-source, and SaaS products, please consider subscribing to &lt;a href="https://buttondown.email/phuctm97"&gt;my newsletter&lt;/a&gt; or &lt;a href="https://twitter.com/phuctm97"&gt;following me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>dohackathon</category>
      <category>saas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Changed My Mind After 2nd Try Tailwind CSS</title>
      <dc:creator>Minh-Phuc Tran</dc:creator>
      <pubDate>Mon, 28 Dec 2020 14:30:48 +0000</pubDate>
      <link>https://dev.to/phuctm97/i-changed-my-mind-after-2nd-try-tailwind-css-4oj4</link>
      <guid>https://dev.to/phuctm97/i-changed-my-mind-after-2nd-try-tailwind-css-4oj4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: Tailwind won't help you write CSS faster or easier, it helps you think less.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because I wanted to give &lt;a href="https://phuctm97.com/blog/hello-world-start-blog-in-html"&gt;my plain HTML website&lt;/a&gt; a little style, I gave Tailwind CSS a shot yesterday. I spent about an hour to read through its documentation and try it on &lt;a href="https://play.tailwindcss.com"&gt;its playground&lt;/a&gt;. The first impression was &lt;em&gt;"meh"&lt;/em&gt;. Tailwind is essentially a set of CSS classes mapping to a few CSS properties, so, in order to use Tailwind, you basically have to kinda learn CSS again, which I believe very few developers enjoy. Tailwind's creators actually knew that and put a very interesting note:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now I know what you're thinking, "this is an atrocity, what a horrible mess!" and you're right, it's kind of ugly. In fact it's just about impossible to think this is a good idea the first time you see it — you have to actually try it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://tailwindcss.com/docs/utility-first"&gt;Tailwind's documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I gave it a try, but less than an hour was probably not enough for me to shift my mind from typical CSS properties to Tailwind classes. So, I didn't like it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use typcial CSS
&lt;/h2&gt;

&lt;p&gt;Because I didn't like Tailwind, today I decided to style my website using Sass while &lt;strong&gt;keeping an eye on what better and worse compared to yesterday experience&lt;/strong&gt;. And, it took me almost 3 hours to finish simple styles and the experience wasn't fun either.&lt;/p&gt;

&lt;h5&gt;
  
  
  So, what's wrong? 😠
&lt;/h5&gt;

&lt;p&gt;I spent &lt;strong&gt;more than half of the time to think of which CSS properties should be grouped, which selectors I should do, what the class names should be&lt;/strong&gt;. Although I consciously tried to avoid that, it still consumed a lot of my energy. I couldn't really explain why, the biggest reason is probably because we always have so many options with CSS and they keep growing over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch back to Tailwind again
&lt;/h2&gt;

&lt;p&gt;So, I switched back to Tailwind again, not that I was convinced that Tailwind will work better, but to see again if Tailwind could solve the problems I mentioned above.&lt;/p&gt;

&lt;p&gt;And... yeah... it actually solved the problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tailwind comes with a very complete set of utilities, I've never had to think of grouping styles together. Almost every element can be styled with just Tailwind classes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I styled directly at element-level, in HTML, so I've rarely had to think of CSS selectors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Because I've never had to write CSS utilities myself, there wasn't any CSS "codebase" growing. Most importantly, I had nothing about CSS to &lt;strong&gt;keep in my mind&lt;/strong&gt;. Now I knew it! When you write CSS, I believe "remembering its existence" consumes a lot of energy!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I had to say it was oddly interesting. Tailwind appears to be a &lt;em&gt;"CSS framework"&lt;/em&gt;, so my expectation was &lt;em&gt;"I'll be able to write CSS faster and easier"&lt;/em&gt;, but when looking at its documentation, it doesn't look faster and easier at all. It turned out, the problem Tailwind solved is somewhat a physiological problem when working with CSS.&lt;/p&gt;

&lt;p&gt;So, give it try if you've ever skeptical!&lt;/p&gt;

</description>
      <category>css</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
