<?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: Nitzan Kletter</title>
    <description>The latest articles on DEV Community by Nitzan Kletter (@n1tzan).</description>
    <link>https://dev.to/n1tzan</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%2F1266565%2F12a1196c-404b-466f-bd76-434f016e2a57.png</url>
      <title>DEV Community: Nitzan Kletter</title>
      <link>https://dev.to/n1tzan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/n1tzan"/>
    <language>en</language>
    <item>
      <title>From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey</title>
      <dc:creator>Nitzan Kletter</dc:creator>
      <pubDate>Fri, 06 Sep 2024 14:33:23 +0000</pubDate>
      <link>https://dev.to/n1tzan/from-zero-to-launch-key-takeaways-from-our-remix-powered-development-journey-3ph7</link>
      <guid>https://dev.to/n1tzan/from-zero-to-launch-key-takeaways-from-our-remix-powered-development-journey-3ph7</guid>
      <description>&lt;p&gt;Around six months ago, I made what some would say is a bold decision by &lt;a href="https://dev.to/n1tzan/choosing-a-fe-framework-in-2024-a-practical-tale-19ej"&gt;choosing Remix as the foundation for our company's web application&lt;/a&gt;. Fast forward to today, and I think it's time to take a step back and reflect on the choices we made. I'll go over the main infrastructure decisions made and sprinkle a bit of practical usage examples on the way.&lt;/p&gt;

&lt;p&gt;So, without further ado, let’s jump straight into the highlights and lowlights of this journey — a mix of satisfaction and lessons learned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65mzoa2nxlfdjp11jhcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65mzoa2nxlfdjp11jhcy.png" alt="Image description" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;
Remix (or should I say React Router?)



&lt;h2&gt;
  
  
  Highlight: Remix
&lt;/h2&gt;

&lt;p&gt;This is probably the "riskiest" infrastructure decision I made at that time, as Remix was not remotely as popular as NextJS and there weren't many examples of big enterprises using Remix to my knowledge. &lt;br&gt;
&lt;a href="https://www.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix" rel="noopener noreferrer"&gt;Fast forward to today - ChatGPT migrated from Next to Remix just a few days ago!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I detail &lt;a href="https://dev.to/n1tzan/choosing-a-fe-framework-in-2024-a-practical-tale-19ej"&gt;in my previous article&lt;/a&gt;, I chose Remix for many reasons, some being its simplicity, the "full-stack" aspect (namely, utilizing the remix server as a "backend for frontend") and its great abstractions for routing, data fetching and mutations.&lt;/p&gt;

&lt;p&gt;Fortunately, Remix delivered 😎 The framework is intuitive, easy to learn and teach others and ensures best practices are being used, making both writing code and testing it straightforward.&lt;/p&gt;

&lt;p&gt;A few months into working with Remix, they announced the &lt;a href="https://remix.run/blog/merging-remix-and-react-router" rel="noopener noreferrer"&gt;official merge with React Router&lt;/a&gt;, which I hope will persuade even more people to use it, just like &lt;a href="https://remix.run/blog/remix-heart-vite" rel="noopener noreferrer"&gt;their move to vite&lt;/a&gt; did.&lt;/p&gt;

&lt;p&gt;It became clear to me in many occasions that Remix was the right call. I'll give one practical example I tackled lately - using a single &lt;code&gt;logger&lt;/code&gt; instance in the remix server to be able to log and trace actions and errors across the entire app to enhance our monitoring abilities. The implementation was very straight-forward:&lt;/p&gt;

&lt;p&gt;Step 1 - create your logger (in my case I used &lt;a href="https://github.com/winstonjs/winston" rel="noopener noreferrer"&gt;winston&lt;/a&gt;, which works great with &lt;a href="https://docs.datadoghq.com/logs/log_collection/nodejs/?tab=winston30" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt; that we use for monitoring)&lt;/p&gt;

&lt;p&gt;Step 2 - add your logger to the server's load context (in my case it was express):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;createRequestHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;getLoadContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// add any other context variables here&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&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;Step 3 (for typescript users) - update Remix's default type definitions to include the logger in the app load context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Logger&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="s1"&gt;winston&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppLoadContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&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;Step 4 - use the logger as you wish in any route's &lt;code&gt;loader&lt;/code&gt; or &lt;code&gt;action&lt;/code&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;someAction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we conclude this section, I do wish to say that there are also things I wish Remix had but they don't yet, like an &lt;a href="https://github.com/remix-run/remix/discussions/8048" rel="noopener noreferrer"&gt;implementation of RSC&lt;/a&gt; for streaming data/components, and &lt;a href="https://github.com/remix-run/remix/discussions/7642" rel="noopener noreferrer"&gt;Route Middlewares&lt;/a&gt; which would be great for authentication/authorization. Fortunately, it looks like these things (and other cool features) are prioritized in their &lt;a href="https://github.com/orgs/remix-run/projects/5" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;, so hopefully we could get them soon!&lt;/p&gt;

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



&lt;h2&gt;
  
  
  Highlight: React Query
&lt;/h2&gt;

&lt;p&gt;Choosing &lt;code&gt;@tanstack/react-query&lt;/code&gt; was an easy decision for me, based on my past positive experiences, and it didn’t disappoint this time either. The API is versatile, extendable, and unopinionated in the best way — making it easy to integrate with other tools.&lt;/p&gt;

&lt;p&gt;I like it so much that I chose it knowing our internal API is GraphQL-based, instead of the more obvious choice that is Apollo Client. There are many reasons as to why: Tanstack Query has an excellent API, it’s significantly more lightweight than Apollo, and because I didn’t want to depend on a tool that’s heavily tailored to a specific technology like GraphQL, in case we ever need to switch or incorporate other technologies.&lt;/p&gt;

&lt;p&gt;Plus, since we're using Remix, I could fully utilize Tanstack Query’s SSR capabilities — prefetching queries on the server-side while still maintaining the ability to mutate, invalidate, or refetch these queries on the client side. Here's a simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HydrationBoundary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQuery&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="s1"&gt;@tanstack/react-query&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;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useLoaderData&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="s1"&gt;@remix-run/react&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;someDataQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&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="s1"&gt;some-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchSomeData&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loader&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;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someDataQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryClient&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// decide whether to handle the error or continue to&lt;/span&gt;
    &lt;span class="c1"&gt;// render the page and retry the query in the client&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyRouteComponent&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;dehydratedState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useLoaderData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;loader&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;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someDataQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HydrationBoundary&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;dehydratedState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
             &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SomeComponent&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;           &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/HydrationBoundary /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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



&lt;h2&gt;
  
  
  Highlight: Tailwind
&lt;/h2&gt;

&lt;p&gt;I was initially skeptical about Tailwind, having never used it before, and because I didn’t quite understand the hype (it seemed to me at first just like syntactic sugar over CSS). However, I decided to give it a try because of its strong recommendations and popularity within the community, and I’m really glad I did. Tailwind’s utility-first approach made it incredibly easy to build a consistent and robust design system right from the start, which, looking back, was a total game changer. &lt;br&gt;
It also pairs perfectly with shadcn, which we used, and together they allowed me to deliver quickly while keeping everything modular and easy to modify later on - a crucial advantage in a startup environment.&lt;/p&gt;

&lt;p&gt;I also really like how easy it is to customize tailwind's theme to your needs - for example, overriding tailwind's default scheme:&lt;/p&gt;

&lt;p&gt;First, define your colors as variable's under tailwind's main &lt;code&gt;.css&lt;/code&gt; file:&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;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* define the primitive design system tokens */&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;188&lt;/span&gt; &lt;span class="m"&gt;76%&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;187&lt;/span&gt; &lt;span class="m"&gt;63%&lt;/span&gt; &lt;span class="m"&gt;82%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;185&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="m"&gt;98%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt; &lt;span class="m"&gt;74%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt; &lt;span class="m"&gt;61%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;188&lt;/span&gt; &lt;span class="m"&gt;92%&lt;/span&gt; &lt;span class="m"&gt;95%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;74%&lt;/span&gt; &lt;span class="m"&gt;39%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;191&lt;/span&gt; &lt;span class="m"&gt;77%&lt;/span&gt; &lt;span class="m"&gt;34%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;51%&lt;/span&gt; &lt;span class="m"&gt;35%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;191&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt; &lt;span class="m"&gt;29%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;51%&lt;/span&gt; &lt;span class="m"&gt;23%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-blue-950&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;52%&lt;/span&gt; &lt;span class="m"&gt;17%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;90%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;85%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;98%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;73%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1%&lt;/span&gt; &lt;span class="m"&gt;62%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;94%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt; &lt;span class="m"&gt;53%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;44%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;36%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2%&lt;/span&gt; &lt;span class="m"&gt;28%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-gray-950&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;5%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;93%&lt;/span&gt; &lt;span class="m"&gt;94%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="m"&gt;96%&lt;/span&gt; &lt;span class="m"&gt;89%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="m"&gt;99%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;96%&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;92%&lt;/span&gt; &lt;span class="m"&gt;69%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="m"&gt;86%&lt;/span&gt; &lt;span class="m"&gt;97%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;88%&lt;/span&gt; &lt;span class="m"&gt;61%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;74%&lt;/span&gt; &lt;span class="m"&gt;49%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;76%&lt;/span&gt; &lt;span class="m"&gt;40%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="m"&gt;72%&lt;/span&gt; &lt;span class="m"&gt;33%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="m"&gt;65%&lt;/span&gt; &lt;span class="m"&gt;29%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--colors-red-950&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hsl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="m"&gt;75%&lt;/span&gt; &lt;span class="m"&gt;19%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c"&gt;/*
      ...
    */&lt;/span&gt;

    &lt;span class="c"&gt;/* define the semantic design system tokens */&lt;/span&gt;

    &lt;span class="py"&gt;--primary-light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-blue-200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-blue-600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--primary-dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-blue-800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--primary-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-blue-50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="py"&gt;--text-default-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--text-default-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--text-default-tertiary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-900&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--text-default-disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--text-default-read-only&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="py"&gt;--disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="py"&gt;--tertiary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--colors-gray-50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c"&gt;/*
      ...
    */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, extend Tailwind's default theme via the tailwind config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Config&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="s1"&gt;tailwindcss&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;ColorTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;BLUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;GRAY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gray&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;RED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&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;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;generateColorScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;scales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;950&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;scales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&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;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`var(--colors-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;colorName&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;scale&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="nx"&gt;acc&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;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&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;customColors&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="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ColorTokens&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nf"&gt;generateColorScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... additional config&lt;/span&gt;
  &lt;span class="na"&gt;theme&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="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customColors&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;satisfies&lt;/span&gt; &lt;span class="nx"&gt;Config&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;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just the tip of the iceberg - you can go on to define custom spacing, text sizing and much more!&lt;/p&gt;

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



&lt;h2&gt;
  
  
  Highlight: Playwright
&lt;/h2&gt;

&lt;p&gt;Previously using Cypress, I was inclined to choose it, but I kept hearing hype around Playwright and figured I'll research it extensively before making a decision. After comparing Playwright with Cypress, it was clear Playwright is the right choice to make - the fact it comes with parallel execution out of the box, the broader browser support, running times and debugging capabilities - all made Playwright the obvious choice.&lt;br&gt;
And, while this is very subjective, I like Playwright's syntax much better. I find it similar to React Testing Library's syntax, which I like, and I tend to think the tests are a lot more readable, with the asynchronous aspect of the tests being very straight forward, unlike the syntax of Cypress that can cause tests to feel bloated by &lt;code&gt;.then()&lt;/code&gt; statements and subsequent indentations.&lt;/p&gt;

&lt;p&gt;I think my favorite feature of Playwright is their implementation of &lt;a href="https://playwright.dev/docs/test-fixtures" rel="noopener noreferrer"&gt;Test Fixtures&lt;/a&gt;. They provide a clean way to initialize and reuse resources like page objects, making tests more modular and maintainable. Make sure to check out the above link to learn more about it!&lt;/p&gt;

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



&lt;h2&gt;
  
  
  Lowlight: (Starting with) Tanstack Table
&lt;/h2&gt;

&lt;p&gt;First off, let me clarify — &lt;code&gt;@tanstack/react-table&lt;/code&gt; is a fantastic tool, which is why I was inclined to choose it in the first place, but it wasn’t the best fit for my particular use case. The very features that make it great, like its small bundle size and customizable API, ended up being less relevant to our needs than I originally thought. Despite having full control of the rendering of the Table, I was having some issues aligning its scrolling behavior to our desired outcome (why is it still not possible in 2024 to have a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; element with dynamic sizing and scrolling on its body only, without resorting to clunky solutions? 🤦).&lt;/p&gt;

&lt;p&gt;I soon realized that to deliver my feature fast &lt;em&gt;and&lt;/em&gt; provide a good user experience, I needed a table with built-in features like pagination, column resizing and row auto-sizing, and I preferred having those out of the box over full control of the UI rendering. Additionally, since the table only appears after a query is run, I could lazy load it, making the bundle size less of a concern.&lt;/p&gt;

&lt;p&gt;I highly recommend using the &lt;a href="https://www.ag-grid.com/theme-builder/" rel="noopener noreferrer"&gt;AG Grid theme builder&lt;/a&gt; to customize AG Grid according to your preferences/design system. And, for those using Cypress for their testing purposes - I found this &lt;a href="https://github.com/kpmck/cypress-ag-grid" rel="noopener noreferrer"&gt;cool plugin&lt;/a&gt; that abstracts AG Grid to easily interact with it in tests &lt;em&gt;(sadly I could not find the same for Playwright 😔)&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Looking back, I definitely feel a sense of pride in what we’ve accomplished. Not every decision was perfect, but taking the time to research and find the most fitting solution was worth it. And when things didn’t go as planned - it challenged us to think critically and adapt quickly, which is important no less.&lt;/p&gt;

&lt;p&gt;Please let me know in the comments if there’s something you’d like to see explored further in future articles.&lt;br&gt;
Here’s to more lessons learned, personal growth and having fun along the way 💃&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>learning</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Choosing a Frontend framework in 2024, a practical tale</title>
      <dc:creator>Nitzan Kletter</dc:creator>
      <pubDate>Tue, 06 Feb 2024 17:14:51 +0000</pubDate>
      <link>https://dev.to/n1tzan/choosing-a-fe-framework-in-2024-a-practical-tale-19ej</link>
      <guid>https://dev.to/n1tzan/choosing-a-fe-framework-in-2024-a-practical-tale-19ej</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I find that one of the hardest parts of starting a new project that requires a user interface is having to choose between the endless options that exist in the front-end ecosystem - React, Vue, Angular, Svelte, HTMX - to only name a few.&lt;/p&gt;

&lt;p&gt;I bumped into this recently when attempting to choose the technology for a new upcoming work project. So, I thought I'd document my thought process in hopes of sharing my findings.&lt;/p&gt;

&lt;p&gt;I hope that this article will be of value to devs in a similar position, and also to those who have little FE experience and want to understand the current challenges in the ecosystem, some solutions that exist and what each brings to the table.&lt;/p&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  What's in this article
&lt;/h2&gt;

&lt;p&gt;I will compare 5 notable FE frameworks/libraries that are either popular, on-the-rise, or both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ReactJS&lt;/strong&gt;, and the React-based frameworks &lt;strong&gt;NextJS&lt;/strong&gt; and &lt;strong&gt;Remix&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SolidJS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTMX&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will give a short description of each, as well as a few pros/cons as I see them for my own situation, and finally, I will share my decision.&lt;/p&gt;

&lt;p&gt;I  am  taking into account that in the near future more people will join me in maintaining this project as it grows in scale, so I'm looking for a solution that will seem easy to kickstart but also to maintain and grow.&lt;/p&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  About myself
&lt;/h2&gt;

&lt;p&gt;I'm Nitzan, and I've been a (mostly Web) developer for the past 7 years. I come from a hands-on cyber background but found that my passion is in the intersection between technology and the user experience, hence my passion for frontend. Over those 7 years I worked on multiple cybersecurity products, generally focusing on threat detection and asset visibility, and will continue my journey with a new startup in the very near future 🙌 &lt;/p&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR plz!
&lt;/h2&gt;

&lt;p&gt;Skip to the final section at the bottom :)&lt;/p&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Dictionary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you come from a FE background you can probably skim through this section, but I think it's necessary for understanding the bigger picture in the comparison later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SPA&lt;/strong&gt; = Single page application; A website that re-renders its content in response to navigation actions (e.g. clicking a link) without making a request to the server to fetch new HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSR&lt;/strong&gt; = Client side rendering; A web development approach where the rendering of a webpage occurs primarily in the browser using JavaScript. This approach is closely associated with SPAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR&lt;/strong&gt; = Server side rendering. The initial content page of the website is generated on the server, so that browser can download a page with HTML content already in place. Updates to the content are still handled in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt; = Search engine optimization; It's the practice of optimizing websites to improve their visibility and ranking in search engine results pages. When SEO is important, SSR approach is preferred over SPA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsijdbvsyho606ad8bog5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsijdbvsyho606ad8bog5.png" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;
If you couldn't tell, I wrote this "joke" myself. Props to https://www.makewordart.com/



&lt;p&gt;    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DOM&lt;/strong&gt; = Document Object Model; A programming interface for web documents. It represents the browser page as a tree of objects, where each object corresponds to a part of the page. Via the DOM, programs can change the document structure, style, and content (objects/elements within).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VDOM&lt;/strong&gt; = Virtual DOM; A concept implemented in libraries like React. It's a lightweight copy of the actual DOM. The VDOM allows for efficient updating of the web page, as it involves only re-rendering components in the VDOM when their state changes, therefore optimizing rendering (and potentially performance), and providing a smooth(er) user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3co0zm4i0fipoa9e7ykt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3co0zm4i0fipoa9e7ykt.gif" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;
DOM vs VDOM, from &lt;a href="https://medium.com/@omkarbhavare2406/the-virtual-dom-an-in-memory-shadow-of-the-real-dom-8be047d68bb9" rel="noopener noreferrer"&gt;this article&lt;/a&gt;



&lt;p&gt;    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI component&lt;/strong&gt; = a self-contained and reusable piece of code that represents a part of the user interface (e.g a button or a form)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSX&lt;/strong&gt; = Syntax extension to JavaScript, combining markup (HTML) with the power of javascript. This syntax is commonly used in the React ecosystem, and UI components in React are usually written with JSX syntax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Management&lt;/strong&gt; = the process of managing the dynamic data, or "state", that controls the behavior and appearance of an application and its UI components. This state can include user inputs, server responses, and other variables that determine what the UI displays at any given moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uefmf3malca2xsmb4qp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uefmf3malca2xsmb4qp.png" width="730" height="379"&gt;&lt;/a&gt;&lt;/p&gt;
stateful UI component lifecycle in React, from &lt;a href="https://blog.logrocket.com/modern-guide-react-state-patterns" rel="noopener noreferrer"&gt;this article&lt;/a&gt;



&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Lets compare!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;React is a JavaScript library used for building user interfaces, primarily SPAs (at least historically). It allows developers to create reusable UI components, manage the state of these components, and efficiently update and render just the right components when data changes (using the VDOM approach).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It has an incredibly large developer community, leverages widely-used JSX syntax, and its idiomatic nature to JS/TS can be a lesser learning curve for people coming from the JS ecosystem.&lt;/li&gt;
&lt;li&gt;It has &lt;a href="https://survey.stackoverflow.co/2023/#most-popular-technologies-webframe-prof" rel="noopener noreferrer"&gt;proved itself&lt;/a&gt; to be a very stable option for many years now and a favorite among web devs, and therefore its not surprising that the job market favors it (and, while this isn't strictly a "technical" reason, it is still important when focusing on long-term maintainability) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React is a UI library at its core (not a framework), therefore it requires, by definition, the usage of additional tools to reach its full potential. While this "unopinionated" approach makes React more flexible on paper, it can make its DX for large scale applications worse compared to meta-frameworks like NextJS, that provide more out-of-the-box features and therefore requires less code to be written.&lt;/li&gt;
&lt;li&gt;As of writing this, there's a lot of criticism of React and where its heading. Apparently, React themselves recommend using a meta-framework and not just "plain React" in their &lt;a href="https://react.dev/learn/start-a-new-react-project" rel="noopener noreferrer"&gt;"getting started" page&lt;/a&gt;, which is... &lt;em&gt;interesting&lt;/em&gt;. I particularly resonated with &lt;a href="https://dev.to/matfrana/react-where-are-you-going-5284"&gt;this article&lt;/a&gt;, and also enjoyed &lt;a href="https://twitter.com/dabit3/status/1750693900648386811" rel="noopener noreferrer"&gt;this funny video&lt;/a&gt;, which I think explains the current turmoil in the React ecosystem (and FE ecosystem in general) pretty well. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  Solid
&lt;/h3&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;Solid is a JS library for creating user interfaces. Instead of using a Virtual DOM, it compiles its templates to real DOM nodes and updates them with fine-grained reactions. While React is well known for its declarative architecture, SolidJS takes a different approach to state management and reactivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its bare-metal, minimal abstractions approach allows for direct access to the DOM, making it easy to use native JavaScript libraries.&lt;/li&gt;
&lt;li&gt;It is more performant and efficient than React - when there is a change in data, it updates only the necessary components (meaning in cases of deeply nested components, it would perform better than React's VDOM).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both because SolidJS is relatively new (created in 2018) and because of its relatively-small community adaptation, there's limited documentation and less available tools and plugins. These reasons, especially compared to React's mature ecosystem and community support, makes SolidJS less appealing, especially when building large scale, complex applications.&lt;/li&gt;
&lt;li&gt;While SolidJS provides efficient SSR capabilities, it does not not offer a built-in, comprehensive solution for hybrid rendering similar to Next.js, which seamlessly combines Server-Side Rendering (SSR), Static Site Generation (SSG), and client-side rendering within the same application framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  HTMX
&lt;/h3&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;HTMX is a library that allows you to access modern browser features directly from HTML/markup, without having to utilize JavaScript. It encapsulates AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML using attributes, so you can build modern user interfaces with the simplicity and power of hypertext. With HTMX, elements can now issue an HTTP request, any event can trigger API requests (not just clicks/form submission), and any element can be the target for update by the request (not just the entire window).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has the potential to simplify the tech stack dramatically - no more different BE and FE frameworks and duplicated utility functions, straight-forward deployment.&lt;/li&gt;
&lt;li&gt;Developer velocity can be greater by eliminating the need to juggle between FE and BE for small changes - no more need to understand the entire code architecture to follow a feature end-to-end (just use "go to definition"!).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTMX, while on-the-rise, remains a smaller community compared to React. Its adoption by the job market and web developers, particularly for large-scale applications, is still limited today next to older frameworks such as React. &lt;/li&gt;
&lt;li&gt;HTMX still doesn't fully replace existing market technologies, especially in developing interactive, complex applications. It has limitations such as client-side interactivity, lack of reusable components and type safety. These factors are essential for scalability and maintainability of applications as they expand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  NextJS
&lt;/h3&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;NextJS is a framework built on top of React that enables functionality such as SSR and SSG, which are not readily available in vanilla React. It implements a set of conventions for structuring React applications as well as commonly needed features like automatic code splitting, optimized prefetching, and route preloading out of the box, offering a streamlined setup for building fast and scalable web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a stable, well-established framework with a mature ecosystem, has a large community and has been going stronger and stronger for years. Even though its a meta-framework, NextJS still feels in-line with how React themselves are evolving the library, being the first mature implementation of React Server Components, and as I said above, the first option that's recommended in React's "getting started" page.&lt;/li&gt;
&lt;li&gt;The conventions and out-of-the-box features implemented by Next make it a very robust choice, especially fitting for larger-scale applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next has a relatively big learning curve - both needing to learn about the React ecosystem as well as Next's own robust ecosystem. Even developers that have used Next previously (most likely with the Page Router) could still have a big learning curve getting used to the App Router paradigm, which has been deemed stable in Next's v13.4 release in May 2023 (just less than a year ago).&lt;/li&gt;
&lt;li&gt;It's owned by Vercel, meaning it also leans towards Vercel as a hosting platform and raises concerns for vendor lock-in. While it is possible to deploy without Vercel, I tend to prefer solutions that allow for easy deployment in whichever way ends up being chosen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  Remix
&lt;/h3&gt;

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



&lt;p&gt;    &lt;/p&gt;

&lt;p&gt;Remix is yet another meta-framework built on top of React, which similarly to NextJS, also implements many common features and use-cases out-of-the-box such as enhanced data loading and caching mechanisms, optimized network requests, built-in state management for forms and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Remix places a strong emphasis on leveraging web fundamentals, aiming for accessible and performant web applications, and features a very straight-forward, declarative syntax for data fetching and data mutations, with the concepts of "loaders" and "actions". This relatively low learning curve (especially for devs with React knowledge) makes it a very interesting candidate. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remix seem to &lt;a href="https://remix.run/blog/remix-vs-next" rel="noopener noreferrer"&gt;fair better than NextJS&lt;/a&gt; in performance and build-related aspects (serving static and dynamic content fast even on slow networks, stable build times, etc), and the framework itself focuses on flexibility as to not sacrifice performance or require application architecture changes when your data scales.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Downsides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As of February 2024, Remix has yet to implement React Server Components (which seems to be the future of React), and has a smaller community relative to other React meta-frameworks, specifically Next, making Next the more obvious and "safe" choice for a new large-scale application focusing on maintainability.&lt;/li&gt;
&lt;li&gt;Its been acquired by Shopify, which draws skepticism that stem from concerns about the future independence and direction of Remix under Shopify's ownership (especially because of its open-source nature)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  Some more thoughts on Next vs Remix
&lt;/h3&gt;

&lt;p&gt;As I wrote above, Next indeed seemed like the more obvious choice at first glance, but I thought it would be right to try out both before choosing one over another. &lt;/p&gt;

&lt;p&gt;After experimenting with both, I can say that writing in Remix was a very smooth and pleasant ride, while getting used to the new App Router paradigm in Next was not too enjoyable for me. It seemed, with Remix, I could spend 2 minutes on reading documentation and get the application up and running, while with Next I had to spend a lot more time reading about the in-and-outs of the App Router and React Server Components just to get a basic example working.&lt;/p&gt;

&lt;p&gt;So, after trying it out myself and doing a lot of reading (especially &lt;a href="https://prateeksurana.me/blog/nextjs-13-vs-remix-an-in-depth-case-study/" rel="noopener noreferrer"&gt;this great article&lt;/a&gt;), and to my own surprise, I actually felt more comfortable choosing Remix over Next, and I look forward to seeing how they decide to implement React Server Components in the future.&lt;/p&gt;

&lt;p&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I'll be picking &lt;strong&gt;Remix&lt;/strong&gt; for a few main reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's built upon the well-known React ecosystem and implements great abstractions for routing, data fetching and mutations, and I find it has a very small learning curve for devs with previous React knowledge (which is common as its favored by the job market). &lt;/li&gt;
&lt;li&gt;I like how flexible the framework is (regarding deployment and otherwise), and I particularly love their philosophy and the emphasis they put on web fundamentals.&lt;/li&gt;
&lt;li&gt;Remix fits great to our planned architecture - since the main business logic will be written in a non-JS language, we will use Remix in a "BFF" (Backend for Frontend) architecture, which is also a &lt;a href="https://remix.run/docs/en/main/guides/bff" rel="noopener noreferrer"&gt;strategy recommended by Remix&lt;/a&gt; themselves. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, I'm extremely intrigued by &lt;strong&gt;HTMX&lt;/strong&gt; because of their innovative approach, but I think that at its current state and community adaptation it's too much of a risky choice. I will definitely try out HTMX for my next personal project though!&lt;/p&gt;

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