<?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: 𝗝𝗼𝗵𝗻</title>
    <description>The latest articles on DEV Community by 𝗝𝗼𝗵𝗻 (@johnnylemonny).</description>
    <link>https://dev.to/johnnylemonny</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%2F3868757%2F52777a3a-3f32-4bcd-8f3c-002c1983dcac.jpg</url>
      <title>DEV Community: 𝗝𝗼𝗵𝗻</title>
      <link>https://dev.to/johnnylemonny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnnylemonny"/>
    <language>en</language>
    <item>
      <title>AI Can Build Your UI — But Can It Maintain It?</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 04 Jun 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/ai-can-build-your-ui-but-can-it-maintain-it-d2l</link>
      <guid>https://dev.to/johnnylemonny/ai-can-build-your-ui-but-can-it-maintain-it-d2l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI can build a surprisingly convincing UI in a few minutes.&lt;/p&gt;

&lt;p&gt;That is useful. It is also dangerous in the same way a good mockup is dangerous: it can look finished before the engineering decisions are finished.&lt;/p&gt;

&lt;p&gt;The question is no longer whether AI can generate components.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can your team maintain what AI generated six months from now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Generated UI has a smell
&lt;/h2&gt;

&lt;p&gt;AI-generated frontend often looks impressive at first glance and strange on review.&lt;/p&gt;

&lt;p&gt;Common signs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicated components,&lt;/li&gt;
&lt;li&gt;inconsistent spacing,&lt;/li&gt;
&lt;li&gt;random animation choices,&lt;/li&gt;
&lt;li&gt;inaccessible markup,&lt;/li&gt;
&lt;li&gt;weak loading and error states,&lt;/li&gt;
&lt;li&gt;hard-coded colors,&lt;/li&gt;
&lt;li&gt;unclear state ownership,&lt;/li&gt;
&lt;li&gt;no component contract,&lt;/li&gt;
&lt;li&gt;tests that only prove rendering happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means the tool is bad. It means the output needs engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give AI constraints, not vibes
&lt;/h2&gt;

&lt;p&gt;Bad prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a beautiful dashboard.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a dashboard page using our existing Card, Button, Badge, and Table components.
Use semantic HTML. Include loading, empty, and error states.
Do not introduce new colors outside the design tokens.
Keep state local unless it is shared by more than one component.
Add keyboard-accessible interactions.
Return the component and a short review checklist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI is much more useful when you give it boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for states, not just screens
&lt;/h2&gt;

&lt;p&gt;A screen is not a component.&lt;/p&gt;

&lt;p&gt;A component has states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loading
empty
success
error
permission denied
partial data
saving
saved
validation failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the generated UI only includes the happy path, it is not ready for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Require component contracts
&lt;/h2&gt;

&lt;p&gt;Before accepting generated UI, define the contract:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;InvoiceTableProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvoiceSummary&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onRetry&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onOpenInvoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvoiceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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 forces the component to have a clear boundary.&lt;/p&gt;

&lt;p&gt;Without a contract, generated UI tends to reach outward: global state, random fetches, hidden assumptions, and props that grow without discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep design tokens non-negotiable
&lt;/h2&gt;

&lt;p&gt;AI loves inventing colors.&lt;/p&gt;

&lt;p&gt;Do not let it.&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="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0b1020&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#121a2f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f8fafc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#94a3b8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#22c55e&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;Tell the tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use only existing design tokens. Do not invent new hex colors, spacing values, shadows, or font sizes unless explicitly requested.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consistency is not decoration. It is maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make accessibility part of the prompt
&lt;/h2&gt;

&lt;p&gt;Do not ask for accessibility after the component is generated.&lt;/p&gt;

&lt;p&gt;Include it at the start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use semantic HTML.
All interactive elements must be keyboard accessible.
Use visible focus states.
Do not use divs as buttons.
Connect form labels and error messages properly.
Avoid ARIA unless native HTML is not enough.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will not guarantee perfect accessibility, but it raises the floor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review generated UI like a pull request
&lt;/h2&gt;

&lt;p&gt;Use this checklist:&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="gu"&gt;## AI-generated UI review&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Does it use existing components and tokens?
&lt;span class="p"&gt;-&lt;/span&gt; Are loading, empty, error, and permission states handled?
&lt;span class="p"&gt;-&lt;/span&gt; Is the markup semantic?
&lt;span class="p"&gt;-&lt;/span&gt; Is keyboard interaction supported?
&lt;span class="p"&gt;-&lt;/span&gt; Are props explicit and typed?
&lt;span class="p"&gt;-&lt;/span&gt; Is state owned by the right component?
&lt;span class="p"&gt;-&lt;/span&gt; Are side effects isolated?
&lt;span class="p"&gt;-&lt;/span&gt; Are tests meaningful?
&lt;span class="p"&gt;-&lt;/span&gt; Is there duplicated logic?
&lt;span class="p"&gt;-&lt;/span&gt; Could another developer safely change this later?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last question is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good uses of AI in frontend
&lt;/h2&gt;

&lt;p&gt;AI is genuinely helpful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first drafts,&lt;/li&gt;
&lt;li&gt;test scaffolding,&lt;/li&gt;
&lt;li&gt;refactoring repetitive markup,&lt;/li&gt;
&lt;li&gt;converting rough UI ideas into components,&lt;/li&gt;
&lt;li&gt;writing stories for component states,&lt;/li&gt;
&lt;li&gt;finding accessibility issues,&lt;/li&gt;
&lt;li&gt;generating edge-case checklists,&lt;/li&gt;
&lt;li&gt;explaining unfamiliar code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is less reliable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture boundaries,&lt;/li&gt;
&lt;li&gt;product intent,&lt;/li&gt;
&lt;li&gt;design consistency,&lt;/li&gt;
&lt;li&gt;long-term ownership,&lt;/li&gt;
&lt;li&gt;subtle accessibility behavior,&lt;/li&gt;
&lt;li&gt;performance tradeoffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it where speed helps. Slow down where judgment matters.&lt;/p&gt;

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

&lt;p&gt;AI can generate UI. That is no longer the impressive part.&lt;/p&gt;

&lt;p&gt;The impressive part is building a frontend system where generated code has to pass through the same standards as human-written code.&lt;/p&gt;

&lt;p&gt;A fast draft is useful. A maintainable product is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Syncfusion, “Frontend Development Trends 2026,” published April 28, 2026: &lt;a href="https://www.syncfusion.com/blogs/post/frontend-development-trends" rel="noopener noreferrer"&gt;https://www.syncfusion.com/blogs/post/frontend-development-trends&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2025 JavaScript Rising Stars, published 2026: &lt;a href="https://risingstars.js.org/2025/en" rel="noopener noreferrer"&gt;https://risingstars.js.org/2025/en&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stack Overflow Blog, “Domain expertise still wanted: the latest trends in AI-assisted knowledge for developers,” published March 16, 2026: &lt;a href="https://stackoverflow.blog/2026/03/16/domain-expertise-still-wanted-the-latest-trends-in-ai/" rel="noopener noreferrer"&gt;https://stackoverflow.blog/2026/03/16/domain-expertise-still-wanted-the-latest-trends-in-ai/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Accessibility Is Not a Lighthouse Checkbox</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 02 Jun 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/accessibility-is-not-a-lighthouse-checkbox-2a1n</link>
      <guid>https://dev.to/johnnylemonny/accessibility-is-not-a-lighthouse-checkbox-2a1n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Automated accessibility checks are useful.&lt;/p&gt;

&lt;p&gt;They catch missing labels, obvious contrast failures, invalid ARIA, and a lot of mistakes that should never reach production.&lt;/p&gt;

&lt;p&gt;But accessibility is not finished when a tool gives you a green score.&lt;/p&gt;

&lt;p&gt;A green score can still hide a page that is confusing, exhausting, or impossible to use with a keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with semantic HTML
&lt;/h2&gt;

&lt;p&gt;The most underrated accessibility tool is still HTML.&lt;/p&gt;

&lt;p&gt;Use the element that matches the job:&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;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save changes&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Account settings&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Main navigation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not turn everything into a &lt;code&gt;div&lt;/code&gt; and then rebuild the platform with ARIA.&lt;/p&gt;

&lt;p&gt;ARIA is powerful. It is also easy to misuse. Native semantics are usually safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard test every important flow
&lt;/h2&gt;

&lt;p&gt;Put the mouse away.&lt;/p&gt;

&lt;p&gt;Can you complete the flow with only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tab
Shift + Tab
Enter
Space
Arrow keys
Escape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opening menus,&lt;/li&gt;
&lt;li&gt;closing dialogs,&lt;/li&gt;
&lt;li&gt;submitting forms,&lt;/li&gt;
&lt;li&gt;moving through filters,&lt;/li&gt;
&lt;li&gt;skipping repeated navigation,&lt;/li&gt;
&lt;li&gt;reaching error messages,&lt;/li&gt;
&lt;li&gt;recovering from mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product cannot be used from a keyboard, it is broken for more people than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus is part of the interface
&lt;/h2&gt;

&lt;p&gt;Removing focus styles is not polish. It is damage.&lt;/p&gt;

&lt;p&gt;Good focus indicators are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visible,&lt;/li&gt;
&lt;li&gt;consistent,&lt;/li&gt;
&lt;li&gt;high contrast,&lt;/li&gt;
&lt;li&gt;not hidden behind shadows,&lt;/li&gt;
&lt;li&gt;not dependent on color alone.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&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;--focus-ring&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to make focus pretty enough for a screenshot. The goal is to make the current location obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forms need more care than we give them
&lt;/h2&gt;

&lt;p&gt;A good form explains itself before, during, and after input.&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;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"email-hint"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-hint"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Use the address where you want to receive account updates.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;place the message near the field,&lt;/li&gt;
&lt;li&gt;describe how to fix it,&lt;/li&gt;
&lt;li&gt;connect it programmatically,&lt;/li&gt;
&lt;li&gt;summarize errors for long forms,&lt;/li&gt;
&lt;li&gt;move focus intentionally after failed submission.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Invalid input” is not a helpful error. It is a shrug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dialogs are traps unless designed carefully
&lt;/h2&gt;

&lt;p&gt;A modal dialog should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move focus into the dialog when opened,&lt;/li&gt;
&lt;li&gt;trap focus while open,&lt;/li&gt;
&lt;li&gt;close with Escape when appropriate,&lt;/li&gt;
&lt;li&gt;return focus to the triggering element,&lt;/li&gt;
&lt;li&gt;have a clear accessible name,&lt;/li&gt;
&lt;li&gt;avoid background interaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that sounds like a lot, use a well-tested primitive. Dialogs are one of the places where “simple custom code” often becomes inaccessible custom code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not use color alone
&lt;/h2&gt;

&lt;p&gt;Color is useful. It should not be the only signal.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required fields are red.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required fields have text, an icon, and programmatic state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For charts, statuses, validation, and alerts, combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text,&lt;/li&gt;
&lt;li&gt;shape,&lt;/li&gt;
&lt;li&gt;position,&lt;/li&gt;
&lt;li&gt;iconography,&lt;/li&gt;
&lt;li&gt;semantic markup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automated tests are a floor
&lt;/h2&gt;

&lt;p&gt;Add accessibility checks to CI:&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;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&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;@playwright/test&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;AxeBuilder&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;@axe-core/playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page has no obvious accessibility violations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AxeBuilder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is good. It is not enough.&lt;/p&gt;

&lt;p&gt;Automated tools cannot fully judge whether the flow makes sense, whether focus movement feels logical, or whether the content is understandable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human checklist
&lt;/h2&gt;

&lt;p&gt;Before release, test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Can the full flow be completed with keyboard only?&lt;/li&gt;
&lt;li&gt;[ ] Is focus always visible?&lt;/li&gt;
&lt;li&gt;[ ] Are headings meaningful and ordered?&lt;/li&gt;
&lt;li&gt;[ ] Are form labels explicit?&lt;/li&gt;
&lt;li&gt;[ ] Are errors helpful and connected to fields?&lt;/li&gt;
&lt;li&gt;[ ] Are dialogs focus-managed?&lt;/li&gt;
&lt;li&gt;[ ] Is color supported by another signal?&lt;/li&gt;
&lt;li&gt;[ ] Does the page make sense with CSS disabled?&lt;/li&gt;
&lt;li&gt;[ ] Does the page make sense with images unavailable?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Accessibility is not a separate layer you add after the “normal” product is done.&lt;/p&gt;

&lt;p&gt;It is part of the product being usable.&lt;/p&gt;

&lt;p&gt;A green automated score is a good start. A real user completing a real task without barriers is the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CSS-Tricks, “HTTP Archive 2025 Web Almanac,” published January 16, 2026: &lt;a href="https://css-tricks.com/http-archive-2025-web-almanac/" rel="noopener noreferrer"&gt;https://css-tricks.com/http-archive-2025-web-almanac/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syncfusion, “Frontend Development Trends 2026,” published April 28, 2026: &lt;a href="https://www.syncfusion.com/blogs/post/frontend-development-trends" rel="noopener noreferrer"&gt;https://www.syncfusion.com/blogs/post/frontend-development-trends&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MDN Web Docs, “Baseline compatibility”: &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>CSS Got Good While You Were Installing More Libraries</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 28 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/css-got-good-while-you-were-installing-more-libraries-dm1</link>
      <guid>https://dev.to/johnnylemonny/css-got-good-while-you-were-installing-more-libraries-dm1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A lot of frontend habits were formed when CSS was weaker.&lt;/p&gt;

&lt;p&gt;We reached for libraries because layout was painful, responsive components were awkward, browser support was uncertain, and the cascade felt like a haunted basement.&lt;/p&gt;

&lt;p&gt;That history is real.&lt;/p&gt;

&lt;p&gt;But modern CSS is not the same tool many developers learned five or ten years ago.&lt;/p&gt;

&lt;p&gt;CSS got good. Quietly. While everyone was arguing about JavaScript frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new default: check the platform first
&lt;/h2&gt;

&lt;p&gt;Before installing a styling library, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the platform solve this now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Often, the answer is yes.&lt;/p&gt;

&lt;p&gt;Modern CSS gives us better primitives for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;responsive components,&lt;/li&gt;
&lt;li&gt;layout composition,&lt;/li&gt;
&lt;li&gt;cascade control,&lt;/li&gt;
&lt;li&gt;design tokens,&lt;/li&gt;
&lt;li&gt;theme switching,&lt;/li&gt;
&lt;li&gt;typography,&lt;/li&gt;
&lt;li&gt;state-based styling,&lt;/li&gt;
&lt;li&gt;fluid spacing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Libraries can still be useful. They should not be reflexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Container queries change component design
&lt;/h2&gt;

&lt;p&gt;Viewport media queries answer this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How wide is the screen?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Container queries answer the more useful question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much space does this component have?&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;container-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@container&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.article-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14rem&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&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 makes components more portable. A card can adapt inside a sidebar, dashboard, modal, or full-width layout without pretending the viewport is the only context.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;:has()&lt;/code&gt; makes parent-aware styling practical
&lt;/h2&gt;

&lt;p&gt;For years, developers wanted a parent selector. Now we have a powerful version of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.field&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:invalid&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-color&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;--color-danger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.card&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.card__media&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&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;Used carefully, &lt;code&gt;:has()&lt;/code&gt; can remove JavaScript that only exists to toggle classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cascade layers make CSS less fragile
&lt;/h2&gt;

&lt;p&gt;The cascade is not bad. Unmanaged cascade is bad.&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;reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;,&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;tokens&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="py"&gt;--space-4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--radius-lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;@layer&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&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;--radius-lg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;padding&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;--space-4&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;Layers let you define priority intentionally instead of relying on selector combat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subgrid fixes a real layout problem
&lt;/h2&gt;

&lt;p&gt;Nested layouts often need to align with parent tracks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.article-list&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.article-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;subgrid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;-1&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 helps create layouts that feel designed, not merely stacked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluid type without a framework
&lt;/h2&gt;

&lt;p&gt;You can create responsive typography with plain CSS:&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="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--step-0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.96rem&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.2vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.125rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--step-3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.75rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="m"&gt;2vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3rem&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&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;--step-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;;&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="nl"&gt;font-size&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;--step-0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.7&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;No runtime. No package. No hydration. Just CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Baseline thinking
&lt;/h2&gt;

&lt;p&gt;The modern question is not “Can I use this feature on my machine?”&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this feature supported well enough for my audience and fallback strategy?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Baseline helps make that decision more systematic, but it does not replace testing. You still need to check accessibility, usability, performance, and your actual user base.&lt;/p&gt;

&lt;h2&gt;
  
  
  The library decision checklist
&lt;/h2&gt;

&lt;p&gt;Before adding a styling dependency, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is the problem solved by modern CSS?&lt;/li&gt;
&lt;li&gt;[ ] Does the library add runtime JavaScript?&lt;/li&gt;
&lt;li&gt;[ ] Does it make accessibility easier or harder?&lt;/li&gt;
&lt;li&gt;[ ] Can the team debug the generated output?&lt;/li&gt;
&lt;li&gt;[ ] Does it work with our design tokens?&lt;/li&gt;
&lt;li&gt;[ ] Can we remove it later?&lt;/li&gt;
&lt;li&gt;[ ] Is the dependency worth the long-term surface area?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;CSS is not just the thing you write after the “real” engineering is done.&lt;/p&gt;

&lt;p&gt;It is a powerful layout and interaction language that now solves problems we used to outsource by default.&lt;/p&gt;

&lt;p&gt;Install libraries when they help. But give the platform a chance first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;web.dev, “Baseline”: &lt;a href="https://web.dev/baseline/" rel="noopener noreferrer"&gt;https://web.dev/baseline/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MDN Web Docs, “Baseline compatibility”: &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CSS-Tricks, “HTTP Archive 2025 Web Almanac,” published January 16, 2026: &lt;a href="https://css-tricks.com/http-archive-2025-web-almanac/" rel="noopener noreferrer"&gt;https://css-tricks.com/http-archive-2025-web-almanac/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>design</category>
    </item>
    <item>
      <title>TypeScript Won — Now Stop Using It Like Fancy JavaScript</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 26 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/typescript-won-now-stop-using-it-like-fancy-javascript-3hi0</link>
      <guid>https://dev.to/johnnylemonny/typescript-won-now-stop-using-it-like-fancy-javascript-3hi0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TypeScript won the argument.&lt;/p&gt;

&lt;p&gt;Most serious JavaScript codebases either use it already or are planning around it. The more interesting question is what we do after adoption.&lt;/p&gt;

&lt;p&gt;Because a lot of TypeScript code is still just JavaScript wearing a blazer.&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUser&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;any&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Technically TypeScript. Spiritually not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types should protect boundaries
&lt;/h2&gt;

&lt;p&gt;The best places to spend type effort are boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API input,&lt;/li&gt;
&lt;li&gt;API output,&lt;/li&gt;
&lt;li&gt;database records,&lt;/li&gt;
&lt;li&gt;form state,&lt;/li&gt;
&lt;li&gt;configuration,&lt;/li&gt;
&lt;li&gt;feature flags,&lt;/li&gt;
&lt;li&gt;permissions,&lt;/li&gt;
&lt;li&gt;domain events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Internal implementation types are useful. Boundary types are where bugs go to die.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace vague shapes with domain language
&lt;/h2&gt;

&lt;p&gt;This is common:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&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="nl"&gt;role&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="nl"&gt;status&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is better than nothing, but it leaves too much room.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&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;UserStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;suspended&lt;/span&gt;&lt;span class="dl"&gt;"&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;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now invalid states have fewer places to hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use branded types for dangerous strings
&lt;/h2&gt;

&lt;p&gt;Not every string is the same kind of string.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Brand&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="kd"&gt;extends&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;__brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Brand&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UserId&lt;/span&gt;&lt;span class="dl"&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;type&lt;/span&gt; &lt;span class="nx"&gt;OrgId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Brand&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OrgId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&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;Now this mistake becomes harder:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&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;A plain string can come from anywhere. A branded string says, “This value passed through a specific gate.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer discriminated unions over boolean soup
&lt;/h2&gt;

&lt;p&gt;Boolean-heavy state is where UI bugs breed.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&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="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;User&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 allows nonsense:&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="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;loading&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;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;Failed&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a union:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RequestState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the impossible states are actually impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate runtime data
&lt;/h2&gt;

&lt;p&gt;TypeScript does not validate JSON at runtime.&lt;/p&gt;

&lt;p&gt;This is one of the most expensive misunderstandings in web development.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line does not make the response a &lt;code&gt;User&lt;/code&gt;. It makes the compiler quiet.&lt;/p&gt;

&lt;p&gt;Use runtime validation at external boundaries:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;editor&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;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invited&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;active&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;suspended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now TypeScript and runtime reality are connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;satisfies&lt;/code&gt; for configuration
&lt;/h2&gt;

&lt;p&gt;Configuration should be checked without losing literal types.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;home&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="na"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&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="s2"&gt;`/&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="s2"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&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;This catches invalid values while preserving useful inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid type theater
&lt;/h2&gt;

&lt;p&gt;Some types add noise but not safety.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringOrNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&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;Callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;any&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;ObjectMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&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;These can be valid in rare cases, but often they are placeholders for decisions nobody made.&lt;/p&gt;

&lt;p&gt;Good TypeScript is not more TypeScript. Good TypeScript is better constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical checklist
&lt;/h2&gt;

&lt;p&gt;Use this before merging TypeScript-heavy changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Are external inputs validated at runtime?&lt;/li&gt;
&lt;li&gt;[ ] Are domain concepts named explicitly?&lt;/li&gt;
&lt;li&gt;[ ] Are impossible UI states impossible?&lt;/li&gt;
&lt;li&gt;[ ] Are IDs and tokens distinguishable?&lt;/li&gt;
&lt;li&gt;[ ] Are &lt;code&gt;any&lt;/code&gt; and unsafe assertions isolated?&lt;/li&gt;
&lt;li&gt;[ ] Can a new developer understand the model from the types?&lt;/li&gt;
&lt;li&gt;[ ] Do the types reduce tests you would otherwise need?&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;TypeScript is not valuable because it makes JavaScript look professional.&lt;/p&gt;

&lt;p&gt;It is valuable because it lets you encode decisions before those decisions become bugs.&lt;/p&gt;

&lt;p&gt;If your types do not protect boundaries, model the domain, or remove impossible states, you are leaving most of the value on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;InfoQ, “State of JavaScript 2025: Survey Reveals a Maturing Ecosystem with TypeScript Cementing Dominance,” published March 20, 2026: &lt;a href="https://www.infoq.com/news/2026/03/state-of-js-survey-2025/" rel="noopener noreferrer"&gt;https://www.infoq.com/news/2026/03/state-of-js-survey-2025/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syncfusion, “Frontend Development Trends 2026,” published April 28, 2026: &lt;a href="https://www.syncfusion.com/blogs/post/frontend-development-trends" rel="noopener noreferrer"&gt;https://www.syncfusion.com/blogs/post/frontend-development-trends&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Cabal for Android: My First Native Kotlin P2P Chat App Reaches Its First Early Version</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 26 May 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/cabal-for-android-my-first-native-kotlin-p2p-chat-app-reaches-its-first-early-version-1n64</link>
      <guid>https://dev.to/johnnylemonny/cabal-for-android-my-first-native-kotlin-p2p-chat-app-reaches-its-first-early-version-1n64</guid>
      <description>&lt;p&gt;A few weeks ago, I wrote about working on &lt;strong&gt;Cabal v3&lt;/strong&gt;, a modern peer-to-peer chat app for Android, and how I started by exploring a React Native direction.&lt;/p&gt;

&lt;p&gt;Later, I also wrote about how I started learning &lt;strong&gt;Kotlin&lt;/strong&gt; by building a real Android app instead of only reading tutorials.&lt;/p&gt;

&lt;p&gt;This post is the continuation of that series.&lt;/p&gt;

&lt;p&gt;Today, I’m happy to share that the first early version of &lt;strong&gt;Cabal for Android&lt;/strong&gt; is now available.&lt;/p&gt;

&lt;p&gt;GitHub repository:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/johnnylemonny/cabal-android" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny/cabal-android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An early version is also available to download from the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Cabal for Android?
&lt;/h2&gt;

&lt;p&gt;Cabal for Android is my attempt to build a modern Android client for Cabal as a native Kotlin application.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a peer-to-peer chat app for Android, built with modern native Android development in mind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This project started as an experiment, but it quickly became something more serious for me. It became a way to learn Kotlin, understand Android development better, and explore how decentralized communication can feel on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  From React Native exploration to native Kotlin
&lt;/h2&gt;

&lt;p&gt;In the first post of this series, I wrote about reviving a P2P mobile chat app idea and experimenting with Cabal on Android.&lt;/p&gt;

&lt;p&gt;At that point, I was thinking about the project from a React Native perspective.&lt;/p&gt;

&lt;p&gt;That was useful because it helped me understand the shape of the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the UI could look like,&lt;/li&gt;
&lt;li&gt;how the chat flow should feel,&lt;/li&gt;
&lt;li&gt;what kind of mobile experience I wanted,&lt;/li&gt;
&lt;li&gt;and how much work would be involved in bringing a P2P chat app back to life on Android.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But after spending more time with the project, I decided to move deeper into native Android development.&lt;/p&gt;

&lt;p&gt;That decision led me to Kotlin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kotlin?
&lt;/h2&gt;

&lt;p&gt;Kotlin had been on my list for a long time.&lt;/p&gt;

&lt;p&gt;I had read about it before. I had seen examples. I understood the basic syntax. But I never really learned it properly because I was missing one important thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a real project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cabal became that project.&lt;/p&gt;

&lt;p&gt;Instead of learning Kotlin in isolation, I learned it by building something that had real screens, real state, real problems, and real trade-offs.&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;

&lt;p&gt;Kotlin stopped being just a list of language features and became a tool I used every day to move the app forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is included in this first version?
&lt;/h2&gt;

&lt;p&gt;This is still an early version, but it is an important milestone.&lt;/p&gt;

&lt;p&gt;The app is now at the point where it feels like a real native Android project instead of just an idea or experiment.&lt;/p&gt;

&lt;p&gt;The first version focuses on the foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native Android structure,&lt;/li&gt;
&lt;li&gt;Kotlin-based implementation,&lt;/li&gt;
&lt;li&gt;a modern mobile app direction,&lt;/li&gt;
&lt;li&gt;Cabal-related chat experience,&lt;/li&gt;
&lt;li&gt;early downloadable build,&lt;/li&gt;
&lt;li&gt;and a codebase that can now evolve further.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not perfect yet.&lt;/p&gt;

&lt;p&gt;It is not “finished” in the final-product sense.&lt;/p&gt;

&lt;p&gt;But it is finished in the most important early-stage sense:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the project exists, runs, can be tested, and has a real direction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That feels like a big step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned while building it
&lt;/h2&gt;

&lt;p&gt;Building this first version taught me much more than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Real apps teach faster than tutorials
&lt;/h3&gt;

&lt;p&gt;Tutorials are useful, but they usually have clean examples and predictable problems.&lt;/p&gt;

&lt;p&gt;Real apps are different.&lt;/p&gt;

&lt;p&gt;When building Cabal for Android, I had to make decisions about structure, state, screens, naming, data flow, and how to keep the app understandable as it grew.&lt;/p&gt;

&lt;p&gt;That kind of learning is harder, but also much more valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Kotlin makes Android development feel focused
&lt;/h3&gt;

&lt;p&gt;One thing I started to appreciate is how Kotlin encourages more intentional code.&lt;/p&gt;

&lt;p&gt;Null safety, data classes, immutability, and concise syntax all helped me think more clearly about the app.&lt;/p&gt;

&lt;p&gt;At first, some parts slowed me down. But over time, those same features made the project feel safer and easier to reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Native Android development has its own rhythm
&lt;/h3&gt;

&lt;p&gt;Coming from other environments, native Android development requires a different mindset.&lt;/p&gt;

&lt;p&gt;You are not only learning Kotlin.&lt;/p&gt;

&lt;p&gt;You are also learning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android project structure,&lt;/li&gt;
&lt;li&gt;lifecycle concepts,&lt;/li&gt;
&lt;li&gt;UI patterns,&lt;/li&gt;
&lt;li&gt;state handling,&lt;/li&gt;
&lt;li&gt;Gradle,&lt;/li&gt;
&lt;li&gt;app packaging,&lt;/li&gt;
&lt;li&gt;and how all of these pieces fit together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was challenging, but also one of the best parts of the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Small milestones matter
&lt;/h3&gt;

&lt;p&gt;This version is early, but shipping it matters.&lt;/p&gt;

&lt;p&gt;It is easy to keep waiting until everything is perfect.&lt;/p&gt;

&lt;p&gt;But open-source projects grow better when they are visible early.&lt;/p&gt;

&lt;p&gt;Putting this first version out there gives the project a real starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this project matters to me
&lt;/h2&gt;

&lt;p&gt;Cabal for Android sits at the intersection of a few things I care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mobile development,&lt;/li&gt;
&lt;li&gt;Kotlin,&lt;/li&gt;
&lt;li&gt;Android,&lt;/li&gt;
&lt;li&gt;open source,&lt;/li&gt;
&lt;li&gt;peer-to-peer communication,&lt;/li&gt;
&lt;li&gt;and learning by building.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like projects that are practical but also a little experimental.&lt;/p&gt;

&lt;p&gt;A P2P chat app is exactly that kind of project.&lt;/p&gt;

&lt;p&gt;It is not just another CRUD app. It forces you to think about networking, identity, local-first experiences, mobile constraints, and how users communicate without depending entirely on centralized infrastructure.&lt;/p&gt;

&lt;p&gt;That makes it a fun and meaningful project to work on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository and download
&lt;/h2&gt;

&lt;p&gt;The project is available here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/johnnylemonny/cabal-android" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny/cabal-android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also an early downloadable version available from the repository.&lt;/p&gt;

&lt;p&gt;If you are interested in Kotlin, Android, peer-to-peer apps, or open-source mobile development, feel free to check it out.&lt;/p&gt;

&lt;p&gt;Feedback, ideas, and contributions are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next?
&lt;/h2&gt;

&lt;p&gt;This first version is only the beginning.&lt;/p&gt;

&lt;p&gt;Next, I want to continue improving the app step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;polish the UI,&lt;/li&gt;
&lt;li&gt;improve the chat experience,&lt;/li&gt;
&lt;li&gt;clean up the architecture,&lt;/li&gt;
&lt;li&gt;test more edge cases,&lt;/li&gt;
&lt;li&gt;improve stability,&lt;/li&gt;
&lt;li&gt;and continue learning Kotlin through real development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also want to keep writing about the process because documenting the journey helps me understand what I’m learning.&lt;/p&gt;

&lt;p&gt;And maybe it helps someone else too.&lt;/p&gt;

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

&lt;p&gt;This project started as an experiment.&lt;/p&gt;

&lt;p&gt;Then it became a Kotlin learning project.&lt;/p&gt;

&lt;p&gt;Now it is becoming a real native Android app.&lt;/p&gt;

&lt;p&gt;That progression has been very motivating.&lt;/p&gt;

&lt;p&gt;The biggest lesson so far is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to learn a technology deeply, build something real with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cabal for Android gave me a reason to learn Kotlin properly.&lt;/p&gt;

&lt;p&gt;And now the first early version is here.&lt;/p&gt;

&lt;p&gt;Thanks for reading — and if you try the app or look through the code, I’d love to hear what you think.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>opensource</category>
      <category>p2p</category>
    </item>
    <item>
      <title>Next.js Caching Is Hard Because You’re Thinking About It Too Late</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 21 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/nextjs-caching-is-hard-because-youre-thinking-about-it-too-late-586m</link>
      <guid>https://dev.to/johnnylemonny/nextjs-caching-is-hard-because-youre-thinking-about-it-too-late-586m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next.js caching is not hard because caching is mysterious.&lt;/p&gt;

&lt;p&gt;It is hard because teams often decide what the user should see, build the feature, ship the route, and only then ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait, should this be cached?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By that point, caching feels like a trap. Static or dynamic? Revalidate or no-store? Tag invalidation? Server Actions? Partial rendering? Why did this page update locally but not in production?&lt;/p&gt;

&lt;p&gt;The problem started earlier.&lt;/p&gt;

&lt;p&gt;Caching is not a setting. It is a product decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with freshness
&lt;/h2&gt;

&lt;p&gt;Before writing code, classify the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Always fresh        account balance, auth state, checkout price
Fresh enough        inventory count, dashboard cards, notifications
Rarely changes      marketing copy, docs navigation, pricing page layout
Versioned           blog posts, changelog entries, release notes
User-triggered      form result, optimistic mutation, saved settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each category deserves a different strategy.&lt;/p&gt;

&lt;p&gt;If you do not define freshness, the framework cannot guess your intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user does not care about your cache
&lt;/h2&gt;

&lt;p&gt;Users care about expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“I changed my profile; I should see it now.”&lt;/li&gt;
&lt;li&gt;“I published a post; the public page should update soon.”&lt;/li&gt;
&lt;li&gt;“I added an item to cart; the total should be correct.”&lt;/li&gt;
&lt;li&gt;“I opened docs; they should load instantly.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those expectations map directly to caching behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache by boundary, not by hope
&lt;/h2&gt;

&lt;p&gt;A page usually contains mixed data.&lt;/p&gt;

&lt;p&gt;Example product page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product title: rarely changes,&lt;/li&gt;
&lt;li&gt;price: may change,&lt;/li&gt;
&lt;li&gt;inventory: fresh enough,&lt;/li&gt;
&lt;li&gt;recommendations: personalized,&lt;/li&gt;
&lt;li&gt;reviews: revalidated periodically,&lt;/li&gt;
&lt;li&gt;cart state: user-specific.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you cache the entire page as one unit, one dynamic section can make the whole route harder to reason about.&lt;/p&gt;

&lt;p&gt;Instead, design boundaries:&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;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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&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;ProductShell&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;LivePurchaseBox&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;Recommendations&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;main&lt;/span&gt;&lt;span class="p"&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;Then each section can have its own freshness model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make invalidation boring
&lt;/h2&gt;

&lt;p&gt;Invalidation should be obvious from the mutation.&lt;/p&gt;

&lt;p&gt;Bad mutation:&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;await&lt;/span&gt; &lt;span class="nf"&gt;updateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better mutation:&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;await&lt;/span&gt; &lt;span class="nf"&gt;updateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&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;invalidateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;invalidateProductList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even better: centralize the policy so developers do not remember tags by hand.&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;invalidateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`product:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&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="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products:list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cache strategy nobody can remember is not a strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid accidental personalization leaks
&lt;/h2&gt;

&lt;p&gt;Caching becomes dangerous when user-specific data slips into shared output.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user names in cached navigation,&lt;/li&gt;
&lt;li&gt;role-specific actions in shared HTML,&lt;/li&gt;
&lt;li&gt;personalized recommendations in static shells,&lt;/li&gt;
&lt;li&gt;auth-only pricing mixed with public product data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple rule helps:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Public data can be shared. User data needs a user boundary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a component depends on cookies, headers, session, or permissions, treat it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use loading states intentionally
&lt;/h2&gt;

&lt;p&gt;Partial rendering and streaming are powerful because they let you show stable UI while dynamic sections load.&lt;/p&gt;

&lt;p&gt;But a loading skeleton is not a substitute for product thinking.&lt;/p&gt;

&lt;p&gt;Good skeleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preserves layout,&lt;/li&gt;
&lt;li&gt;communicates progress,&lt;/li&gt;
&lt;li&gt;appears only where delay is expected,&lt;/li&gt;
&lt;li&gt;does not cause content to jump.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad skeleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flashes for 80ms,&lt;/li&gt;
&lt;li&gt;shifts the layout,&lt;/li&gt;
&lt;li&gt;appears everywhere,&lt;/li&gt;
&lt;li&gt;hides the fact that the data model is too slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debugging questions
&lt;/h2&gt;

&lt;p&gt;When a Next.js route behaves strangely, I use this checklist:&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="gu"&gt;## Caching debug checklist&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Is this route expected to be static, dynamic, or mixed?
&lt;span class="p"&gt;-&lt;/span&gt; Which data must be fresh for every request?
&lt;span class="p"&gt;-&lt;/span&gt; Which data can be cached safely?
&lt;span class="p"&gt;-&lt;/span&gt; Is any user-specific data crossing a shared cache boundary?
&lt;span class="p"&gt;-&lt;/span&gt; What event invalidates this data?
&lt;span class="p"&gt;-&lt;/span&gt; Does the mutation revalidate the exact thing the user expects?
&lt;span class="p"&gt;-&lt;/span&gt; Is the loading state masking an architecture problem?
&lt;span class="p"&gt;-&lt;/span&gt; Can the behavior be explained in one paragraph?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last question matters. If nobody on the team can explain the caching behavior simply, production will explain it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;Think of caching as a conversation between three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data truth&lt;/strong&gt; — where the correct data lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User expectation&lt;/strong&gt; — when the user expects to see changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering boundary&lt;/strong&gt; — where the UI can safely reuse work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When those three agree, caching feels boring.&lt;/p&gt;

&lt;p&gt;When they disagree, caching feels haunted.&lt;/p&gt;

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

&lt;p&gt;Next.js caching is not something to sprinkle on a finished route.&lt;/p&gt;

&lt;p&gt;It belongs in the first design conversation.&lt;/p&gt;

&lt;p&gt;Decide freshness early. Draw boundaries early. Name invalidation early.&lt;/p&gt;

&lt;p&gt;Your future self will spend less time asking why the page is stale and more time building things users actually notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js, “Next.js 16,” published October 21, 2025: &lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;https://nextjs.org/blog/next-16&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;InfoWorld, “Next.js 16 features explicit caching, AI-powered debugging,” published October 24, 2025: &lt;a href="https://www.infoworld.com/article/4078213/next-js-16-features-explicit-caching-ai-powered-debugging.html" rel="noopener noreferrer"&gt;https://www.infoworld.com/article/4078213/next-js-16-features-explicit-caching-ai-powered-debugging.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>React Is Not the Problem — Your Client-Side Mental Model Is</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 19 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/react-is-not-the-problem-your-client-side-mental-model-is-gcg</link>
      <guid>https://dev.to/johnnylemonny/react-is-not-the-problem-your-client-side-mental-model-is-gcg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React gets blamed for a lot of slow websites.&lt;/p&gt;

&lt;p&gt;Sometimes that is fair. A careless React app can ship too much JavaScript, hydrate too much UI, and re-render too often.&lt;/p&gt;

&lt;p&gt;But React is rarely the whole problem. The deeper problem is a mental model that says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If something appears on the screen, it must be a client-side component.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That assumption made sense for many single-page apps. It makes less sense for much of the web we are building now.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better default
&lt;/h2&gt;

&lt;p&gt;Try this default instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Render as much as possible before the browser receives the page. Hydrate only what must be interactive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This does not mean every product should be static. It means interactivity should be intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three kinds of UI
&lt;/h2&gt;

&lt;p&gt;Most screens contain three different kinds of UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Content UI
&lt;/h3&gt;

&lt;p&gt;This is information the user reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product names,&lt;/li&gt;
&lt;li&gt;documentation,&lt;/li&gt;
&lt;li&gt;blog content,&lt;/li&gt;
&lt;li&gt;pricing details,&lt;/li&gt;
&lt;li&gt;profile summaries,&lt;/li&gt;
&lt;li&gt;search results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This usually does not need client-side state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Navigation UI
&lt;/h3&gt;

&lt;p&gt;This helps the user move:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;menus,&lt;/li&gt;
&lt;li&gt;tabs,&lt;/li&gt;
&lt;li&gt;breadcrumbs,&lt;/li&gt;
&lt;li&gt;pagination,&lt;/li&gt;
&lt;li&gt;links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of it needs JavaScript. Much of it does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Interaction UI
&lt;/h3&gt;

&lt;p&gt;This changes based on user action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;forms,&lt;/li&gt;
&lt;li&gt;filters,&lt;/li&gt;
&lt;li&gt;carts,&lt;/li&gt;
&lt;li&gt;editors,&lt;/li&gt;
&lt;li&gt;dashboards,&lt;/li&gt;
&lt;li&gt;command palettes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where client-side React shines.&lt;/p&gt;

&lt;p&gt;The mistake is treating all three categories the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The client component tax
&lt;/h2&gt;

&lt;p&gt;A client component costs more than its source file.&lt;/p&gt;

&lt;p&gt;It can pull in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependencies,&lt;/li&gt;
&lt;li&gt;state management,&lt;/li&gt;
&lt;li&gt;event handlers,&lt;/li&gt;
&lt;li&gt;hydration work,&lt;/li&gt;
&lt;li&gt;serialization boundaries,&lt;/li&gt;
&lt;li&gt;re-render behavior,&lt;/li&gt;
&lt;li&gt;test complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So do not ask, “Can this be a client component?”&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What user interaction requires this to be a client component?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you cannot name the interaction, the component is probably in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep islands small
&lt;/h2&gt;

&lt;p&gt;A common pattern is to make the whole layout interactive because one child needs state.&lt;/p&gt;

&lt;p&gt;Avoid this:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;product&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;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setQuantity&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&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;ProductHero&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;ProductDescription&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;QuantityPicker&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setQuantity&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;main&lt;/span&gt;&lt;span class="p"&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;Prefer this:&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;product&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&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;ProductHero&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;ProductDescription&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;QuantityPicker&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;main&lt;/span&gt;&lt;span class="p"&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;Now the interactive island is the picker, not the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop fetching twice
&lt;/h2&gt;

&lt;p&gt;Another slow pattern is server rendering a page and then immediately fetching the same data again in the browser.&lt;/p&gt;

&lt;p&gt;That creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extra network requests,&lt;/li&gt;
&lt;li&gt;loading flicker,&lt;/li&gt;
&lt;li&gt;duplicated caching logic,&lt;/li&gt;
&lt;li&gt;inconsistent error states.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the server already has the data required for first render, use it. Fetch in the browser when the user’s interaction creates new information needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memoization is not architecture
&lt;/h2&gt;

&lt;p&gt;React developers sometimes reach for &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and memoized components when the real issue is that too much state lives too high in the tree.&lt;/p&gt;

&lt;p&gt;Before memoizing, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can this state move closer to the component that uses it?&lt;/li&gt;
&lt;li&gt;Can this work happen on the server?&lt;/li&gt;
&lt;li&gt;Can this derived value be computed once before render?&lt;/li&gt;
&lt;li&gt;Can this component stop receiving unstable props?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memoization can help. It cannot fix a confused ownership model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision checklist
&lt;/h2&gt;

&lt;p&gt;For every component, ask:&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="gu"&gt;## Component placement review&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Does it need browser-only APIs?
&lt;span class="p"&gt;-&lt;/span&gt; Does it respond to user input before navigation?
&lt;span class="p"&gt;-&lt;/span&gt; Does it manage local interactive state?
&lt;span class="p"&gt;-&lt;/span&gt; Does it need real-time updates?
&lt;span class="p"&gt;-&lt;/span&gt; Does it depend on viewport measurements?
&lt;span class="p"&gt;-&lt;/span&gt; Could it render on the server and pass data to a smaller client child?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If most answers are “no,” keep it server-rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  React is still useful
&lt;/h2&gt;

&lt;p&gt;The point is not to write less React because React is bad.&lt;/p&gt;

&lt;p&gt;The point is to use React where it creates value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rich forms,&lt;/li&gt;
&lt;li&gt;optimistic updates,&lt;/li&gt;
&lt;li&gt;complex stateful widgets,&lt;/li&gt;
&lt;li&gt;collaborative UI,&lt;/li&gt;
&lt;li&gt;dashboards,&lt;/li&gt;
&lt;li&gt;editors,&lt;/li&gt;
&lt;li&gt;design systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But rendering a heading, a paragraph, and a price card does not require a client-side mental model.&lt;/p&gt;

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

&lt;p&gt;React did not make your app slow by itself.&lt;/p&gt;

&lt;p&gt;Your architecture made the browser responsible for work the browser did not need to do.&lt;/p&gt;

&lt;p&gt;Once you start treating client-side JavaScript as a budget instead of a default, React becomes easier to use well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;InfoQ, “State of JavaScript 2025: Survey Reveals a Maturing Ecosystem with TypeScript Cementing Dominance,” published March 20, 2026: &lt;a href="https://www.infoq.com/news/2026/03/state-of-js-survey-2025/" rel="noopener noreferrer"&gt;https://www.infoq.com/news/2026/03/state-of-js-survey-2025/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js, “Next.js 16,” published October 21, 2025: &lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;https://nextjs.org/blog/next-16&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Stop Shipping 700KB of JavaScript for a Button</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 14 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/stop-shipping-700kb-of-javascript-for-a-button-3cnc</link>
      <guid>https://dev.to/johnnylemonny/stop-shipping-700kb-of-javascript-for-a-button-3cnc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Modern Web Development in 2026&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a strange kind of web performance problem that does not look like a bug.&lt;/p&gt;

&lt;p&gt;The page loads. The button appears. The design looks fine. The framework did its job. Lighthouse is not screaming loudly enough to block the release.&lt;/p&gt;

&lt;p&gt;Then a real user taps the button on a mid-range phone while the main thread is busy doing work that should never have been shipped to the browser in the first place.&lt;/p&gt;

&lt;p&gt;That is when the interface stops feeling like software and starts feeling like a negotiation.&lt;/p&gt;

&lt;p&gt;This article is not an anti-JavaScript rant. JavaScript is one of the reasons the web is as capable as it is. The problem is simpler: &lt;strong&gt;we keep shipping client-side work that does not need to be client-side work&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable question
&lt;/h2&gt;

&lt;p&gt;Before optimizing a component, ask this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the browser need this code to make the first interaction useful?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;move it to the server,&lt;/li&gt;
&lt;li&gt;delay it,&lt;/li&gt;
&lt;li&gt;delete it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most performance wins come from that boring list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common failure mode
&lt;/h2&gt;

&lt;p&gt;A button starts as a button.&lt;/p&gt;

&lt;p&gt;Then it becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a component library import,&lt;/li&gt;
&lt;li&gt;a theme provider dependency,&lt;/li&gt;
&lt;li&gt;an analytics wrapper,&lt;/li&gt;
&lt;li&gt;an icon package import,&lt;/li&gt;
&lt;li&gt;a tooltip,&lt;/li&gt;
&lt;li&gt;an animation primitive,&lt;/li&gt;
&lt;li&gt;a client component boundary,&lt;/li&gt;
&lt;li&gt;a hydration root,&lt;/li&gt;
&lt;li&gt;and a tiny state machine nobody asked for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user sees one button. The browser receives a small town.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget the page before you polish it
&lt;/h2&gt;

&lt;p&gt;A performance budget is not a punishment. It is a design constraint.&lt;/p&gt;

&lt;p&gt;Use a simple budget before arguing about micro-optimizations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial JavaScript:      under 170 KB compressed
Route-level JS:          under 80 KB compressed
Third-party scripts:     justified one by one
Critical CSS:            small enough to inline when useful
Images above the fold:   explicit dimensions and optimized format
Hydration islands:       only where interaction is required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your exact numbers can differ. The important part is that the budget exists before the page is already slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the bundle, not the vibes
&lt;/h2&gt;

&lt;p&gt;Run a bundle analyzer and look for the boring surprises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npx source-map-explorer &lt;span class="s2"&gt;"dist/**/*.js"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the analyzer that fits your stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Next.js example&lt;/span&gt;
&lt;span class="nv"&gt;ANALYZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is this package in the initial route?&lt;/li&gt;
&lt;li&gt;Is this library used for one function?&lt;/li&gt;
&lt;li&gt;Did an icon import pull in more than one icon?&lt;/li&gt;
&lt;li&gt;Is a date library doing work that &lt;code&gt;Intl&lt;/code&gt; can do?&lt;/li&gt;
&lt;li&gt;Is a chart library needed before the user scrolls to the chart?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bundle analysis is humbling because it replaces opinions with weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be suspicious of client boundaries
&lt;/h2&gt;

&lt;p&gt;In server-first frameworks, the most expensive line in a file may be this one:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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;That line is not bad. It is a contract.&lt;/p&gt;

&lt;p&gt;It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This component and the code it pulls in must be available to the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So keep client components narrow:&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;// Good boundary: only the interactive part is client-side.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;product&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&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;ProductSummary&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;AddToCartButton&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;ProductDetails&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&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;main&lt;/span&gt;&lt;span class="p"&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;The button can be interactive. The whole page does not need to hydrate because one button exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delay what is not needed yet
&lt;/h2&gt;

&lt;p&gt;Some code is useful, just not immediately.&lt;/p&gt;

&lt;p&gt;Good candidates for lazy loading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;charts below the fold,&lt;/li&gt;
&lt;li&gt;markdown editors,&lt;/li&gt;
&lt;li&gt;maps,&lt;/li&gt;
&lt;li&gt;video players,&lt;/li&gt;
&lt;li&gt;complex filters,&lt;/li&gt;
&lt;li&gt;admin-only panels,&lt;/li&gt;
&lt;li&gt;onboarding tours,&lt;/li&gt;
&lt;li&gt;non-critical animations.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HeavyChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HeavyChart&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;loading&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ChartSkeleton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ssr&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this carefully. Lazy loading is not a magic spell. It moves cost. That is useful only when the moved cost no longer blocks the first useful interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third-party scripts are product decisions
&lt;/h2&gt;

&lt;p&gt;Performance often dies by a thousand approved vendors.&lt;/p&gt;

&lt;p&gt;Create a small script review:&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="gu"&gt;### Third-party script review&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; What user or business outcome does this script support?
&lt;span class="p"&gt;-&lt;/span&gt; Does it run on every route?
&lt;span class="p"&gt;-&lt;/span&gt; Can it load after interaction or consent?
&lt;span class="p"&gt;-&lt;/span&gt; Who owns it?
&lt;span class="p"&gt;-&lt;/span&gt; How do we remove it?
&lt;span class="p"&gt;-&lt;/span&gt; What is the fallback if it fails?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nobody owns a third-party script, the browser owns the consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize for interaction, not screenshots
&lt;/h2&gt;

&lt;p&gt;A page that appears quickly but cannot respond is not fast.&lt;/p&gt;

&lt;p&gt;Look for long tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;list&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;for &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;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Long task:&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;duration&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="nf"&gt;observe&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;longtask&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;buffered&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then trace what blocks the main thread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hydration,&lt;/li&gt;
&lt;li&gt;large JSON parsing,&lt;/li&gt;
&lt;li&gt;expensive rendering,&lt;/li&gt;
&lt;li&gt;synchronous storage access,&lt;/li&gt;
&lt;li&gt;client-side sorting/filtering of large lists,&lt;/li&gt;
&lt;li&gt;heavy analytics startup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser is not a server. Stop treating it like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping a new route, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is the initial JavaScript budget visible in CI?&lt;/li&gt;
&lt;li&gt;[ ] Are client components as small as possible?&lt;/li&gt;
&lt;li&gt;[ ] Is non-critical UI lazy loaded?&lt;/li&gt;
&lt;li&gt;[ ] Are third-party scripts justified?&lt;/li&gt;
&lt;li&gt;[ ] Are images sized and optimized?&lt;/li&gt;
&lt;li&gt;[ ] Is the main thread free before the first important interaction?&lt;/li&gt;
&lt;li&gt;[ ] Did you test on a slower device or throttled profile?&lt;/li&gt;
&lt;li&gt;[ ] Can the page still be useful if enhancements arrive late?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The boring truth
&lt;/h2&gt;

&lt;p&gt;Most users do not care which framework you used.&lt;/p&gt;

&lt;p&gt;They care that the page loads, responds, and gets out of their way.&lt;/p&gt;

&lt;p&gt;The fastest JavaScript is the JavaScript you never send. The second fastest is the JavaScript you send later. The third fastest is the JavaScript you actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HTTP Archive, “Performance | 2025 Web Almanac,” published January 15, 2026: &lt;a href="https://almanac.httparchive.org/en/2025/performance" rel="noopener noreferrer"&gt;https://almanac.httparchive.org/en/2025/performance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CSS-Tricks, “HTTP Archive 2025 Web Almanac,” published January 16, 2026: &lt;a href="https://css-tricks.com/http-archive-2025-web-almanac/" rel="noopener noreferrer"&gt;https://css-tricks.com/http-archive-2025-web-almanac/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;You can find me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;https://github.com/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DEV: &lt;a href="https://dev.to/johnnylemonny"&gt;https://dev.to/johnnylemonny&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>React devs: rethink useEffect! Treat it as sync with external systems, not lifecycle replacement. Avoid hidden state-machine traps &amp; keep your code clean. #webdev #react</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Fri, 08 May 2026 17:06:29 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/react-devs-rethink-useeffect-treat-it-as-sync-with-external-systems-not-lifecycle-replacement-1f0j</link>
      <guid>https://dev.to/johnnylemonny/react-devs-rethink-useeffect-treat-it-as-sync-with-external-systems-not-lifecycle-replacement-1f0j</guid>
      <description></description>
      <category>frontend</category>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The AI Code Review Checklist I Use Before Merging Any TypeScript PR</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Thu, 30 Apr 2026 10:40:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/the-ai-code-review-checklist-i-use-before-merging-any-typescript-pr-fc0</link>
      <guid>https://dev.to/johnnylemonny/the-ai-code-review-checklist-i-use-before-merging-any-typescript-pr-fc0</guid>
      <description>&lt;p&gt;AI can write code quickly.&lt;/p&gt;

&lt;p&gt;That is no longer the interesting part.&lt;/p&gt;

&lt;p&gt;The interesting part is what happens &lt;strong&gt;after&lt;/strong&gt; the code is generated.&lt;/p&gt;

&lt;p&gt;Because in real projects, the bottleneck is rarely “how do we get code faster?”&lt;br&gt;
It is usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this correct?&lt;/li&gt;
&lt;li&gt;Does it match the architecture?&lt;/li&gt;
&lt;li&gt;Does it handle edge cases?&lt;/li&gt;
&lt;li&gt;Is it safe to merge?&lt;/li&gt;
&lt;li&gt;Can someone else maintain it next month?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I stopped treating AI output like finished code.&lt;/p&gt;

&lt;p&gt;Now I treat it like a draft that needs a reliable review system.&lt;/p&gt;

&lt;p&gt;This is the checklist I use before I merge any AI-generated TypeScript PR.&lt;/p&gt;

&lt;p&gt;Not a theoretical checklist.&lt;br&gt;
A practical one.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Can I explain the change in one sentence?
&lt;/h2&gt;

&lt;p&gt;Before I look at the code, I force myself to summarize the PR in one sentence.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This change validates uploaded image metadata before saving records to the database.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I cannot describe the change clearly, one of two things is probably true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the PR is doing too much&lt;/li&gt;
&lt;li&gt;the code is hiding the real intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI often produces code that &lt;em&gt;looks&lt;/em&gt; organized while actually mixing multiple concerns.&lt;/p&gt;

&lt;p&gt;If the purpose is fuzzy, I stop there and split the change.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Does the code match the requested scope?
&lt;/h2&gt;

&lt;p&gt;AI loves to be helpful.&lt;/p&gt;

&lt;p&gt;Sometimes too helpful.&lt;/p&gt;

&lt;p&gt;It will often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rename unrelated variables&lt;/li&gt;
&lt;li&gt;refactor nearby code&lt;/li&gt;
&lt;li&gt;introduce “small improvements”&lt;/li&gt;
&lt;li&gt;create utility functions nobody asked for&lt;/li&gt;
&lt;li&gt;solve adjacent problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes review harder.&lt;/p&gt;

&lt;p&gt;So one of my first checks is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did the model change only what needed to change?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a PR was supposed to fix one validation bug but now touches eight files, I get suspicious immediately.&lt;/p&gt;

&lt;p&gt;A good AI-generated PR is usually narrower than you think.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Are the boundaries explicit?
&lt;/h2&gt;

&lt;p&gt;For TypeScript projects, this is one of the biggest signals of quality.&lt;/p&gt;

&lt;p&gt;I look for clear boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input types&lt;/li&gt;
&lt;li&gt;output types&lt;/li&gt;
&lt;li&gt;domain models&lt;/li&gt;
&lt;li&gt;DTOs&lt;/li&gt;
&lt;li&gt;API contracts&lt;/li&gt;
&lt;li&gt;validation layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI-generated code is much easier to trust when the edges are visible.&lt;/p&gt;

&lt;p&gt;Bad sign:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveUser&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CreateUserInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;email&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="nl"&gt;displayName&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="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CreateUserResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&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="nl"&gt;email&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="nl"&gt;displayName&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="p"&gt;}&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;saveUser&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="nx"&gt;CreateUserInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreateUserResult&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;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the AI patch adds logic without tightening the contract, I usually improve the boundary before I approve the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Did the AI generate code, or did it generate a new abstraction?
&lt;/h2&gt;

&lt;p&gt;This matters a lot.&lt;/p&gt;

&lt;p&gt;Sometimes AI gives you useful implementation.&lt;br&gt;
Sometimes it gives you a brand new architecture you did not ask for.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extra layers&lt;/li&gt;
&lt;li&gt;generic helpers&lt;/li&gt;
&lt;li&gt;“reusable” wrappers&lt;/li&gt;
&lt;li&gt;configuration systems&lt;/li&gt;
&lt;li&gt;class hierarchies for simple logic&lt;/li&gt;
&lt;li&gt;abstraction before repetition actually exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful question here is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would I still create this abstraction if a human teammate had not suggested it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, I remove it.&lt;/p&gt;

&lt;p&gt;AI-generated code often becomes bloated not because it is broken, but because it is too eager to generalize.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Is there a test for the thing that actually changed?
&lt;/h2&gt;

&lt;p&gt;I do not just ask, “Are there tests?”&lt;/p&gt;

&lt;p&gt;I ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a test for the exact behavior this PR claims to fix or add?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one test for the happy path&lt;/li&gt;
&lt;li&gt;one test for the expected failure mode&lt;/li&gt;
&lt;li&gt;one test for the edge case that is easiest to miss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the PR fixes a parsing bug, I want a parsing test.&lt;br&gt;
If it changes authorization logic, I want an authorization test.&lt;br&gt;
If it adds fallback behavior, I want a test that proves the fallback works.&lt;/p&gt;

&lt;p&gt;AI often writes tests that mirror the implementation too closely.&lt;/p&gt;

&lt;p&gt;So I look for tests that verify behavior, not just structure.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Does the code fail safely?
&lt;/h2&gt;

&lt;p&gt;A surprising amount of AI-generated code handles success better than failure.&lt;/p&gt;

&lt;p&gt;So I explicitly review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing values&lt;/li&gt;
&lt;li&gt;invalid input&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;third-party failures&lt;/li&gt;
&lt;li&gt;null and undefined cases&lt;/li&gt;
&lt;li&gt;partial success scenarios&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;user-facing error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ask myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if this dependency is down?&lt;/li&gt;
&lt;li&gt;What happens if the payload shape changes?&lt;/li&gt;
&lt;li&gt;What happens if this field is missing?&lt;/li&gt;
&lt;li&gt;What happens if the operation succeeds halfway?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is “the app probably throws something weird,” the PR is not ready.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Are runtime checks present at the system edges?
&lt;/h2&gt;

&lt;p&gt;TypeScript is great, but it does not validate runtime data by itself.&lt;/p&gt;

&lt;p&gt;So any AI-generated code that touches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request bodies&lt;/li&gt;
&lt;li&gt;query parameters&lt;/li&gt;
&lt;li&gt;local storage&lt;/li&gt;
&lt;li&gt;database results&lt;/li&gt;
&lt;li&gt;webhooks&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;third-party APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;should make me ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the runtime validation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Types help inside the codebase.&lt;br&gt;
Validation protects the boundary.&lt;/p&gt;

&lt;p&gt;For 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;z&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;zod&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;CreatePostSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;([]),&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;CreatePostInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&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;CreatePostSchema&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;function&lt;/span&gt; &lt;span class="nf"&gt;parseCreatePostInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;CreatePostInput&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;CreatePostSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If AI writes strongly typed code without validating incoming data, it creates a false sense of safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Are names better after the change — or worse?
&lt;/h2&gt;

&lt;p&gt;AI can produce valid code with terrible naming.&lt;/p&gt;

&lt;p&gt;And bad names are expensive because they survive code review surprisingly often.&lt;/p&gt;

&lt;p&gt;So I check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;function names&lt;/li&gt;
&lt;li&gt;variable names&lt;/li&gt;
&lt;li&gt;type names&lt;/li&gt;
&lt;li&gt;file names&lt;/li&gt;
&lt;li&gt;booleans&lt;/li&gt;
&lt;li&gt;enum values&lt;/li&gt;
&lt;li&gt;error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want names that reflect the domain, not the implementation trick.&lt;/p&gt;

&lt;p&gt;Bad:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataProcessorManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoiceRetryScheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRetryScheduler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When naming gets vague, maintainability drops fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Does the PR introduce duplicate logic?
&lt;/h2&gt;

&lt;p&gt;AI often rewrites something that already exists somewhere else in the codebase.&lt;/p&gt;

&lt;p&gt;That creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;near-duplicate validators&lt;/li&gt;
&lt;li&gt;inconsistent helpers&lt;/li&gt;
&lt;li&gt;slightly different parsing functions&lt;/li&gt;
&lt;li&gt;multiple ways to do the same thing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I always scan for duplication before approving.&lt;/p&gt;

&lt;p&gt;My rule is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the logic already exists, reuse it&lt;/li&gt;
&lt;li&gt;if the existing abstraction is bad, improve it&lt;/li&gt;
&lt;li&gt;do not allow AI to create parallel versions of the same idea&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Duplicate code is especially dangerous when it looks clean.&lt;br&gt;
It feels harmless at first and becomes expensive later.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Would I be comfortable debugging this at 2 AM?
&lt;/h2&gt;

&lt;p&gt;This is one of my favorite review questions.&lt;/p&gt;

&lt;p&gt;Because code can be technically correct and still be operationally terrible.&lt;/p&gt;

&lt;p&gt;I look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;meaningful logs&lt;/li&gt;
&lt;li&gt;useful error messages&lt;/li&gt;
&lt;li&gt;predictable branching&lt;/li&gt;
&lt;li&gt;obvious control flow&lt;/li&gt;
&lt;li&gt;easy-to-trace data transformations&lt;/li&gt;
&lt;li&gt;no “magic” hidden in helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If production breaks, will this code help the next person understand what happened?&lt;/p&gt;

&lt;p&gt;Or will it force them to reverse-engineer AI-generated cleverness under pressure?&lt;/p&gt;

&lt;p&gt;If debugging would be painful, I simplify the code before merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Is the security model still intact?
&lt;/h2&gt;

&lt;p&gt;Any AI-generated PR that touches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;tokens&lt;/li&gt;
&lt;li&gt;cookies&lt;/li&gt;
&lt;li&gt;headers&lt;/li&gt;
&lt;li&gt;uploads&lt;/li&gt;
&lt;li&gt;database access&lt;/li&gt;
&lt;li&gt;redirects&lt;/li&gt;
&lt;li&gt;HTML rendering&lt;/li&gt;
&lt;li&gt;shell commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gets a slower review.&lt;/p&gt;

&lt;p&gt;I specifically check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authorization, not just authentication&lt;/li&gt;
&lt;li&gt;input handling&lt;/li&gt;
&lt;li&gt;secret leakage&lt;/li&gt;
&lt;li&gt;unsafe defaults&lt;/li&gt;
&lt;li&gt;overly broad permissions&lt;/li&gt;
&lt;li&gt;accidental exposure of internal fields&lt;/li&gt;
&lt;li&gt;client/server boundary mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not trust “looks secure.”&lt;/p&gt;

&lt;p&gt;I want the security assumptions to be obvious in the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Can I roll this back easily?
&lt;/h2&gt;

&lt;p&gt;This final check is underrated.&lt;/p&gt;

&lt;p&gt;Even a good change can fail in production.&lt;/p&gt;

&lt;p&gt;So before merging I ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is the change isolated?&lt;/li&gt;
&lt;li&gt;is it behind a flag?&lt;/li&gt;
&lt;li&gt;can it be reverted cleanly?&lt;/li&gt;
&lt;li&gt;does it change data shape or persistence behavior?&lt;/li&gt;
&lt;li&gt;does it create migration risk?&lt;/li&gt;
&lt;li&gt;does it depend on coordinated deployment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI makes it easy to create larger patches than necessary.&lt;br&gt;
Rollback thinking forces the patch back into a safer shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  My quick merge rubric
&lt;/h2&gt;

&lt;p&gt;If I need a fast decision, I use this simple rubric.&lt;/p&gt;

&lt;h3&gt;
  
  
  I merge when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;the scope is narrow&lt;/li&gt;
&lt;li&gt;the intent is obvious&lt;/li&gt;
&lt;li&gt;the boundaries are typed&lt;/li&gt;
&lt;li&gt;runtime input is validated&lt;/li&gt;
&lt;li&gt;tests prove the claimed behavior&lt;/li&gt;
&lt;li&gt;failure paths are handled&lt;/li&gt;
&lt;li&gt;naming is clear&lt;/li&gt;
&lt;li&gt;security assumptions are visible&lt;/li&gt;
&lt;li&gt;rollback is simple&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  I request changes when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;the PR does more than requested&lt;/li&gt;
&lt;li&gt;the code adds unnecessary abstractions&lt;/li&gt;
&lt;li&gt;tests are shallow&lt;/li&gt;
&lt;li&gt;boundary validation is missing&lt;/li&gt;
&lt;li&gt;names are vague&lt;/li&gt;
&lt;li&gt;duplicate logic appears&lt;/li&gt;
&lt;li&gt;debugging would be painful&lt;/li&gt;
&lt;li&gt;the operational or security story is unclear&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;AI can absolutely make teams faster.&lt;/p&gt;

&lt;p&gt;But speed only matters if the output is reviewable, understandable, and safe to ship.&lt;/p&gt;

&lt;p&gt;That is why I do not ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Did AI write this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Would I still approve this if I had to own it in production?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single question has improved my reviews more than any tool setting.&lt;/p&gt;

&lt;p&gt;If you are using AI heavily in your TypeScript workflow, build a checklist like this one.&lt;/p&gt;

&lt;p&gt;It does not have to be identical.&lt;/p&gt;

&lt;p&gt;It just has to be consistent.&lt;/p&gt;

&lt;p&gt;Because the real productivity gain is not generated code.&lt;/p&gt;

&lt;p&gt;It is generated code that survives review without creating future pain.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I recommend this!</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Tue, 28 Apr 2026 16:37:34 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/i-recommend-this-1bo7</link>
      <guid>https://dev.to/johnnylemonny/i-recommend-this-1bo7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie" class="crayons-story__hidden-navigation-link"&gt;15 Essential Sections Every README Needs: Give Your Project What It Deserves&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Ready-to-use markdown template&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/georgekobaidze" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F55651%2F3ad144e9-ca91-4395-b73a-9a0a3d843af9.jpg" alt="georgekobaidze profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/georgekobaidze" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Giorgi Kobaidze
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Giorgi Kobaidze
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3553477" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/georgekobaidze" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F55651%2F3ad144e9-ca91-4395-b73a-9a0a3d843af9.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Giorgi Kobaidze&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie" id="article-link-3553477"&gt;
          15 Essential Sections Every README Needs: Give Your Project What It Deserves
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/documentation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;documentation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/development"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;development&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;161&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/georgekobaidze/15-essential-sections-every-readme-needs-give-your-project-what-it-deserves-fie#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              80&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How I Started Learning Kotlin by Building a Real Android App</title>
      <dc:creator>𝗝𝗼𝗵𝗻</dc:creator>
      <pubDate>Wed, 22 Apr 2026 09:20:00 +0000</pubDate>
      <link>https://dev.to/johnnylemonny/how-i-started-learning-kotlin-by-building-a-real-android-app-32ic</link>
      <guid>https://dev.to/johnnylemonny/how-i-started-learning-kotlin-by-building-a-real-android-app-32ic</guid>
      <description>&lt;p&gt;For a long time, Kotlin was one of those technologies I kept meaning to learn “properly.”&lt;/p&gt;

&lt;p&gt;You probably know the feeling.&lt;/p&gt;

&lt;p&gt;You read a few examples.&lt;br&gt;&lt;br&gt;
You understand the syntax.&lt;br&gt;&lt;br&gt;
You save a few tutorials.&lt;br&gt;&lt;br&gt;
You tell yourself you’ll build something with it “soon.”&lt;/p&gt;

&lt;p&gt;And then nothing happens.&lt;/p&gt;

&lt;p&gt;That was me for a while.&lt;/p&gt;

&lt;p&gt;The problem wasn’t that Kotlin looked hard.&lt;br&gt;&lt;br&gt;
The problem was that learning it in isolation felt abstract.&lt;/p&gt;

&lt;p&gt;I could read about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;null safety,&lt;/li&gt;
&lt;li&gt;data classes,&lt;/li&gt;
&lt;li&gt;coroutines,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;when&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;extension functions,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;but none of it really &lt;em&gt;stuck&lt;/em&gt; until I had a reason to use it inside a real Android app.&lt;/p&gt;

&lt;p&gt;That’s when things changed.&lt;/p&gt;

&lt;p&gt;Instead of trying to “finish a Kotlin course,” I decided to learn Kotlin by building something real — small enough to complete, but real enough to force me to understand how Android development actually works.&lt;/p&gt;

&lt;p&gt;This post is about what that approach taught me, what slowed me down, and what I’d focus on first if I started learning Kotlin again today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I chose to learn Kotlin by building instead of studying longer
&lt;/h2&gt;

&lt;p&gt;At some point I realized I was consuming too much “learning material” and not enough actual friction.&lt;/p&gt;

&lt;p&gt;And friction is where real learning starts.&lt;/p&gt;

&lt;p&gt;It is easy to feel productive when you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading docs,&lt;/li&gt;
&lt;li&gt;watching tutorials,&lt;/li&gt;
&lt;li&gt;highlighting syntax,&lt;/li&gt;
&lt;li&gt;copying code examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But you don’t really understand a language until you hit questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How should I structure this screen?&lt;/li&gt;
&lt;li&gt;Where should this logic live?&lt;/li&gt;
&lt;li&gt;Why is this nullable?&lt;/li&gt;
&lt;li&gt;Why does this state not update the way I expected?&lt;/li&gt;
&lt;li&gt;What is the most Kotlin-like way to write this instead of just translating Java or JavaScript habits?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building an app forces those questions to happen naturally.&lt;/p&gt;

&lt;p&gt;It also gives you a much better feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write code,&lt;/li&gt;
&lt;li&gt;break something,&lt;/li&gt;
&lt;li&gt;fix it,&lt;/li&gt;
&lt;li&gt;understand one concept more deeply,&lt;/li&gt;
&lt;li&gt;repeat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That loop taught me more than another week of passive learning would have.&lt;/p&gt;




&lt;h2&gt;
  
  
  The kind of app I built
&lt;/h2&gt;

&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; start with a big idea.&lt;/p&gt;

&lt;p&gt;I didn’t try to build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a full social app,&lt;/li&gt;
&lt;li&gt;an e-commerce platform,&lt;/li&gt;
&lt;li&gt;a chat system,&lt;/li&gt;
&lt;li&gt;or a feature-packed productivity tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That would have been a mistake.&lt;/p&gt;

&lt;p&gt;Instead, I picked something small and realistic:&lt;br&gt;
a simple Android app with a few clear screens, local state, user input, and enough structure to feel like a real product.&lt;/p&gt;

&lt;p&gt;That mattered a lot.&lt;/p&gt;

&lt;p&gt;A good beginner project should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;small enough to finish,&lt;/li&gt;
&lt;li&gt;useful enough to stay motivating,&lt;/li&gt;
&lt;li&gt;and complex enough to teach real patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to impress people with scope.&lt;/p&gt;

&lt;p&gt;The goal is to create a project that forces you to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kotlin syntax in real conditions,&lt;/li&gt;
&lt;li&gt;Android UI structure,&lt;/li&gt;
&lt;li&gt;state handling,&lt;/li&gt;
&lt;li&gt;user interactions,&lt;/li&gt;
&lt;li&gt;navigation,&lt;/li&gt;
&lt;li&gt;and a bit of architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where the language starts making sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  What helped me most when learning Kotlin
&lt;/h2&gt;

&lt;p&gt;A few things made a huge difference early on.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Writing Kotlin every day, even in small amounts
&lt;/h3&gt;

&lt;p&gt;I learned very quickly that consistency mattered more than long study sessions.&lt;/p&gt;

&lt;p&gt;A focused 30–45 minutes of building something real helped me much more than occasionally spending 4 hours jumping between tutorials.&lt;/p&gt;

&lt;p&gt;Kotlin started to feel natural only after repeated exposure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;writing functions,&lt;/li&gt;
&lt;li&gt;handling nullable values,&lt;/li&gt;
&lt;li&gt;creating data models,&lt;/li&gt;
&lt;li&gt;wiring UI interactions,&lt;/li&gt;
&lt;li&gt;refactoring small pieces of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, even simple things felt slower than they should.&lt;/p&gt;

&lt;p&gt;But that’s normal.&lt;/p&gt;

&lt;p&gt;The important part was not speed.&lt;br&gt;&lt;br&gt;
It was repetition.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Accepting that Kotlin is not just “better Java”
&lt;/h3&gt;

&lt;p&gt;This was one of the biggest mental shifts.&lt;/p&gt;

&lt;p&gt;If you come from another language, especially something like Java, JavaScript, or TypeScript, it is tempting to treat Kotlin as “the same thing with nicer syntax.”&lt;/p&gt;

&lt;p&gt;That mindset slows you down.&lt;/p&gt;

&lt;p&gt;Kotlin becomes much more enjoyable when you stop translating your old habits directly and start asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the idiomatic Kotlin way to do this?&lt;/li&gt;
&lt;li&gt;Why does this language encourage immutability here?&lt;/li&gt;
&lt;li&gt;Why is null handling so explicit?&lt;/li&gt;
&lt;li&gt;Why do data classes feel so natural in app development?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more I leaned into Kotlin as its own language, the easier it became to write cleaner code.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Building features instead of collecting concepts
&lt;/h3&gt;

&lt;p&gt;At the beginning, I thought I needed to “learn coroutines,” “learn data classes,” or “learn sealed classes” one by one.&lt;/p&gt;

&lt;p&gt;That was not the best approach for me.&lt;/p&gt;

&lt;p&gt;What worked better was learning those things inside real features.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user input taught me about state,&lt;/li&gt;
&lt;li&gt;screen transitions taught me about navigation,&lt;/li&gt;
&lt;li&gt;modeling items taught me about data classes,&lt;/li&gt;
&lt;li&gt;error handling taught me about nullability,&lt;/li&gt;
&lt;li&gt;asynchronous work pushed me toward coroutines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approach made the concepts feel useful immediately.&lt;/p&gt;

&lt;p&gt;And useful concepts are easier to remember than isolated definitions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What confused me the most at first
&lt;/h2&gt;

&lt;p&gt;Learning Kotlin through a real Android project was helpful — but not frictionless.&lt;/p&gt;

&lt;p&gt;A few things definitely slowed me down.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Null safety felt great… until I had to actually design around it
&lt;/h3&gt;

&lt;p&gt;At first, null safety looked like one of Kotlin’s cleanest features.&lt;/p&gt;

&lt;p&gt;And it is.&lt;/p&gt;

&lt;p&gt;But it also forced me to become more deliberate.&lt;/p&gt;

&lt;p&gt;Instead of casually passing values around and “handling it later,” I had to think earlier about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what can actually be null,&lt;/li&gt;
&lt;li&gt;what should be required,&lt;/li&gt;
&lt;li&gt;what should have a default value,&lt;/li&gt;
&lt;li&gt;and where I was making assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was frustrating at first because it exposed weak decisions immediately.&lt;/p&gt;

&lt;p&gt;But looking back, that was a good thing.&lt;/p&gt;

&lt;p&gt;Kotlin was not making development harder.&lt;br&gt;&lt;br&gt;
It was making sloppy assumptions harder.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Android structure is part of the learning curve, not just Kotlin
&lt;/h3&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;When you learn Kotlin for Android, you are not learning just one thing.&lt;/p&gt;

&lt;p&gt;You are learning at least three layers at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kotlin itself,&lt;/li&gt;
&lt;li&gt;Android concepts,&lt;/li&gt;
&lt;li&gt;and your chosen UI / architecture approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means confusion is not always caused by the language.&lt;/p&gt;

&lt;p&gt;Sometimes the real question is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a Kotlin issue?&lt;/li&gt;
&lt;li&gt;an Android lifecycle issue?&lt;/li&gt;
&lt;li&gt;a UI state issue?&lt;/li&gt;
&lt;li&gt;a project structure issue?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I understood that, I stopped blaming Kotlin for every confusing moment.&lt;/p&gt;

&lt;p&gt;A lot of beginner frustration actually comes from trying to learn the whole Android stack at once.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. There is a difference between “it works” and “this is clean”
&lt;/h3&gt;

&lt;p&gt;One of the biggest lessons was realizing how easy it is to build something that works… and how much harder it is to build something that still feels clean after the third or fourth feature.&lt;/p&gt;

&lt;p&gt;Early in the project, I often wrote code like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;directly inside the screen,&lt;/li&gt;
&lt;li&gt;with too much logic in one place,&lt;/li&gt;
&lt;li&gt;and without thinking much about separation of concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That got me moving quickly, which was good.&lt;/p&gt;

&lt;p&gt;But after a while, I could feel the cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;harder refactors,&lt;/li&gt;
&lt;li&gt;messier UI code,&lt;/li&gt;
&lt;li&gt;less confidence when adding features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was actually one of the most valuable parts of the project.&lt;/p&gt;

&lt;p&gt;It taught me that learning Kotlin is not just about syntax.&lt;br&gt;&lt;br&gt;
It is also about learning where code should live.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Kotlin features that clicked fastest for me
&lt;/h2&gt;

&lt;p&gt;Some Kotlin features made sense almost immediately once I used them in a real app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data classes
&lt;/h3&gt;

&lt;p&gt;These were one of the quickest wins.&lt;/p&gt;

&lt;p&gt;As soon as I started modeling app data, data classes felt obvious and useful.&lt;/p&gt;

&lt;p&gt;They reduce boilerplate and make data easier to reason about.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;when&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This felt much cleaner than long conditional chains.&lt;/p&gt;

&lt;p&gt;It became especially useful for handling UI states and branching logic in a more readable way.&lt;/p&gt;




&lt;h3&gt;
  
  
  Null safety
&lt;/h3&gt;

&lt;p&gt;Even though it was frustrating at first, it made my code more intentional.&lt;/p&gt;

&lt;p&gt;Once I stopped fighting it, I started trusting my code more.&lt;/p&gt;




&lt;h3&gt;
  
  
  Immutability by default
&lt;/h3&gt;

&lt;p&gt;This changed how I thought about state.&lt;/p&gt;

&lt;p&gt;When building UI, predictable state matters a lot.&lt;br&gt;&lt;br&gt;
Kotlin nudged me toward better habits there.&lt;/p&gt;




&lt;h3&gt;
  
  
  Extension functions
&lt;/h3&gt;

&lt;p&gt;These made code feel more expressive once I understood where they were actually useful.&lt;/p&gt;

&lt;p&gt;Not “clever” useful — but genuinely cleaner in small repeated patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would focus on first if I started again
&lt;/h2&gt;

&lt;p&gt;If I had to restart from zero today, I would not try to learn everything.&lt;/p&gt;

&lt;p&gt;I would focus on this order:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Core Kotlin basics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;variables&lt;/li&gt;
&lt;li&gt;functions&lt;/li&gt;
&lt;li&gt;classes&lt;/li&gt;
&lt;li&gt;nullability&lt;/li&gt;
&lt;li&gt;collections&lt;/li&gt;
&lt;li&gt;control flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Kotlin features that matter quickly in apps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;data classes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;when&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;immutable patterns&lt;/li&gt;
&lt;li&gt;simple extension functions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Basic Android app flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;screens&lt;/li&gt;
&lt;li&gt;user input&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;navigation&lt;/li&gt;
&lt;li&gt;local data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Code organization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;separating UI from logic&lt;/li&gt;
&lt;li&gt;keeping files readable&lt;/li&gt;
&lt;li&gt;avoiding giant screens or giant classes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Then only later: deeper architecture and advanced patterns
&lt;/h3&gt;

&lt;p&gt;I would avoid over-optimizing too early.&lt;/p&gt;

&lt;p&gt;The first milestone should not be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Build the perfect architecture.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Build something working, understandable, and easy enough to improve.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes everything else easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  My biggest takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest thing I learned is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kotlin became much easier once I stopped trying to “master Kotlin” and started trying to build something with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift changed everything.&lt;/p&gt;

&lt;p&gt;A real app gave every concept a reason to exist.&lt;/p&gt;

&lt;p&gt;Without a project, Kotlin was just a list of language features.&lt;/p&gt;

&lt;p&gt;Inside a project, it became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a way to model data,&lt;/li&gt;
&lt;li&gt;a way to structure UI logic,&lt;/li&gt;
&lt;li&gt;a way to write safer code,&lt;/li&gt;
&lt;li&gt;and a way to think more clearly about app development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when learning started to feel real.&lt;/p&gt;




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

&lt;p&gt;If you are trying to learn Kotlin right now, my honest advice is:&lt;/p&gt;

&lt;p&gt;Don’t wait until you feel “ready.”&lt;/p&gt;

&lt;p&gt;Don’t try to complete every tutorial first.&lt;/p&gt;

&lt;p&gt;Build something small.&lt;br&gt;&lt;br&gt;
Make it real.&lt;br&gt;&lt;br&gt;
Let the project expose the gaps in your understanding.&lt;/p&gt;

&lt;p&gt;That is where the best learning happens.&lt;/p&gt;

&lt;p&gt;Not when everything is clear.&lt;/p&gt;

&lt;p&gt;But when the code forces you to ask better questions.&lt;/p&gt;




&lt;p&gt;If you’re learning Kotlin too, I’d love to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What has been harder for you so far — the language itself, Android concepts, or figuring out how to structure a real app?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>beginners</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
