<?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: Erik Hanchett</title>
    <description>The latest articles on DEV Community by Erik Hanchett (@erikch).</description>
    <link>https://dev.to/erikch</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1994%2F0j84YwMs.jpeg</url>
      <title>DEV Community: Erik Hanchett</title>
      <link>https://dev.to/erikch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erikch"/>
    <language>en</language>
    <item>
      <title>Nuxt 4.5 SSR Streaming Is Kind Of A Big Deal</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:41:30 +0000</pubDate>
      <link>https://dev.to/erikch/nuxt-45-ssr-streaming-is-kind-of-a-big-deal-2i37</link>
      <guid>https://dev.to/erikch/nuxt-45-ssr-streaming-is-kind-of-a-big-deal-2i37</guid>
      <description>&lt;p&gt;&lt;a href="https://nuxt.com/blog/v4-5" rel="noopener noreferrer"&gt;Nuxt 4.5&lt;/a&gt; launched last month and it's really neat. One of my most favorite features is the experimental SSR streaming. With one simple change in the Nuxt config I was able to reduce the Largest Contentful Paint (LCP) by over 2 seconds. I simply enabled the experimental SSR streaming, and the first byte arrived in 16 milliseconds.&lt;/p&gt;

&lt;p&gt;With out it, the server spent about 2.5 seconds rendering the page. Streaming changed what the browser could display during that wait. &lt;/p&gt;

&lt;p&gt;As a part of my testing I also tried out two other features: &lt;code&gt;useLayout()&lt;/code&gt; and named views. Let's take a look at all of these now!&lt;/p&gt;

&lt;p&gt;This post walks through all three features using Nuxt 4.5.2. Let's take a look!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you rather watch check out my full video here&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Bty-hzN8l84"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22 or another version supported by Nuxt 4.5&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;li&gt;Basic experience with Nuxt pages, components, and layouts&lt;/li&gt;
&lt;li&gt;Chrome or another browser with document-request timing tools&lt;/li&gt;
&lt;li&gt;My sample app! &lt;a href="https://github.com/ErikCH/nuxt-ssr-streaming" rel="noopener noreferrer"&gt;https://github.com/ErikCH/nuxt-ssr-streaming&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After cloning down the repo, build it and run it in preview.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
npm run typecheck
npm run build
npm run preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo preview runs on port 3010. The streamed route is &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;/buffered&lt;/code&gt; uses the same components with streaming disabled by a route rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 1: Experimental SSR streaming
&lt;/h2&gt;

&lt;p&gt;Normal server-side rendering can buffer the document until every server-rendered component finishes. A slow database query or API request can leave the browser waiting even when the page shell is ready.&lt;/p&gt;

&lt;p&gt;Nuxt 4.5 added the experimental &lt;code&gt;ssrStreaming&lt;/code&gt; option. It uses Vue's web-stream renderer to flush ready HTML while later boundaries continue rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a controlled slow component
&lt;/h3&gt;

&lt;p&gt;I used a fixed delay so the delivery difference would be easy to see. The delay runs on the server, and &lt;code&gt;useState&lt;/code&gt; serializes the result into the Nuxt payload for hydration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SlowResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;resolvedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;serverDurationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;renderMarker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRoute&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SlowResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`slow-panel:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolvedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serverDurationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;renderMarker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&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;startedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&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;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;resolvedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;serverDurationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renderMarker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place the component inside a Vue &lt;code&gt;Suspense&lt;/code&gt; boundary. The fallback becomes part of the shell that Nuxt can send before &lt;code&gt;SlowPanel&lt;/code&gt; resolves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&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;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Nuxt 4.5 rendering test&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The shell is ready&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Suspense&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;SlowPanel&lt;/span&gt; &lt;span class="na"&gt;:delay-ms=&lt;/span&gt;&lt;span class="s"&gt;"2500"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-busy=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Shell received&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Waiting for the slow server boundary...&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Suspense&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;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback needs to be useful. Navigation, page context, and a clear loading state give the visitor something to work with. Sending an empty shell earlier does little for the experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable streaming in production
&lt;/h3&gt;

&lt;p&gt;The demo enables streaming outside development and keeps &lt;code&gt;/buffered&lt;/code&gt; as a control route.&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;ssrStreamingEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;compatibilityDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-07-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;devLogs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ssrStreaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ssrStreamingEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;runtimeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ssrStreamingEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;routeRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/buffered&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;streaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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 &lt;code&gt;/buffered&lt;/code&gt; route uses the same slow component, delay, and shell. Its route rule changes the response mode without changing the work performed by the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compare the responses
&lt;/h3&gt;

&lt;p&gt;Open the streamed and buffered routes as full document requests. Disable the browser cache and use a hard reload. Client-side navigation does not create a new SSR document request.&lt;/p&gt;

&lt;p&gt;You can also measure the response chunks from a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run measure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My local production preview produced this result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;streamed: first byte 16 ms, complete 2562 ms, 14 chunks, 5.8 KB
buffered: first byte 2518 ms, complete 2518 ms, 1 chunk, 5.8 KB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both routes performed the same delayed work and returned the same amount of HTML. The streamed route delivered the response in 14 chunks and gave the browser useful HTML much earlier. The buffered route delivered one chunk after the slow boundary finished.&lt;/p&gt;

&lt;p&gt;This was just a test, on a somewhat contrived setup. However, I would try this yourself in your own app and see how the results go. Make sure to try it in development first, before going to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the production constraints
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ssrStreaming&lt;/code&gt; is experimental and disabled by default. Nuxt can fall back to buffered rendering for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bots and crawlers&lt;/li&gt;
&lt;li&gt;Cached routes&lt;/li&gt;
&lt;li&gt;Incremental Static Regeneration (ISR)&lt;/li&gt;
&lt;li&gt;Stale-while-revalidate (SWR) routes&lt;/li&gt;
&lt;li&gt;Redirects&lt;/li&gt;
&lt;li&gt;Routes using &lt;code&gt;ssr: false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prerendered output&lt;/li&gt;
&lt;li&gt;Routes with &lt;code&gt;streaming: false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Streaming also changes when the HTTP response becomes committed. After the shell has been flushed, later component code may be too late to change the status, headers, or cookies. Test authentication redirects, cookie writes, cache headers, errors, and middleware before enabling streaming on a production route.&lt;/p&gt;

&lt;p&gt;I would start with a content-heavy page that has a useful shell and one isolated slow boundary. Measure the deployed route with its real adapter and route rules. Keep buffering where the application needs to finish response decisions before sending HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 2: Read the resolved layout with &lt;code&gt;useLayout()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Nuxt 4.5 added a stable &lt;code&gt;useLayout()&lt;/code&gt; composable. It returns a read-only computed ref containing the layout Nuxt resolved for the current route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;output&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"layout-badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;code&amp;gt;&lt;/span&gt;useLayout()&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/output&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value accounts for page metadata, route-rule layout selection, and the default layout. It also updates during navigation.&lt;/p&gt;

&lt;p&gt;In the demo, two child pages select different layouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/pages/features/overview.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/pages/features/focus.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent page stays mounted while navigation changes the child route. The computed layout updates from &lt;code&gt;default&lt;/code&gt; to &lt;code&gt;focus&lt;/code&gt; without requiring the parent to inspect route metadata itself.&lt;/p&gt;

&lt;p&gt;Reading &lt;code&gt;route.meta.layout&lt;/code&gt; can miss layouts selected elsewhere in Nuxt's resolution chain. Use &lt;code&gt;useLayout()&lt;/code&gt; when a component needs the layout Nuxt is actually rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 3: Render multiple outlets with named views
&lt;/h2&gt;

&lt;p&gt;Named views let one route render into multiple page outlets. Nuxt 4.5 uses a &lt;code&gt;name@view.vue&lt;/code&gt; filename convention for the additional view files.&lt;/p&gt;

&lt;p&gt;The demo uses this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/pages/
  features.vue
  features/
    index.vue
    overview.vue
    overview@sidebar.vue
    focus.vue
    focus@sidebar.vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent page creates a default outlet and a named sidebar outlet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Named view outlets"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtPage&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;aside&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtPage&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sidebar"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you visit &lt;code&gt;/features/overview&lt;/code&gt;, &lt;code&gt;overview.vue&lt;/code&gt; fills the default outlet and &lt;code&gt;overview@sidebar.vue&lt;/code&gt; fills the sidebar. The &lt;code&gt;focus&lt;/code&gt; files do the same work for &lt;code&gt;/features/focus&lt;/code&gt;. Both outlets belong to one URL, and the parent does not need a manual component map.&lt;/p&gt;

&lt;p&gt;Keep route metadata in the default page file. Nuxt ignores &lt;code&gt;definePageMeta&lt;/code&gt; in an &lt;code&gt;@sidebar&lt;/code&gt; file. If a route has no matching named-view file, that outlet remains empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finale
&lt;/h2&gt;

&lt;p&gt;I am really impressed by the incremental changes in Nuxt 4.5. SSR streaming is something I'll be using on all my future apps. And useLayout and named views are nice DX bonuses. &lt;/p&gt;

&lt;p&gt;Leave a comment and let me know which of these features you'll be using next!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/blog/v4-5" rel="noopener noreferrer"&gt;Nuxt 4.5 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/docs/4.x/guide/going-further/experimental-features" rel="noopener noreferrer"&gt;Nuxt experimental features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/docs/4.x/api/composables/use-layout" rel="noopener noreferrer"&gt;Nuxt &lt;code&gt;useLayout()&lt;/code&gt; documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/docs/4.x/guide/directory-structure/app/pages#named-views" rel="noopener noreferrer"&gt;Nuxt named views documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vuejs.org/api/ssr.html#rendertowebstream" rel="noopener noreferrer"&gt;Vue server-rendering API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Bty-hzN8l84" rel="noopener noreferrer"&gt;Watch the full Nuxt 4.5 video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>ssr</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How To learn Web Development in 2026</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 03 Aug 2026 17:19:26 +0000</pubDate>
      <link>https://dev.to/erikch/how-to-learn-web-development-in-2026-52j5</link>
      <guid>https://dev.to/erikch/how-to-learn-web-development-in-2026-52j5</guid>
      <description>&lt;p&gt;Learning web development in 2026 is a lot different than it was when I first learned in 2010. Back then, I used Udemy courses, Stack Overflow, and official documentation. I’d spend hours copying and pasting code, only to discover that the blog post I was pulling it from was out of date. It was frustrating, to say the least.&lt;/p&gt;

&lt;p&gt;Luckily, learning a new framework in 2026 is a lot easier than it was. &lt;/p&gt;

&lt;p&gt;Recently I was at Chain React. It's a React Native conference and for &lt;a href="https://chainreactconf.com/talks/how-i-learned-react-native-with-ai" rel="noopener noreferrer"&gt;my talk&lt;/a&gt; I decided to really deep into the framework and learn it. I started doing the same thing I always did, reading the docs, buying courses etc. Then I realized I could use AI to speed up my learning. This is what I want to teach you today, with a few additional tips along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to watch instead, check out my full video on the subject!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2WIlokBwlzY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;(Full disclosure before I start naming tools: I'm a Developer Advocate at AWS, and &lt;a href="https://kiro.dev/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; is an AWS product. It's the agentic harness I use daily. Everything here works the same in Claude Code, Cursor, or whatever you already have open.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a goal, not a curriculum
&lt;/h2&gt;

&lt;p&gt;I don't start by learning a framework. I start with something that I want to build for fun.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4bboqg3ouoms52chq52.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo4bboqg3ouoms52chq52.png" alt="thinking slide" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I was learning React Native, it was a workout tracker. I wanted to learn a program called 75 Hard, which is 75 days of hitting the same set of daily goals including a diet, an outdoor workout, and a second workout. I wanted to check those off on my phone. That was the goal.&lt;/p&gt;

&lt;p&gt;Making sure you have a goal is really important. A todo list app teaches you nothing, because it means almost nothing. A real app forces decisions you have to make, like does it work offline, where does the data live, what happens when permissions get denied. Pick something with a little resistance in it, ideally tied to a hobby so you'll still care as your learning progression continues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide how much you actually need to know
&lt;/h2&gt;

&lt;p&gt;In other words, how much of it do I need to actually learn to achieve my goal?&lt;/p&gt;

&lt;p&gt;In today's age, you need to learn the primitives. The basics, and some architecture of how it all works. You do not need to memorize API signatures anymore.&lt;/p&gt;

&lt;p&gt;Let's imagine you are learning React. If you're using &lt;code&gt;useState&lt;/code&gt; everywhere, understand what it does and why re-renders happen. Whether you can recall the exact argument order from memory is irrelevant, because the model will write it. Same with optional chaining, same with whatever config format the build tool wants this year. Learn the basics of the framework or library you are learning. Let the agent handle the syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask three models what they'd build today
&lt;/h2&gt;

&lt;p&gt;Before I learn anything, I want to know what the ecosystem actually looks like right now. So I ask several models the same broad question and compare.&lt;/p&gt;

&lt;p&gt;When I was learning React Native, I ran the same prompt through Gemini, Claude, and GPT. Something like this:&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 mobile application for iOS.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That comes back with Swift, which tells me something. Then:&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 cross-platform iOS and Android application.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I get Flutter or React Native. I'm not asking for code here. I'm reading the consensus from the agent to find out which frameworks and libraries it's recommending, which tells me whether the thing I'm about to learn is the thing I should be learning.&lt;/p&gt;

&lt;p&gt;When the models disagree, that's when I dig in further to find out why. I'll usually google around a bit to see if I'm on the right path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get a personalized learning path
&lt;/h2&gt;

&lt;p&gt;Finally, I work on a personalized learning path, based on what I know already. Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build me a curriculum to learn React Native. Assume I already
have basic knowledge of React, HTML, CSS, and JavaScript.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second sentence is where I brought in my own personalization. I know web development, so I wanted to make sure my learning path is tailored to me. Also, I called out React Native, as per the last section, I learned it's the most popular and makes the most sense for me.&lt;/p&gt;

&lt;p&gt;Because I told it I already knew web basics, it skips React fundamentals and goes straight to what's different: native components instead of the DOM, &lt;code&gt;StyleSheet&lt;/code&gt; instead of the CSS cascade, navigation as a stack instead of URLs, you get the idea. I then get topics with a suggested time frame, and I can work through them at my own pace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; model selection matters here more than almost anywhere else. I ran this against Sonnet 4 and Opus 5. Sonnet 4 handed me what was essentially 2023 React Native. Opus 5 gave me current information and a noticeably better curriculum. Every model has a different training cutoff, and a stale curriculum is worse than no curriculum, because you don't know which parts are wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're stuck on an older or a local model, run an adversarial review. Have the cheap model draft the learning path, then hand it to a current model and ask what's out of date. Going past two or three models surfaces a surprising amount, and it's also a good idea when you are trying to save tokens.&lt;/p&gt;

&lt;p&gt;Also make sure you use some of the tools your harness gives you. Most harnesses have search built in, plus MCP servers and skills. If you're learning React, install the current React skills before you generate anything. Scaffolding tools help here too: &lt;code&gt;create-next-app&lt;/code&gt; now drops an &lt;code&gt;AGENTS.md&lt;/code&gt; in your project, and that file is a better starting point for your curriculum than the model's memory. &lt;/p&gt;

&lt;h2&gt;
  
  
  Build it: I do, we do, you do
&lt;/h2&gt;

&lt;p&gt;A learning path is a good start. It's still reading. To move to actual understanding I used an old teaching framework from 1983.&lt;/p&gt;

&lt;p&gt;It's called gradual release of responsibility, from a paper by Pearson and Gallagher, and it sits on top of Vygotsky's zone of proximal development. The sequence is &lt;strong&gt;I do, we do, you do&lt;/strong&gt;. The teacher demonstrates, then you work together, then you work alone.&lt;/p&gt;

&lt;p&gt;To use this framework today, the &lt;strong&gt;the AI becomes the "I."&lt;/strong&gt; It does, you watch. Then you work together with the AI, and finally you do it yourself. It's really helped me learn. Let's start with ** I do **&lt;/p&gt;

&lt;h3&gt;
  
  
  I do: let it build, then read it
&lt;/h3&gt;

&lt;p&gt;Let the agent build the whole thing. Just create a simple prompt, and let it do it's thing. Don't type anything. Read the output, look at the file structure, see how it wired things together.&lt;/p&gt;

&lt;p&gt;You are not learning yet, and that's fine. This phase is the right one because it gives you an idea what is possible. If you like you could look over the code that was written, but it's not the best way to learn.&lt;/p&gt;

&lt;h3&gt;
  
  
  We do: spec-driven development
&lt;/h3&gt;

&lt;p&gt;This is where most of the learning actually happened for me.&lt;/p&gt;

&lt;p&gt;Spec-driven development means writing structured specifications first, so the agent can build and verify against them. In practice I ask for a spec instead of an app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a spec that helps build a retro workout planner app.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favz2px260gllq0i7yxhm.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favz2px260gllq0i7yxhm.png" alt="wedo graphic" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What comes back is a design document, and the design document is the cheat sheet. Mine told me to use Expo Router 57, &lt;code&gt;expo-sqlite&lt;/code&gt;, an image picker, and notifications. That's a map of the architecture and the current library choices for a framework I didn't know yet. When I was working like this, I would constantly ask clarifying questions and ask why certain decisions were made. I would even often ask for changes.&lt;/p&gt;

&lt;p&gt;Then it generates requirements, usually as user stories. I skip this one when I'm learning. It's more useful when you're shipping to other people.&lt;/p&gt;

&lt;p&gt;The implementation plan is the part you need to pay attention to. It's a task list. &lt;strong&gt;Instead of letting the agent execute it, I work through the tasks myself, with the design doc open.&lt;/strong&gt; When I get stuck, I say so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I tried to install NativeWind 5 and I don't understand what to do here. Can you help?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbi8yu5j856qvl0efjzs.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbi8yu5j856qvl0efjzs.png" alt="implementation plan" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I get help, and I might even read the official documentation anyways. That's the "we do" phase working exactly as designed. I have enough context to attempt it and a patient buddy for the gaps. It's almost like &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" rel="noopener noreferrer"&gt;rubber ducking&lt;/a&gt;, but with something that can respond.&lt;/p&gt;

&lt;p&gt;Keep in mind, if you write a spec and then let the agent implement all of it, you haven't learned much. You can now explain what your app does and still not explain how the framework does it. So I settled on a rule. &lt;strong&gt;Spec the what, hand-write the how, at least once per concept.&lt;/strong&gt; Write the spec for the app, but make sure your writing things yourself (with help if needed). &lt;/p&gt;

&lt;h3&gt;
  
  
  You do: turn it off
&lt;/h3&gt;

&lt;p&gt;Close the tab. Build something small with no assistance.&lt;/p&gt;

&lt;p&gt;Don't skip this step. This is the best way to check your recall and that you really understand everything. I wouldn't write the whole thing from scratch, mind you, but a few pieces just to make sure I understand the underlying concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more patterns worth stealing
&lt;/h2&gt;

&lt;p&gt;Let the AI quiz you. Take the learning path you already generated and ask for a quiz on it, then go back and forth. It's a quick way to find out which parts you only think you know.&lt;/p&gt;

&lt;p&gt;The stronger version of this flips the direction. Don't ask the model to explain hooks to you. Explain hooks &lt;em&gt;to the model&lt;/em&gt; and ask it to grade you. "Here's my understanding of the New Architecture, what did I get wrong?" Use text-to-speach if you can, to make this even quicker.&lt;/p&gt;

&lt;p&gt;The other pattern is learning in public, which I was doing long before agents existed. Post what you're learning, on YouTube or Bluesky or a blog. Writing it down for someone else is what exposes the parts you weren't sure on. This post exists because I told a conference I'd stand up and explain React Native, and that deadline taught me more than any course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I've landed
&lt;/h2&gt;

&lt;p&gt;Start with a goal you care about. Use your usual harness on a current model. Have it build you a plan, with search and MCP servers turned on so it isn't working from memory. Learn the primitives and the architecture, skip memorizing the APIs. Then try out something like: having the AI build it, build alongside it, then build alone.&lt;/p&gt;

&lt;p&gt;AI didn't replace the learning. It replaced the searching. All those hours of hunting for the right Stack Overflow answer are gone, and I don't miss them. But the part where you sit with something confusing until it stops being confusing? That still has to happen. There's just no tab for it.&lt;/p&gt;

&lt;p&gt;How do you learn a new framework these days? Let me know in the comments if you do it differently. Until next time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>7 Kiro Features You're Probably Not Using</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 27 Jul 2026 15:39:49 +0000</pubDate>
      <link>https://dev.to/aws/7-kiro-features-youre-probably-not-using-2417</link>
      <guid>https://dev.to/aws/7-kiro-features-youre-probably-not-using-2417</guid>
      <description>&lt;p&gt;Did you know that &lt;a href="https://kiro.dev/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; doesn't just have a Spec-Driven Development (SDD) flow, but also a bug fix workflow that helps you resolve one issue at a time? That's one of seven features worth knowing about.&lt;/p&gt;

&lt;p&gt;If you're completely new to Kiro, it's an agentic harness for the CLI, web, IDE, iOS, and more. It helps teams and individuals do their best work while coding. I've been using it since it launched in July last year, and I keep finding features I didn't know were there.&lt;/p&gt;

&lt;p&gt;(Full disclosure: I'm a Developer Advocate at AWS, and Kiro is a part of AWS. I use it every day, and I'll be forthcoming about the parts that are still preview or experimental.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; Kiro ships fast. I've flagged the version-sensitive bits of these features inline. Check the &lt;a href="https://kiro.dev/docs/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;docs&lt;/a&gt; if something looks different in your build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Stop approving every single command
&lt;/h2&gt;

&lt;p&gt;After talking to a lot of people about Kiro, one of the main pieces of feedback I get is on approving commands. When Kiro asks permission to run a shell command, the default reaction is to hit yes and move on. Then it asks again for the next &lt;code&gt;git&lt;/code&gt; command. And the next one.&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Tab&lt;/strong&gt; instead in the CLI. This allows you to edit it and put the exact permissions you'd like. For example you can be pickier on the trust tiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt;     &lt;span class="c"&gt;# this exact command&lt;/span&gt;
git pull &lt;span class="k"&gt;*&lt;/span&gt;            &lt;span class="c"&gt;# git pull with any arguments&lt;/span&gt;
git &lt;span class="k"&gt;*&lt;/span&gt;                 &lt;span class="c"&gt;# anything git&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;                     &lt;span class="c"&gt;# the entire shell tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever you pick persists for the session and gets stored as a regex in your agent's &lt;code&gt;allowedCommands&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;/tools trust-all&lt;/code&gt;, which trusts everything. It's the documented replacement for the old &lt;code&gt;/acceptall&lt;/code&gt;, and the &lt;a href="https://kiro.dev/docs/cli/chat/security/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;security docs&lt;/a&gt; are blunt about it: don't use it in production or with sensitive data, and you're responsible for whatever it does. &lt;/p&gt;

&lt;p&gt;One version note: on CLI v3 this moves to a &lt;code&gt;permissions.yaml&lt;/code&gt; file, so the agent JSON advice above is v2. More on v3 in a minute.&lt;/p&gt;

&lt;p&gt;Full details: &lt;a href="https://kiro.dev/docs/cli/chat/permissions/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;tool permissions&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The &lt;code&gt;#&lt;/code&gt; menu is bigger than &lt;code&gt;#file&lt;/code&gt; in the IDE
&lt;/h2&gt;

&lt;p&gt;Type &lt;code&gt;#&lt;/code&gt; in the IDE chat and you get a list of context providers. &lt;code&gt;#file&lt;/code&gt; is the one I use a lot. The full list:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#codebase&lt;/code&gt; · &lt;code&gt;#file&lt;/code&gt; · &lt;code&gt;#folder&lt;/code&gt; · &lt;code&gt;#git diff&lt;/code&gt; · &lt;code&gt;#terminal&lt;/code&gt; · &lt;code&gt;#problems&lt;/code&gt; · &lt;code&gt;#url:&lt;/code&gt; · &lt;code&gt;#code:&lt;/code&gt; · &lt;code&gt;#repository&lt;/code&gt; · &lt;code&gt;#current&lt;/code&gt; · &lt;code&gt;#steering:&lt;/code&gt; · &lt;code&gt;#docs:&lt;/code&gt; · &lt;code&gt;#spec:&lt;/code&gt; · &lt;code&gt;#mcp:&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can stack them in a single message. &lt;code&gt;#git diff #problems review my changes&lt;/code&gt; is a different request than either one alone.&lt;/p&gt;

&lt;p&gt;The one I've been using more is the &lt;code&gt;#terminal&lt;/code&gt;. It hands Kiro your recent terminal output and command history, so you stop copying and pasting errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;terminal why did this build fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing I find handy is the "#Currently Open files." However, keep in mind that Kiro pulls in your open files and their dependencies automatically without you asking.&lt;/p&gt;

&lt;p&gt;Full list: &lt;a href="https://kiro.dev/docs/chat/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el#context-providers" rel="noopener noreferrer"&gt;context providers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also you can't drag a file in the IDE's own explorer to the chat window, however you can copy and past the location of the file and past that into the chat window as a workaround. &lt;/p&gt;

&lt;h2&gt;
  
  
  3. Pick a workflow instead of freeform chatting
&lt;/h2&gt;

&lt;p&gt;Kiro gives you structured starting points, and it's tempting to just start typing into the chat box instead. Instead you can choose any of these in the IDE. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc5jfyheu0l5gd8ftqp2m.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc5jfyheu0l5gd8ftqp2m.png" alt="IDE chooser" width="800" height="749"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spec&lt;/strong&gt; for a full feature, with requirements, design, and tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; when you want an implementation plan and zero code changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug Fix&lt;/strong&gt; to investigate, diagnose, and resolve one problem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick Spec&lt;/strong&gt; when you want Kiro to ask a couple of clarifying questions and then generate the requirements, design, and tasks for you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bug Fix is the one I'd try first. It writes down current behavior, expected behavior, and unchanged behavior. I like how it doesn't change a bunch of files it shouldn't. It basically tells the agent what it is not allowed to touch. &lt;/p&gt;

&lt;p&gt;On a feature spec you also get a choice between requirements-first and tech design-first. If you already know your architecture, pick design-first. There's no reason to answer requirements questions you already have answers to.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Skills are slash commands
&lt;/h2&gt;

&lt;p&gt;Kiro activates a &lt;a href="https://kiro.dev/docs/skills/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;skill&lt;/a&gt; automatically when your prompt matches the skill's description. That works, but I feel like it doesn't always automatically activate.&lt;/p&gt;

&lt;p&gt;You can invoke one directly instead. Skills in &lt;code&gt;.kiro/skills/&lt;/code&gt; or &lt;code&gt;~/.kiro/skills/&lt;/code&gt; show up as slash commands, so a skill named &lt;code&gt;pr-review&lt;/code&gt; becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/pr-review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That loads the entire instruction file, not just the description. Works in the CLI and the IDE.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kiro.dev/docs/powers/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Powers&lt;/a&gt; are a different thing and worth knowing about separately. A power bundles MCP server config with steering and optional hooks, and Kiro loads it only when your task is relevant to it. Powers are keyword-activated, and they're free on every plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. You might be running an old build
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;Help &amp;gt; About&lt;/code&gt; and check your version against &lt;a href="https://kiro.dev/downloads/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;kiro.dev/downloads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Kiro's own changelog says auto-updates were paused for IDE 1.0.x and tells you to download the latest release directly from the site. So if your version doesn't match what's on the downloads page, that's why, and no amount of restarting will fix it. Reinstalling keeps your settings, extensions, and sign-in state.&lt;/p&gt;

&lt;p&gt;On the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli &lt;span class="nt"&gt;--version&lt;/span&gt;
kiro-cli update
kiro-cli doctor      &lt;span class="c"&gt;# install and config problems&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;/changelog&lt;/code&gt; in the CLI inside a chat session shows the release notes inline, which is a nicer way to find out what shipped than scrolling a webpage.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The work doesn't have to run on your laptop
&lt;/h2&gt;

&lt;p&gt;One subscription covers the IDE, the CLI, &lt;a href="https://kiro.dev/docs/web/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro on the web&lt;/a&gt;, ACP-compatible editors, and automation in CI/CD. Same credit pool.&lt;/p&gt;

&lt;p&gt;I really like the web version. You kick off a session, it runs in a cloud sandbox, and it opens the pull request when it's done. Your laptop can be closed. There are also Automations, which are cron-scheduled agent runs that open PRs on their own.&lt;/p&gt;

&lt;p&gt;Web is still in preview, gated to Pro and above, and it needs a connected GitHub account. There's an iOS app too, but it's early access via TestFlight, Pro or higher, and it's a surface for starting and reviewing cloud sessions rather than an editor. No Android yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The experimental stuff is opt-in and worth trying
&lt;/h2&gt;

&lt;p&gt;Three of these are behind a flag or a toggle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI v3.&lt;/strong&gt; Run &lt;code&gt;kiro-cli --v3&lt;/code&gt; and it runs alongside your existing 2.x setup without touching it. You get the spec agent in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kiro-cli &lt;span class="nt"&gt;--v3&lt;/span&gt;
/spec new my-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus capability-based &lt;code&gt;permissions.yaml&lt;/code&gt;, standalone hooks in &lt;code&gt;.kiro/hooks/*.json&lt;/code&gt;, and markdown agent configs. See the &lt;a href="https://kiro.dev/docs/cli/v3/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;v3 docs&lt;/a&gt; for the full list of features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Focus in the IDE.&lt;/strong&gt; Toggle it from the button in the top right. The layout flips: parallel sessions down the left, chat in the middle, specs and diffs in a panel on the right. It's a different job than editing files. You're directing several agents and reviewing what they produce. Settings, powers, MCP management, the terminal, and direct file editing all still live in the normal view, so you'll toggle back and forth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/goal&lt;/code&gt;.&lt;/strong&gt; Give it an objective and a definition of done, and it loops implement-then-verify until the criteria are met:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal Migrate the auth module to the new SDK. Done when all tests pass and there are no TypeScript errors.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is available in v3 CLI.&lt;/p&gt;

&lt;p&gt;Default is five iterations, &lt;code&gt;--max &amp;lt;n&amp;gt;&lt;/code&gt; raises it, &lt;code&gt;/goal clear&lt;/code&gt; cancels. The docs are right that the definition of done is the part that matters. "All tests pass" works. "Make it better" does not. You can interrupt mid-loop, or nudge it without cancelling using queue steering with &lt;code&gt;Ctrl+S&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few more worth a mention
&lt;/h2&gt;

&lt;p&gt;While I'm listing CLI commands: &lt;code&gt;/rewind&lt;/code&gt; forks the conversation at an earlier turn instead of you fighting a thread that went off the rails, and the original session is preserved. &lt;code&gt;/compact&lt;/code&gt; summarizes history to free up context. &lt;code&gt;/tangent&lt;/code&gt; (&lt;code&gt;Ctrl+T&lt;/code&gt;) lets you run commands on the side (like /btw in Claude). &lt;code&gt;/guide&lt;/code&gt; is a docs-grounded agent for "how do I do X in Kiro" questions, and it can write agents, prompts, and steering files into &lt;code&gt;.kiro/&lt;/code&gt; for you.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Shift+Enter&lt;/code&gt; for multi-line input has been annoying you, &lt;code&gt;/settings terminal&lt;/code&gt; fixes it and backs up your terminal config first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go when you're stuck
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://kiro.dev/discord/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Kiro Discord&lt;/a&gt; is the main support channel, and there's an active subreddit at r/kiroIDE. Note the name, it's kiroIDE, not r/kiro. Billing lives in your account settings. Bugs and feature requests go to the &lt;a href="https://github.com/kirodotdev/Kiro/issues/new/choose" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;, and &lt;code&gt;/issue&lt;/code&gt; in the CLI opens that workflow for you. If you need to send logs along with a bug report, &lt;code&gt;/logdump&lt;/code&gt; zips them up.&lt;/p&gt;

&lt;p&gt;Which of these did you not know about? Let me know in the comments. Until next time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Nuxt vs SvelteKit: What works better?</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Wed, 22 Jul 2026 01:13:24 +0000</pubDate>
      <link>https://dev.to/erikch/nuxt-vs-sveltekit-what-works-better-132h</link>
      <guid>https://dev.to/erikch/nuxt-vs-sveltekit-what-works-better-132h</guid>
      <description>&lt;p&gt;Nuxt vs SvelteKit. Which one is better?&lt;/p&gt;

&lt;p&gt;That is is what I've been testing out this week. I built the same app twice. Once in Nuxt with the version 5 compatibility preview turned on, and once in SvelteKit using its experimental remote functions.&lt;/p&gt;

&lt;p&gt;I created a basic task app. Where you can add or remove tasks. I also logged the network requests and timing. The biggest thing I noticed was that the SvelteKit app would make one request, when creating a new task, while the Nuxt app needed two.&lt;/p&gt;

&lt;p&gt;That one difference turned out to be the most interesting part of the whole comparison, so let's jump in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Both apps are a simple task list backed by an in browser memory store. Both are production builds running locally, and both render the initial task list on the server.&lt;/p&gt;

&lt;p&gt;Quick caveat, Nuxt 5 is not released yet. My Nuxt app is stable Nuxt 4.5 with the compatibility flag set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;compatibilityDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-07-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compatibilityVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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;And SvelteKit's remote functions are still marked experimental in the docs. So this is a comparison of directions, not finished products.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;When I add a task in the Nuxt app, I get a POST to &lt;code&gt;/api/tasks&lt;/code&gt; (about 690 ms) followed by a GET to &lt;code&gt;/api/tasks&lt;/code&gt; (about 450 ms) to refresh the list. A little over 1,100 ms total, and the timeline panel reports two browser requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv3bxswcuz5iy702h5ns.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv3bxswcuz5iy702h5ns.png" alt="Nuxt request" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I add a task in the SvelteKit app, I get one request. About 1,100 ms. The server still does both operations, the mutation and the read, but they come back in a single response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fur0jeepdlow1acyb8w7d.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fur0jeepdlow1acyb8w7d.png" alt="Svelte Request" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Plain refreshes were nearly identical. 447 ms in Nuxt, 448 ms in SvelteKit. I ran this quite a few times, and if I had to pick, SvelteKit felt slightly faster overall. But the totals were close enough that I wouldn't choose a framework based on them.&lt;/p&gt;

&lt;p&gt;Let's talk about how requests work in each. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Nuxt version: explicit API routes
&lt;/h2&gt;

&lt;p&gt;If you've used Nuxt before, this will feel familiar. I have two handlers in &lt;code&gt;server/api&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/api/tasks.get.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineEventHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readTasks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/task-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;readTasks&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The POST handler validates the title and saves the task. These are normal HTTP endpoints. Anything that speaks HTTP can call them.&lt;/p&gt;

&lt;p&gt;On the page, &lt;code&gt;useFetch&lt;/code&gt; loads the initial data during SSR, so hydration doesn't fetch it again. When I add a task, I post with &lt;code&gt;$fetch&lt;/code&gt; and then call &lt;code&gt;refresh()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TaskSnapshot&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/tasks&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task-dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitTask&lt;/span&gt;&lt;span class="p"&gt;()&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;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/tasks&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;},&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;refresh&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 code is explicit, and the network tab matches the code exactly. POST first, GET second.&lt;/p&gt;

&lt;p&gt;Could I avoid the second request? Sure. The POST could return the updated list and I could patch local state myself. I wrote it this way because invalidate-and-refetch is the workflow most of us reach for, and it's exactly the pattern SvelteKit's remote functions are designed to improve. I also find by adding a GET request we are verifying the exact output after the POST.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SvelteKit version: remote functions
&lt;/h2&gt;

&lt;p&gt;This is the experimental feature. You turn it on in &lt;code&gt;svelte.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;remoteFunctions&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;compilerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;async&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="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;Then you create a file ending in &lt;code&gt;.remote.ts&lt;/code&gt; and export your server functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tasks.remote.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$app/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;addTask&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;addTaskToStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readTasks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/server/task-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;valibot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&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;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;query&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;readTasks&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskInput&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;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addTaskToStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// This runs on the server, and the refreshed query value&lt;/span&gt;
  &lt;span class="c1"&gt;// comes back in the same command response.&lt;/span&gt;
  &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;getTasks&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;refresh&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;result&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function bodies always run on the server. In the browser, they become typed wrappers around endpoints SvelteKit generates for you. I really enjoy how there is no public endpoint to hit, it's isolated. It's created for the call, which means I can use environment variables and secrets in there without thinking about it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;void getTasks().refresh()&lt;/code&gt; line is the API on the server side to trigger the refresh. After the mutation, SvelteKit refreshes the query on the server and packages the new value into the command's response. That's the single-flight mutation, and it's why the network tab shows one request instead of two.&lt;/p&gt;

&lt;p&gt;On the page, I just import and call the functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&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="nx"&gt;addTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTasks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tasks.remote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTasks&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;submitTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SubmitEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;addTask&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;render&lt;/span&gt; &lt;span class="nf"&gt;dashboard&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;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;await tasks&lt;/code&gt; at the bottom works almost like a subscription. When the server-side refresh happens, the task list updates automatically. No server routes to worry about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24q7zjeyi2ru8skt8l6t.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24q7zjeyi2ru8skt8l6t.png" alt="remotefunctions" width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My first look at this pattern, I thought it was a little complicated. But it clicked pretty fast, and the query-refreshes-inside-the-command idea makes sense once you see it in the network tab. If you've used server actions in Next or TanStack Start, this will feel like family. I'd say I still like TanStack Start's server actions a bit better, but this is close.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: remote functions have been available since SvelteKit 2.27 and they are still experimental. The API has changed several times over the past few months. If you adopt them early, pin your versions and budget time for migrations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does Nuxt 5 offer?
&lt;/h2&gt;

&lt;p&gt;Nuxt 5 doesn't have an answer to remote functions. Most of the confirmed work is in the underlying structure. A new version of Nitro, a new Vite integration, and framework internals. The &lt;a href="https://nuxt.com/docs/getting-started/upgrade" rel="noopener noreferrer"&gt;upgrade guide&lt;/a&gt; walks through what to expect, and it's mostly foundation work rather than new application-level APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  My verdict
&lt;/h2&gt;

&lt;p&gt;I'm sticking with Nuxt. I love the API routes pattern, I love Vue, and nothing in this demo is a reason to rewrite an existing app. You can return updated data from a mutation today and skip the second request yourself if it matters.&lt;/p&gt;

&lt;p&gt;But I do miss server actions. Frameworks like SvelteKit, Next, and TanStack Start all have some version of a typed, non-public server function you can call from a component. I hope Nuxt adds something like it in a future update, beyond the server components they have now.&lt;/p&gt;

&lt;p&gt;If you're starting a SvelteKit project and can live with an experimental API, try remote functions first. The single-flight mutation is awesome, and the types crossing the boundary for free is a great developer experience.&lt;/p&gt;

&lt;p&gt;Which side are you on: explicit API routes, or remote functions that generate the transport for you? Let me know in the comments if you agree or disagree. &lt;/p&gt;

&lt;p&gt;BTW, I used &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; for all my research for this post and video! Check it out , it's an amazing harness!&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>sveltekit</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Used Spec-Driven Development to Build a Game (TanStack Start)</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Tue, 14 Jul 2026 22:26:53 +0000</pubDate>
      <link>https://dev.to/erikch/how-i-used-spec-driven-development-to-build-a-game-a5p</link>
      <guid>https://dev.to/erikch/how-i-used-spec-driven-development-to-build-a-game-a5p</guid>
      <description>&lt;p&gt;I've been deep in spec-driven development (SDD) lately, and I want to walk through what it actually is, then show you how I used it to build a &lt;a href="http://programwitherik.com/kirogame" rel="noopener noreferrer"&gt;game&lt;/a&gt; for &lt;a href="https://kiro.dev/birthday/" rel="noopener noreferrer"&gt;Kiro's&lt;/a&gt; first birthday.&lt;/p&gt;

&lt;p&gt;If you haven't run into spec-driven development, or SDD, don't worry, I got you. We'll cover what it is, where it helps, where it's overkill, and how I used it to make this game!&lt;/p&gt;

&lt;p&gt;Would you like to watch a video instead? Check it out!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/_D-QDJAXMKQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is spec-driven development?
&lt;/h2&gt;

&lt;p&gt;SDD breaks down into three phases, and all of this happens before any code gets written.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz890nfmi2bpt62xr5et5.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz890nfmi2bpt62xr5et5.png" alt="SDD phases" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First you create a requirements document. Then you have your AI editor turn that into a design document. Then, from the requirements and the design, you generate a list of tasks. &lt;/p&gt;

&lt;p&gt;The most important part is that there's a human in the middle at every step. You check the requirements. You check the design. You review and edit the tasks that get created. Nothing gets built until you've signed off on the plan.&lt;/p&gt;

&lt;p&gt;This isn't set in stone, though. Sometimes the requirements and design get combined into a single document. Other times you run it in a quick mode where all three happen at once. It depends on what you're trying to do. When I work on big features I create all three documents, when I'm trying to do something quick, like a demo, I might just use some sort of quick or plan mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;Like anything else, SDD comes with trade-offs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffke4na5ou12lwuyk4vmw.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffke4na5ou12lwuyk4vmw.png" alt="SDD Tradeoffs" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thing I like most is the high first-pass success rate. If you actually do your job up front, writing a real requirements doc and a real design, the end product is a lot more likely to be what you wanted.&lt;/p&gt;

&lt;p&gt;It's also easier to review. You're breaking a big, complex feature into smaller steps, so you can check each one instead, and push up multiple smaller PRs. And it helps prevent you from going in the wrong direction. Instead of letting the agent run off in a bunch of directions and finding out way too late, you're checking the code after every step or two. &lt;/p&gt;

&lt;p&gt;The downside is the up front planning time. SDD takes more time before you see any code, so it's not meant for every task. For throwaway code or a small issue, it's not needed. On the other hand any features that takes more thought it does really well. You give up a little time at the start, and in return you get a more stable, better working app at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project: a Kiro birthday game
&lt;/h2&gt;

&lt;p&gt;Let me show you what I built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l71pzxpg2iwaxuy63sd.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9l71pzxpg2iwaxuy63sd.png" alt="Game image" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a &lt;a href="http://programwitherik.com/kirogame" rel="noopener noreferrer"&gt;rogue-like game&lt;/a&gt; where you walk around and explore a bunch of buildings. Each building represents a different point in time for Kiro, because this is part of our Kiro birthday celebration. Kiro is turning one. So one building covers when Kiro went generally available, another is the house we had at re:Invent, and so on. Visit all the houses and you unlock a bonus house/section. I thought it would be a fun way to tell Kiro's history.&lt;/p&gt;

&lt;p&gt;Under the hood it's Phaser and TanStack Start. Phaser is the gaming library doing the 2D Final Fantasy style look, and TanStack Start is the web library wrapping it all. There's music in there too, which I'll get to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used SDD to build it
&lt;/h2&gt;

&lt;p&gt;I opened up Kiro and told it to use spec mode. You can do this right in the IDE, and as of v3 of the CLI you can use spec mode there as well. Either way, it creates those three documents: requirements, design, and tasks.&lt;/p&gt;

&lt;p&gt;I gave it a prompt describing the game. Something like, I'm creating this 2D video game, Final Fantasy style, here are the different classes and names, here are the different buildings we're going to have. From that it generated the requirements in EARS format, which is the classic structure of a user story plus acceptance criteria. As I mentioned earlier, I double checked it at every stage to make sure the docs it created were correct.&lt;/p&gt;

&lt;p&gt;From the requirements it built a design. You can skip this part if you'd rather keep everything in one doc, but I like having it. The design got into the technical details. Phaser as the TypeScript library for the front end, TanStack Start with useEffect all over the place, the high-level structure, the scenes, the boundaries, the game shell. &lt;/p&gt;

&lt;p&gt;Then came the task list, with every single task broken out. I told it to build an MVP first, so it took the full list and pulled the four most important tasks to the front. I went in and made sure that ordering made sense, had it implement those first four, looked at the output, and confirmed it held up before moving on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools that made it easier
&lt;/h2&gt;

&lt;p&gt;A few things helped a lot here.&lt;/p&gt;

&lt;p&gt;I used a couple of MCP servers. Context7 to grab the Phaser documentation, so the agent wasn't guessing at the API. And the Chrome browser MCP extension, so Kiro could open the app, look at it, and make sure it matched the requirements.&lt;/p&gt;

&lt;p&gt;I also ran auto mode. I bounced between models, and while Claude 4.8 is great, it does chew through a lot of credits. Auto mode did really well, especially paired with SDD. That's one of the underrated parts of this approach. When everything is already written down in the spec, you don't need the greatest state-of-the-art model to get what you need. Auto mode was more than enough for most of this.&lt;/p&gt;

&lt;p&gt;On top of that I used some skills. The &lt;a href="https://github.com/aws/agent-toolkit-for-aws" rel="noopener noreferrer"&gt;AWS Agent Toolkit&lt;/a&gt;, plus the Amplify and serverless skills. I ran &lt;code&gt;/aws-amplify&lt;/code&gt; and it helped me get the game hosted on Amplify hosting. For the music I used ElevenLabs. I had a scripts folder wired up to a bunch of services, PixelLab for art, ElevenLabs for audio, even Hugging Face. All of it driven from inside Kiro, with a &lt;code&gt;.env&lt;/code&gt; file holding the keys, and it generated the assets for me too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's the basic version of my spec-driven development workflow.  It's more planning up front, but for a real feature it pays off.&lt;/p&gt;

&lt;p&gt;If you want to try the game, check it &lt;a href="http://programwitherik.com/kirogame" rel="noopener noreferrer"&gt;out&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Do you use spec-driven development in your day-to-day? I'm curious what your experience has been. Take care, and thanks for reading.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>kiro</category>
      <category>programming</category>
    </item>
    <item>
      <title>TanStack Start vs Nuxt: One Framework to rule them all?</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Tue, 07 Jul 2026 21:38:55 +0000</pubDate>
      <link>https://dev.to/erikch/tanstack-vs-nuxt-one-framework-to-rule-them-all-4acl</link>
      <guid>https://dev.to/erikch/tanstack-vs-nuxt-one-framework-to-rule-them-all-4acl</guid>
      <description>&lt;p&gt;I love Nuxt and I really like TanStack Start. But which one is better? Or are they about the same?&lt;/p&gt;

&lt;p&gt;And if they are about the same, does it do anything my Nuxt setup can't, and is that worth leaving Vue for React? So I decided to build the same app in both frameworks and take a look. Read on below to find out!&lt;/p&gt;

&lt;p&gt;If you'd rather watch a video, check out the video on the same topic!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/DBLXThKhTsY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;In both frameworks I built a small GitHub user lookup app. You type a username, the profile gets fetched on the server, and the username lands in the URL as a &lt;code&gt;?user=&lt;/code&gt; query param so the result is shareable. Type &lt;code&gt;ErikCH&lt;/code&gt;, hit enter, and the card renders. Refresh the page and it's still there. It has the same behaviour so the difference lies in the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Difference one: server functions vs server routes
&lt;/h2&gt;

&lt;p&gt;On the Nuxt side we call a server route from &lt;code&gt;useAsyncData&lt;/code&gt;. Server routes are the more idiomatic way to use Nuxt to call things on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/pages/index.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&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="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="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GithubUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~~/server/api/github.get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;user&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="dl"&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&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;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useAsyncData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github-user&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="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/github&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;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;watch&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="nx"&gt;props&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;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;props&lt;/code&gt; option on &lt;code&gt;definePageMeta&lt;/code&gt; maps the query into a typed page prop and re-runs on client navigation. &lt;code&gt;useAsyncData&lt;/code&gt; fetches when there's a username and refetches whenever it changes. The conditional that returns &lt;code&gt;Promise.resolve(null)&lt;/code&gt; skips the request on an empty query param, (or when you first load).&lt;/p&gt;

&lt;p&gt;The server route does the outbound call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/api/github.get.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;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="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;querySchema&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;user&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;trim&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing ?user=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&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;event&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;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getValidatedQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;querySchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&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;$fetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.github.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getValidatedQuery&lt;/code&gt; runs the schema and hands back a typed, validated &lt;code&gt;user&lt;/code&gt;. A bad shape, like an array or an empty string, gets a 400 before the handler runs.&lt;/p&gt;

&lt;p&gt;TanStack Start takes a different route. There's no separate API endpoint. You write a server function and call it directly from the loader.&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;// src/routes/index.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getGithubUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&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="nf"&gt;inputValidator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&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;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://api.github.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GitHub API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;GithubUser&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One API covers reads and writes. This same &lt;code&gt;createServerFn&lt;/code&gt; handles a GET here, and it would handle a POST mutation the same way, called from a loader or straight from a component. The return type flows through to the caller with no manual annotation.&lt;/p&gt;

&lt;p&gt;The route wires it up:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchSchema&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;user&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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;validateSearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loaderDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="o"&gt;=&amp;gt;&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="na"&gt;loader&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="na"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&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;getGithubUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;validateSearch&lt;/code&gt; defines the query, &lt;code&gt;loaderDeps&lt;/code&gt; narrows it to just what the loader needs, and the loader runs on navigation, a lot like &lt;code&gt;script setup&lt;/code&gt; running when a Nuxt page loads.&lt;/p&gt;

&lt;p&gt;Between the two, I really like TanStack and it's server functions. I don't have to worry about another end point being exposed to the internet, and it's simple to use. Though both work for a lot of different scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server components in both
&lt;/h2&gt;

&lt;p&gt;Both frameworks also let you skip the client entirely with server components, and both still label the feature experimental.&lt;/p&gt;

&lt;p&gt;Nuxt calls them islands. You flip one flag in &lt;code&gt;nuxt.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;componentIslands&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="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 a component named with a &lt;code&gt;.server.vue&lt;/code&gt; suffix renders only on the server. It can call &lt;code&gt;useFetch&lt;/code&gt; directly, and no client-side JavaScript ships for it. Basically you embed the server component in a client component and that's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/components/GithubUserCard.server.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GithubUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~~/server/api/github.get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/github&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;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The card fetches on the server and streams its HTML into the page. When &lt;code&gt;?user=&lt;/code&gt; changes, Nuxt refetches the island in place. The GitHub response never lands in the browser bundle, which is nice to help reduce bundle size.&lt;/p&gt;

&lt;p&gt;TanStack Start added server components too, back in April. They're also experimental, and from the docs they look pretty different from the Next.js version people know, built around a readable-stream API. I tried it and found it more complicated than the Nuxt island, so for this app I stayed on the server function.&lt;/p&gt;

&lt;p&gt;Of everything here, the server function is still my favorite. Server routes earn their place when something outside your app needs to call in, like a webhook, or some sort of callback. For the rest, the function felt better and cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed search params
&lt;/h2&gt;

&lt;p&gt;This is where TanStack Start made me a little jealous.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;validateSearch&lt;/code&gt; puts the query schema on the route itself, the &lt;code&gt;user&lt;/code&gt; param is typed everywhere it shows up. Not only in the component, but in navigation and links.&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;// Typed against the route's search schema:&lt;/span&gt;
&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&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="nf"&gt;trim&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&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="nx"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ErikCH&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;Look&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass a key that isn't in the schema and you get a red squiggle before you save. Delete the target on a &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; and the editor lists the real routes, because the routes are typed as well. I could see me using this all over, and if you're using an agent to help code, this will help catch the errors even quicker.&lt;/p&gt;

&lt;p&gt;Nuxt gets you close, with two caveats. Turn on &lt;code&gt;experimental.typedPages&lt;/code&gt; and your &lt;code&gt;&amp;lt;NuxtLink&amp;gt;&lt;/code&gt; routes are typed, so that half matches. With the &lt;code&gt;props&lt;/code&gt; plus Zod pattern above, the &lt;code&gt;user&lt;/code&gt; value reaching your component is typed and validated too. What Nuxt doesn't give you yet is the query itself typed on the link. You can type the prop, but not the &lt;code&gt;?user=&lt;/code&gt; on a &lt;code&gt;&amp;lt;NuxtLink&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are on the Nuxt team, does anyone know a workaround? Leave a comment? I'd love a typed-query-params story on the router in Nuxt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What they actually share
&lt;/h2&gt;

&lt;p&gt;Interesting enough, both of these frameworks share a lot. Both build on Vite, so both dev servers start are super fast. Both run on Nitro for the server layer, which adds a lot of flexibility when deploying to different places. Overall in that sense they are very similar.&lt;/p&gt;

&lt;p&gt;One practical thing to weigh. As of this recording TanStack Start is still a Release Candidate (RC), so expect a few rough edges and bugs. Nuxt is on 4.4.x, production-hardened, with a modules ecosystem for images, i18n, SEO, and a lot that TanStack Start doesn't have yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=DBLXThKhTsY" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs" rel="noopener noreferrer"&gt;TanStack Start docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/blog/announcing-tanstack-start-v1" rel="noopener noreferrer"&gt;TanStack Start v1 Release Candidate announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/server-components" rel="noopener noreferrer"&gt;TanStack Start server components guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vite.dev" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nitro.build" rel="noopener noreferrer"&gt;Nitro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;So, should a Nuxt developer switch? For me, no, at least not today. I'm staying on Nuxt. I like Vue, the framework is mature, and TanStack features are nice, but not enough for me to move over (specially since it's still React).&lt;/p&gt;

&lt;p&gt;If you're already a React developer, or starting something new in React, TanStack Start is genuinely fun and I'd reach for it. React fans will feel at home in TanStack Start, Vue fans in Nuxt. I'll keep an eye on TanStack Start and pull it out for the odd project every now and then.&lt;/p&gt;

&lt;p&gt;One last note. I did all the research for the video and this post in &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an agent that works on the IDE, CLI and Web. &lt;/p&gt;

&lt;p&gt;If you've run both, tell me which way you'd go in the comments.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>tanstack</category>
      <category>vue</category>
      <category>react</category>
    </item>
    <item>
      <title>Loop Engineering: Do Frontend and Fullstack Devs Actually Need It?</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Tue, 30 Jun 2026 17:59:18 +0000</pubDate>
      <link>https://dev.to/erikch/loop-engineering-do-frontend-and-fullstack-devs-actually-need-it-48eb</link>
      <guid>https://dev.to/erikch/loop-engineering-do-frontend-and-fullstack-devs-actually-need-it-48eb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I keep hearing the term loop engineering. It's all over my feed, every AI newsletter, half the dev videos I open. So I had the same question you probably have. Is this actually important, and do I need to learn it as a frontend or fullstack developer?&lt;/p&gt;

&lt;p&gt;I spent some time with it and made a video breaking down what it is and where it fits. This post is the written version, with a practical example you can copy.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Q1tgkuCcQBo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What loop engineering actually is
&lt;/h2&gt;

&lt;p&gt;Here's how I think about it. You give your coding agent a goal, and it runs over itself again and again, almost recursively, until that goal is met.&lt;/p&gt;

&lt;p&gt;You can also frame it as four parts. You give the agent a trigger or a system prompt with a clear ending state. It acts on that trigger. It observes to check whether the work is actually done. If it hits the stop condition, it stops. If it doesn't, it goes around again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft007ezbk7lnn3ddi1srv.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft007ezbk7lnn3ddi1srv.png" alt="Anatomy of a loop" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's basically it. The idea is that you are defining a goal and  a stop condition, then letting the agent close the gap on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's the agentic loop you already know
&lt;/h2&gt;

&lt;p&gt;If you've built anything with agents, this should feel familiar. It's the classic agentic loop. You give an agent a set of tools, and it uses those tools with its own reasoning to reach an answer.&lt;/p&gt;

&lt;p&gt;Say I want to research the latest tech trends. I give the agent a tool to search Google and a tool to search Reddit. On the first pass it searches Google and pulls what it finds. That isn't enough, so it loops again and pulls from Reddit. Then it combines both and hands the answer back to me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt9tocg85rfm4hubn6eu.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt9tocg85rfm4hubn6eu.png" alt="Agentic Loop" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You've seen the same thing in a chatbot. Ask it for the weather and it might call a few tools, look at the context of your question, and come back with what you asked for. Loop engineering is that pattern, pointed at your actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical example: fixing a PR on a loop
&lt;/h2&gt;

&lt;p&gt;Let me show you where this earns its place day to day.&lt;/p&gt;

&lt;p&gt;I had a pull request that I pushed up using &lt;a href="https://kiro.dev/web" rel="noopener noreferrer"&gt;Kiro Web&lt;/a&gt;. It connected straight to my GitHub repo, I gave it an action, and it opened the PR for me. You can do this with Claude Code, Cursor, and other agents too.&lt;/p&gt;

&lt;p&gt;Then I noticed some tests were failing in my CI/CD pipeline. My GitHub Actions were red. So I gave it one prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fix all issues on the PR. Keep going until it's all fixed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence is the stop condition. Kiro went to work in the background, running again and again, fixing the failures and looping until everything passed. I didn't babysit it through each round.&lt;/p&gt;

&lt;p&gt;Tests are a great place to start with this, because the pass or fail signal gives the loop a clear way to check its own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automations are the other big use case
&lt;/h2&gt;

&lt;p&gt;The other place loops shine is on a schedule. &lt;a href="https://addyosmani.com/blog/loop-engineering/" rel="noopener noreferrer"&gt;Addy Osmani&lt;/a&gt; wrote a great article on loop engineering where he points out that automations running on a schedule, doing discovery and triage on their own, are a strong fit for this.&lt;/p&gt;

&lt;p&gt;If you can find work that a cron job or a scheduled tool can kick off every day, that's a candidate. A couple of examples:&lt;/p&gt;

&lt;p&gt;At the end of every day, run all the tests, and if anything is broken, let the agent fix it. Or keep your documentation current. The loop checks the docs against the latest code and keeps going until they match.&lt;/p&gt;

&lt;p&gt;If you use Claude Code, there's a &lt;code&gt;/loop&lt;/code&gt; command and a &lt;code&gt;/goal&lt;/code&gt; command worth knowing. They let you set up in-session scheduled work, and &lt;code&gt;/goal&lt;/code&gt; lets you set a persistent, verifiable objective instead of a single one-off task. You can do the same thing in Kiro and other tools, you just have to be more explicit and write your own system prompts to drive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;Now the part where I push back a little.&lt;/p&gt;

&lt;p&gt;Don't tear up your whole workflow to jam loops in everywhere. There are real spots where they make sense, especially when your agent supports them and you've found an automation worth setting up. But I still think spec-driven development and plain back-and-forth vibe coding will solve most of your problems day to day. Loops are a tool to reach for in the right moment, not a religion.&lt;/p&gt;

&lt;p&gt;If you want to go deeper, three people shaped how I think about this. Kent C. Dodds made an excellent video on what he's discovered and how he works this way. Theo did a great one too. And Addy Osmani's article on loop engineering is worth your time.&lt;/p&gt;

&lt;p&gt;So I'll ask you what I asked at the end of the video. On your next project, are you going to try loop engineering? Or is it just hype? Let me know.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=jCZKiHrUNT4" rel="noopener noreferrer"&gt;Kent C. Dodds on loop engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=iJVJwmCKW9o" rel="noopener noreferrer"&gt;Theo (t3.gg) on loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://addyosmani.com/blog/loop-engineering/" rel="noopener noreferrer"&gt;Addy Osmani — Loop Engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/web/" rel="noopener noreferrer"&gt;Kiro Web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>3 Tools That Make AI Suck Less at Coding</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 22 Jun 2026 00:47:45 +0000</pubDate>
      <link>https://dev.to/erikch/3-tools-that-make-ai-suck-less-at-coding-4c3</link>
      <guid>https://dev.to/erikch/3-tools-that-make-ai-suck-less-at-coding-4c3</guid>
      <description>&lt;p&gt;I've completely updated my workflow, and I use AI coding agents for everything. Writing code, testing, and just helping me be more productive. However, AI isn't perfect and often after a long back-and-forth session I'll have working code, but it will have a few code smells. Duplicated code, dead-end code, lengthy functions are common. One way to fix that is to add all these problems to an AGENTS.md file and that works. But that's not always possible, depending on the codebase and team I'm working with.&lt;/p&gt;

&lt;p&gt;To that end, I went looking for tools that could help me detect and fix these issues. And so after some research I found three tools that could work almost as a pipeline. One finds these issues, one reviews the changes before they ever reach a pull request, and one helps me evaluate my agents. I use &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; for most of my day to day, so I wired all of these into that loop, but they run with whatever agent you already have.&lt;/p&gt;

&lt;p&gt;If you'd rather watch than read, here's the full walkthrough:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/NMJ_CfB11x0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This post covers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finding dead code and duplication with Fallow&lt;/li&gt;
&lt;li&gt;Reviewing the AI's diff with CodeRabbit before you open a PR&lt;/li&gt;
&lt;li&gt;Measuring whether the output improves with Kiln&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20 or newer, so you have &lt;code&gt;npx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A JavaScript or TypeScript project, ideally one with some AI-generated code in it&lt;/li&gt;
&lt;li&gt;A coding agent like &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, Claude Code, or Cursor for the review-loop parts&lt;/li&gt;
&lt;li&gt;Git, for the diff-based workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Find the mess: Fallow
&lt;/h2&gt;

&lt;p&gt;The first problem I've seen often is that AI can generate a mess of code. It will duplicate a block, leave an orphaned file behind, and grow a function to a thousand lines without ever flagging it, because each individual edit looked reasonable in isolation. You need a tool that reads the whole codebase at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/fallow-rs/fallow" rel="noopener noreferrer"&gt;Fallow&lt;/a&gt; is the one I reached for. It's static codebase intelligence for JS and TS, and it works a lot like ESLint, except it's built around these common AI code issues. You can run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fallow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you a full report. The sections I care about most are dead code, duplication, and complexity. Dead code finds files that aren't reachable from any entry point, exports nothing imports, and dependencies you've stopped using. &lt;strong&gt;When I ran this on my vibe-coded chess app (see video) it found dozens of these problems&lt;/strong&gt;  Duplication shows you the exact line ranges that got copied between two places. Complexity scores each function on how branchy and how hard to test it is, then ranks your worst offenders so you know where to start.&lt;/p&gt;

&lt;p&gt;There's a fix command for most things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx fallow fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather have an agent fix it you can use a JSON output and the skill. Run it with &lt;code&gt;--json&lt;/code&gt; and you get structured findings an agent can read and act on. Fallow also ships an agent skill you can install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add fallow-rs/fallow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that's in place, I can tell &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; to build a feature and then run Fallow on its own work to clean up after itself. It catches the duplicate block it just wrote and removes it before I even see it. You can also drop &lt;code&gt;fallow audit&lt;/code&gt; into CI so it compares your branch against main and only flags what your change introduced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Knip
&lt;/h3&gt;

&lt;p&gt;If Fallow is more than you need, &lt;a href="https://knip.dev" rel="noopener noreferrer"&gt;Knip&lt;/a&gt; does the dead-code half on its own and has been around longer. It finds unused files, dependencies, and exports in JS and TS projects in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx knip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've found knip to be a little slower then Fallow. YMMV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the diff: CodeRabbit
&lt;/h2&gt;

&lt;p&gt;Finding dead code is great, but what about reviews?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; puts a review step back in. It's AI code review that runs on your diff, and the version I like runs locally from the CLI so it happens before the PR exists. Install it with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://cli.coderabbit.ai/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point it at your uncommitted changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;coderabbit review &lt;span class="nt"&gt;--plain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads the diff and comes back with specific findings. It also hands you a fix prompt you can paste straight back into your agent, so the loop closes quickly.&lt;/p&gt;

&lt;p&gt;Jack Herrington made the point in his video that lower-end models get noticeably more useful once CodeRabbit is in the loop, because the review catches what the cheaper model missed. That makes sense to me. The agent writes, CodeRabbit reviews, the agent fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the improvement: Kiln
&lt;/h2&gt;

&lt;p&gt;The first two tools clean up individual changes. This last one answers a bigger question. When I tweak a prompt, swap a model, or add a skill, is my agent's output actually better, or does it just feel better because the last three runs went well?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Kiln-AI/Kiln" rel="noopener noreferrer"&gt;Kiln&lt;/a&gt; is the tool I use to stop guessing. It's built for creating and evaluating AI systems, so you set up datasets, run evals, and compare results across changes. &lt;/p&gt;

&lt;p&gt;For my use case, I connected it to some output a LLM was producing. I ran an eval on it to make sure it was as I expected. When I added the Fallow skill, I could iterate on the system prompt and strings. It's the same instinct behind writing tests for your code, pointed at the thing generating the code.&lt;/p&gt;

&lt;p&gt;This is the least flashy of the three and the one I'd skip first if you're in a hurry. But if you're serious about tuning a workflow you'll use every day, measuring beats vibes every time.&lt;/p&gt;

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

&lt;p&gt;You don't have to use all three, so here are some good guide lines for each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your agent leaves dead code and duplication everywhere, start with Fallow, or Knip if you want something smaller.&lt;/li&gt;
&lt;li&gt;If you need more code reviews, add CodeRabbit and let it review before you do.&lt;/li&gt;
&lt;li&gt;If you're tuning prompts, models, or skills and want proof you're improving, set up Kiln.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep in mind these tools don't replace knowing what good code looks like. They raise the floor on what your AI ships so the version that lands in your editor is closer to something you'd have written yourself. That's extremely important. AI made writing code fast, and it made reviewing code the bottleneck. These three move that bottleneck.&lt;/p&gt;

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

&lt;p&gt;AI compresses how long it takes to generate code and stretches how long it takes to trust it. Fallow and Knip find the mess it leaves behind, CodeRabbit reviews the change before it slips past you, and Kiln tells you whether any of your tuning is working. &lt;/p&gt;

&lt;p&gt;If you want to see these running inside a real agent loop, I walk through all of them in the &lt;a href="https://www.youtube.com/watch?v=NMJ_CfB11x0" rel="noopener noreferrer"&gt;companion video&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fallow-rs/fallow" rel="noopener noreferrer"&gt;Fallow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://knip.dev" rel="noopener noreferrer"&gt;Knip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Kiln-AI/Kiln" rel="noopener noreferrer"&gt;Kiln&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The most popular AI coding skills right now</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 15 Jun 2026 17:04:07 +0000</pubDate>
      <link>https://dev.to/aws/the-most-popular-ai-coding-skills-right-now-4183</link>
      <guid>https://dev.to/aws/the-most-popular-ai-coding-skills-right-now-4183</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;It's crazy to me that some GitHub repos, that were just created in the last year, have more stars then some of the most popular programming frameworks out there. At the time of writing, &lt;a href="https://github.com/obra/superpowers" rel="noopener noreferrer"&gt;Superpowers&lt;/a&gt;, is around 226,000, ahead of the Vue repo and way ahead of Next.js. A separate repo based on Andrej Karpathy's coding advice has 174,000. And they aren't even code. They're folders of markdown files that tell your AI coding agent how to write software better.&lt;/p&gt;

&lt;p&gt;In other words these are agent skills, and they've blown up over the past year into one of the most-starred new categories of developer tooling. The format started at Anthropic, a &lt;code&gt;SKILL.md&lt;/code&gt; file with a short description and a set of instructions, and now it works in Claude Code, &lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; and all your other favorite coding agents.&lt;/p&gt;

&lt;p&gt;This post is a tour of some of the skills I find most interesting that people are using everyday. I'll be in Kiro for most of this, but these run in whatever agent you already have, with install paths that vary per agent.&lt;/p&gt;

&lt;p&gt;This post covers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What an agent skill actually is&lt;/li&gt;
&lt;li&gt;The big workflow skills (Superpowers, Karpathy, Matt Pocock, GStack, GSD)&lt;/li&gt;
&lt;li&gt;Framework skills and the "ships with your package" model&lt;/li&gt;
&lt;li&gt;The Agent Toolkit for AWS&lt;/li&gt;
&lt;li&gt;How to pick one and try it safely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you like, you can watch a &lt;a href="https://youtu.be/enWhBCyU36U?si=wySkthIQYr6592m5" rel="noopener noreferrer"&gt;full video&lt;/a&gt; on the subject instead! Also if nothing else, please check out the &lt;a href="https://github.com/aws/agent-toolkit-for-aws" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt;! More information below. &lt;em&gt;Full disclosure, I'm a developer advocate for AWS&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an agent skill?
&lt;/h2&gt;

&lt;p&gt;A skill is a directory with a &lt;code&gt;SKILL.md&lt;/code&gt; file inside it. The file has a name, a one-line description, and instructions. These instructions include which steps to follow, which APIs to call, which mistakes to avoid and how to check the result.&lt;/p&gt;

&lt;p&gt;Skills should only run when needed. Your agent reads the short description, decides if the skill is relevant to what you asked, and only then loads the full instructions. Reference files load on demand after that. So you can have fifty skills installed and your context window stays clean, because the agent pulls in the two it needs and ignores the rest. &lt;em&gt;In practice, I've found I still need to manually tell my agent to run the skills by using the &lt;code&gt;/&lt;/code&gt; command and the skill name. YMMV&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The big workflow skills
&lt;/h2&gt;

&lt;p&gt;Most of the popular general-purpose skills are trying to fix the problem agents often have, going in the wrong direction. Agents when left alone often rush in, touch too many files, and they skip planning. These five take different approaches to slowing it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Superpowers (obra/superpowers)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/obra/superpowers" rel="noopener noreferrer"&gt;Superpowers&lt;/a&gt; is the most-starred of the bunch and the most opinionated. It is a full development methodology that triggers automatically. It really reminds me of a super powered Spec-Driven Development (SDD) workflow. &lt;/p&gt;

&lt;p&gt;The moment the agent sees you are building something, it stops and asks what you are actually trying to do. It works a spec out of the conversation, shows it to you (optionally in a web browser), and only after you sign off does it write an implementation plan. Then it hands the plan to subagents that build and review each other's work. Be aware, this can eat up a lot of tokens.&lt;/p&gt;

&lt;p&gt;It is available through the official Claude plugin marketplace, plus Codex, Cursor, Gemini CLI, and a handful of others. You can also get it working with Kiro by from &lt;a href="https://www.skills.sh/obra/superpowers/brainstorming" rel="noopener noreferrer"&gt;Skills.sh&lt;/a&gt;. Though, I'd probably skip it if you're using Kiro, since most of the SDD is already there in Kiro. &lt;/p&gt;

&lt;p&gt;If you want one system that owns the whole loop from idea to reviewed code, this is the one to reach for. In Claude Code, install it from the official plugin marketplace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin install superpowers@claude-plugins-official
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Karpathy guidelines (multica-ai/andrej-karpathy-skills)
&lt;/h3&gt;

&lt;p&gt;This one is almost the opposite. It is a short file of behavioral rules, now at &lt;a href="https://github.com/multica-ai/andrej-karpathy-skills" rel="noopener noreferrer"&gt;174,000 stars&lt;/a&gt;. Andrej Karpathy did not write it, despite the name, which I find really funny. It is a community-built file derived from a January post where Karpathy listed the ways LLM coding agents tend to fail, and the repo later moved to the multica-ai org.&lt;/p&gt;

&lt;p&gt;The rules come down to four simple ideas. Think before coding, keep it simple, make surgical changes, and stay focused on the goal. If Superpowers feels like too much process for you, this is the low-commitment starting point. It is a single &lt;code&gt;CLAUDE.md&lt;/code&gt; (or AGENTS.md, or you can use it as a Skill), so the simplest install is to drop it into your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; CLAUDE.md https://raw.githubusercontent.com/multica-ai/andrej-karpathy-skills/main/CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Personally, I'm not sure if this is worth it in my own testing. However, it's easy enough to add it to your own &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt; so give it a shot though and make your own opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Matt Pocock's skills (mattpocock/skills)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/mattpocock/skills" rel="noopener noreferrer"&gt;Matt Pocock's collection&lt;/a&gt; sits in the middle and takes a deliberate stance against the all-in-one systems. In fact, he's often said he doesn't like SDD, and thinks it's too cumbersome. His framing is that approaches which own your entire process also take away your control and make bugs in that process hard to fix. So his skills are small, composable, and easy to adapt. You install the ones you want with the Vercel &lt;code&gt;skills&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills@latest add mattpocock/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;His most popular one, and the one I like the most, is the &lt;code&gt;grill-me&lt;/code&gt;, which makes the agent interview you about what you are building before it writes anything. There is a more powerful version, &lt;code&gt;grill-with-docs&lt;/code&gt;, that does the same interview and also builds a shared-language document for your project. The repo's pitch is that this shared-language document pays off across sessions. There is also &lt;code&gt;tdd&lt;/code&gt; for a red-green-refactor loop, &lt;code&gt;diagnose&lt;/code&gt; for debugging, and &lt;code&gt;improve-codebase-architecture&lt;/code&gt; for rescuing a project that has turned into spaghetti.&lt;/p&gt;

&lt;p&gt;Another skill called &lt;code&gt;caveman&lt;/code&gt; helps reduce token costs. I find the &lt;code&gt;caveman&lt;/code&gt; and &lt;code&gt;grill-me&lt;/code&gt; skills the most useful. Though, I find &lt;code&gt;grill-me&lt;/code&gt; a little annoying after a while, when I just want to get something done. &lt;/p&gt;

&lt;h3&gt;
  
  
  GStack (garrytan/gstack)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/garrytan/gstack" rel="noopener noreferrer"&gt;GStack&lt;/a&gt; is Garry Tan's Claude Code setup, and at nearly 110,000 stars it is one of the most popular skill collections out there. Instead of one mode that handles everything, it gives the agent distinct roles, each behind its own slash command. It has product vision, designer, engineering manager, release manager, doc engineer, QA, and post-launch retrospective. It is the same genre as Superpowers, a full workflow, but organized around the roles on a software team rather than a sequence of phases. If you think in terms of "who would handle this part," GStack maps to that mental model nicely. It installs with a clone and a setup script (pass &lt;code&gt;--host kiro&lt;/code&gt;, &lt;code&gt;--host cursor&lt;/code&gt;, and so on for other agents):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--single-branch&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.claude/skills/gstack &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also works with Kiro nicely.&lt;/p&gt;

&lt;h3&gt;
  
  
  GSD Core (open-gsd/gsd-core)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/open-gsd/gsd-core" rel="noopener noreferrer"&gt;GSD Core&lt;/a&gt; (Git. Ship. Done.) is a spec-driven system that pitches itself as a fix for context rot, the slow drop in quality as your context window fills up during a long session. It breaks work into atomic plans that run in fresh context windows, keeping the main session light while subagents do the heavy lifting. It targets more than a dozen agents. The project moved from the old &lt;code&gt;gsd-build&lt;/code&gt; org to its current home at &lt;code&gt;open-gsd/gsd-core&lt;/code&gt;, and the installer prompts for your runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @opengsd/gsd-core@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While using this, I found that it burned through tokens fast. It also takes over your whole workflow, that I disliked. Still, it's worth trying out to see if it works well for your own personal projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework skills and the "ships with your package" model
&lt;/h2&gt;

&lt;p&gt;The skills above are framework-agnostic. The more interesting development for frontend folks is skills that know your specific stack.&lt;/p&gt;

&lt;p&gt;The Vue community moved early here. &lt;a href="https://github.com/vuejs-ai/skills" rel="noopener noreferrer"&gt;vuejs-ai/skills&lt;/a&gt; is a community Vue 3 skills project (the README notes the author may propose transferring it to the Vue org), and its &lt;code&gt;vue-best-practices&lt;/code&gt; skill is a structured workflow covering the Composition API, reactivity, and the patterns you want a model to follow. &lt;a href="https://github.com/antfu/skills" rel="noopener noreferrer"&gt;Anthony Fu's collection&lt;/a&gt; covers Vue, Vite, Nuxt, Pinia, VueUse, and Vitest. Both install through the same CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add vuejs-ai/skills
npx skills add antfu/skills &lt;span class="nt"&gt;--skill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The newer idea is shipping skills inside the package itself. Three things are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/skilld-dev/skilld" rel="noopener noreferrer"&gt;skilld&lt;/a&gt; generates version-aware skills from the npm dependencies you already have, pulling from docs, release notes, and GitHub issues. It solves the problem where a model's training cutoff means it writes last year's version of your library.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tanstack.com/intent" rel="noopener noreferrer"&gt;TanStack Intent&lt;/a&gt; (&lt;code&gt;@tanstack/intent&lt;/code&gt;) flips that around for maintainers. It lets library authors generate and ship &lt;code&gt;SKILL.md&lt;/code&gt; files inside their npm package, versioned with each release, so the skill updates when the library does. It launched recently and ties straight into the TanStack ecosystem.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/vercel-labs/skills" rel="noopener noreferrer"&gt;Vercel skills CLI&lt;/a&gt; is the &lt;code&gt;npx skills&lt;/code&gt; tool that installs any of this across 70-plus agents. If you use Kiro, it loads skills from &lt;code&gt;.kiro/skills/&lt;/code&gt; automatically with no config, which makes it an easy way to try things on camera or in a real project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is each of those three:&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;# Generate version-aware skills from your installed npm dependencies&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; skilld

&lt;span class="c"&gt;# Library maintainers: scaffold SKILL.md files inside your own package&lt;/span&gt;
npx @tanstack/intent scaffold

&lt;span class="c"&gt;# The universal installer used throughout this post — pattern is `npx skills add &amp;lt;owner/repo&amp;gt;`&lt;/span&gt;
npx skills add vercel-labs/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Agent Toolkit for AWS
&lt;/h2&gt;

&lt;p&gt;AWS ships skills too, in the same open format, so they run in Kiro, Claude Code, Cursor, and Codex.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/agent-toolkit/latest/userguide/skills.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Agent Toolkit for AWS&lt;/a&gt; was announced in May 2026 and is the officially supported way to bring AWS expertise into your agent. It pairs plugins for Claude Code and Codex with a curated collection of skills and the &lt;a href="https://aws.amazon.com/blogs/aws/the-aws-mcp-server-is-now-generally-available/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS MCP Server&lt;/a&gt;, which reached general availability on May 6, 2026. The toolkit covers areas like deployment, serverless, and database work. Setup is per-agent. In Kiro you add the AWS MCP Server through your MCP config and then pull in skills with &lt;code&gt;npx skills&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install AWS-curated skills via the universal CLI&lt;/span&gt;
npx skills add aws/agent-toolkit-for-aws/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are building on AWS,  you should download this. Even if you are not using AWS I would download it for the future. Because with this the agent loads service-specific guidance instead of guessing at an API it half-remembers from training.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to pick one and try it safely
&lt;/h2&gt;

&lt;p&gt;Personally, I would use Matt Pococks skills and try out one of the other larger skill workflows like Superpowers. See if it works for you. Then pair it with a framework specific skills as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last thing, and it matters. A skill is instructions you are dropping into your agent's context, so treat it the way you treat an npm dependency. Read the &lt;code&gt;SKILL.md&lt;/code&gt; before you install it. A repo with 100,000 stars is popular, but stars are a popularity signal, not a quality review. Skim the file, see what it tells your agent to do, and keep the ones that match how you actually work.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Agent skills went from a quiet Anthropic format to repos with more stars than the frameworks we build on, and it makes sense why. They take the patterns a good engineer carries in their head and hand them to the model at the start of every task. The big workflow skills give you structure while the framework skills give you correctness, and the AWS plugins give you service-specific knowledge you would otherwise paste in by hand.&lt;/p&gt;

&lt;p&gt;My advice: install one, read what it actually tells the agent, and see if the output matches how you'd have built it yourself. That's the real test, and it's the one only you can run. If you want to see these in action, I walk through them in the companion video.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>aws</category>
      <category>programming</category>
    </item>
    <item>
      <title>TanStack Start Is Kind of a Big Deal</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 08 Jun 2026 17:06:43 +0000</pubDate>
      <link>https://dev.to/erikch/tanstack-start-is-kind-of-a-big-deal-4nec</link>
      <guid>https://dev.to/erikch/tanstack-start-is-kind-of-a-big-deal-4nec</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;People keep telling me TanStack Start is kind of a big deal, and I wanted to know if that holds up or not. I've been spending a lot of time at conferences lately, and TanStack Start comes up quite often in conversation. The community is split if server components is the right answer. TanStack Start has gone the opposite direction with it's clean client-side first components approach, with lot so ways to call server-side code, even in the same component.&lt;/p&gt;

&lt;p&gt;I'm a Vue and Nuxt person most days, so I'm not here to dunk on anyone's framework. What I want to figure out is simpler: are there specific things TanStack Start does that Next.js and Nuxt don't, and are they good enough to switch for?&lt;/p&gt;

&lt;p&gt;After some research I have come up with three things I really like about TanStack Start. These things alone aren't probably enough for me to switch, but I'm getting close.&lt;/p&gt;

&lt;p&gt;If you'd like to watch a video instead, check this out!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/I59bnwcxmAo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22+&lt;/li&gt;
&lt;li&gt;Comfort with React and TypeScript&lt;/li&gt;
&lt;li&gt;You do not need any Next.js or TanStack experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;A GitHub user lookup. You type a username, the app fetches that user from the GitHub API on the server, and renders their profile. It's a perfect app to show these three features.&lt;/p&gt;

&lt;p&gt;You can find the full code in the &lt;a href="https://github.com/ErikCH/tanstack-start-nextjs-killer-demo" rel="noopener noreferrer"&gt;demo repo&lt;/a&gt;. Let's start by creating the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the app
&lt;/h2&gt;

&lt;p&gt;TanStack has a CLI, so scaffolding is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @tanstack/cli@latest create my-app &lt;span class="nt"&gt;--framework&lt;/span&gt; React
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It asks about a package manager and a few add-ons, then sets up a project on Vite with file-based routing. Run it:&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="nb"&gt;cd &lt;/span&gt;my-app
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dev server was ready in under a second on my machine. That Vite-powered startup is really nice, and it's the same Vite speed I covered in my earlier Vite videos.&lt;/p&gt;

&lt;p&gt;The structure is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── routes/
│   ├── __root.tsx      # the document shell
│   └── index.tsx       # the home route
├── router.tsx          # router config
└── routeTree.gen.ts    # auto-generated, don't edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the three features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 1: One server function for reads and writes
&lt;/h2&gt;

&lt;p&gt;Let me be fair up front, Next.js can also call server code directly. Next has React Server Functions, and in mutation contexts those are Server Actions. You mark a function with &lt;code&gt;"use server"&lt;/code&gt; and call it from a component, no API route required. Nuxt has its own version with &lt;code&gt;server/api&lt;/code&gt; routes plus &lt;code&gt;useFetch&lt;/code&gt;, which gives you typed responses too. So "call a function on the server" is not unique.&lt;/p&gt;

&lt;p&gt;However, the difference is the constraint. Next.js Server Actions run as POST requests and are built for mutations. The Next docs themselves steer you to Server Components or Route Handlers for reading data. You &lt;em&gt;can&lt;/em&gt; call a Server Action from a client component to read, but it still goes over POST and isn't the idiomatic, cacheable GET path. &lt;/p&gt;

&lt;p&gt;TanStack Start doesn't split it this way. One primitive, &lt;code&gt;createServerFn&lt;/code&gt;, handles both a GET read and a POST mutation, and you call it the same way from anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;GithubUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="nx"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;html_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="nx"&gt;public_repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;following&lt;/span&gt;&lt;span class="p"&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;const&lt;/span&gt; &lt;span class="nx"&gt;getGithubUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServerFn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&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="nf"&gt;inputValidator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handler&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&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;GithubUser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.github.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.github+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// a token here stays on the server, never ships to the client:&lt;/span&gt;
        &lt;span class="c1"&gt;// Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" not found`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;GithubUser&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;.handler&lt;/code&gt; runs only on the server, so a token never reaches the browser. I set &lt;code&gt;method: 'GET'&lt;/code&gt; because this is a read, and I call it straight from my route loader like a normal async function. There is no route handler, or RSC boundary to think about and no endpoint string to keep in sync. (This snippet is trimmed for clarity. The version in the repo adds &lt;code&gt;encodeURIComponent&lt;/code&gt; on the input and separate handling for 404 and 403 rate-limit responses.)&lt;/p&gt;

&lt;p&gt;You can watch this happen in the browser too. Open the network tab, run a lookup, and you won't see a request to &lt;code&gt;api.github.com&lt;/code&gt; anywhere. The only call is the one to my own server. The GitHub fetch is happening server-side, which is exactly where I want it, and any token isn't leaked.&lt;/p&gt;

&lt;p&gt;I really like how types work here. I annotate the handler's return as &lt;code&gt;GithubUser&lt;/code&gt; once, and that type flows through the loader and into the component without me re-typing it at each call, and that holds whether it's a GET or a POST. Rename a field in the interface and every call that still reads the old property lights up red. (One caveat, that's compile-time propagation, not runtime validation. I cast the GitHub response with &lt;code&gt;as GithubUser&lt;/code&gt;, so if you want to prove the external JSON actually matches, you'd add a runtime schema check.) You can get there with a route handler too, with shared types or a schema validator. The difference is that TanStack infers the chain for you by default instead of asking you to wire it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 2: Search params that are actually typed
&lt;/h2&gt;

&lt;p&gt;This is the one where TanStack genuinely has an advantage as of today. Next.js gives you generic string and string-array search params, not route-local schema validation. &lt;code&gt;useSearchParams&lt;/code&gt; hands you a read-only &lt;code&gt;URLSearchParams&lt;/code&gt;, and while &lt;code&gt;typedRoutes&lt;/code&gt; plus the &lt;code&gt;PageProps&lt;/code&gt; helper have improved path, navigation, and page-prop typing, none of that validates or transforms the &lt;em&gt;values&lt;/em&gt; inside the query string the way TanStack Router does. You can get there in Next with Zod, &lt;code&gt;nuqs&lt;/code&gt;, or &lt;code&gt;next-typesafe-url&lt;/code&gt;, but it's something you add on. Nuxt can validate a route with &lt;code&gt;definePageMeta&lt;/code&gt;'s &lt;code&gt;validate&lt;/code&gt; function, which can return &lt;code&gt;false&lt;/code&gt; or a &lt;code&gt;Partial&amp;lt;NuxtError&amp;gt;&lt;/code&gt; to reject a route, but it doesn't turn &lt;code&gt;route.query&lt;/code&gt; into a typed, validated query object for your component.&lt;/p&gt;

&lt;p&gt;TanStack Router treats search params as validated route state out of the box:&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;const&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;validateSearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;search&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="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;loaderDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="o"&gt;=&amp;gt;&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="na"&gt;loader&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="na"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&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;getGithubUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Home&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;I validate &lt;code&gt;?user=&lt;/code&gt; once. After that, &lt;code&gt;Route.useSearch()&lt;/code&gt; gives me &lt;code&gt;{ user: string }&lt;/code&gt;, fully typed, anywhere in the component. The loader reads that param and runs the server function, so loading the page with &lt;code&gt;?user=ErikCH&lt;/code&gt; in the URL loads the profile directly, with no extra client wiring. The lookup is shareable and survives a refresh, and I never wrote client state to make that happen. You can plug in Zod if you want richer schemas. &lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 3: Type safety that runs end to end by default
&lt;/h2&gt;

&lt;p&gt;Typed navigation by itself isn't unique, and I want to be straight about that. Next.js has &lt;code&gt;typedRoutes&lt;/code&gt; for statically typed links, and Nuxt has typed navigation built in through &lt;code&gt;experimental.typedPages&lt;/code&gt;, plus the &lt;code&gt;nuxt-typed-router&lt;/code&gt; module for more. So all three can stop you from typoing a route.&lt;/p&gt;

&lt;p&gt;The difference is how far the chain reaches and how much setup it takes. Next's &lt;code&gt;typedRoutes&lt;/code&gt; types the path, not the search param values. Nuxt's typed pages are opt-in and cover routes and params. In TanStack it's on by default, and the same type system covers your route params, your search params, and your loader data in one connected chain.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNavigate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullPath&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;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&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="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;// typed: { user: string }&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I pass a search param that doesn't exist, or the wrong type, the type checker flags it. Vite itself transpiles TypeScript without type checking, so this is &lt;code&gt;tsc --noEmit&lt;/code&gt; (I keep it in a &lt;code&gt;typecheck&lt;/code&gt; script and run it in CI) or your editor catching it inline. And because the loader's return type flows into &lt;code&gt;Route.useLoaderData()&lt;/code&gt;, the data I render is typed by the same chain that typed the navigation. That whole path, from the server function return through the loader, the search params, and the link, is one thing instead of three features you wire up separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding in AI
&lt;/h2&gt;

&lt;p&gt;I lean on AI coding assistants like Kiro for a lot of my code, and TanStack Start is new enough that the models don't have great knowledge on it. When I asked for a server function, I'd sometimes get an older API shape back, because the training data is behind.&lt;/p&gt;

&lt;p&gt;TanStack ships a fix for exactly this, and it's the last thing I showed in the video. There's a package called TanStack Intent that wires your coding agent into current TanStack patterns. You install it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @tanstack/intent@latest &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates or updates your agent config, defaulting to &lt;code&gt;AGENTS.md&lt;/code&gt; (it can target others like &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;.cursorrules&lt;/code&gt; too), with skill-loading instructions. Your agent reads it, sees which TanStack skills are available, and pulls the current docs for whatever it's working on instead of guessing from stale training data.&lt;/p&gt;

&lt;p&gt;So I opened Kiro CLI, which picks up that &lt;code&gt;AGENTS.md&lt;/code&gt; on its own, and gave it this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please review my existing repository against the newly loaded TanStack Intent rules. Check my implementation for anti-patterns, missing edge cases, or deprecated syntax.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It worked through the skills and came back with a list, a couple of &lt;code&gt;verbatimModuleSyntax&lt;/code&gt; notes, some dev-tools setup for TanStack Start, a shell component thing. One last thing, I wasn't pinning the latest version tags in my &lt;code&gt;package.json&lt;/code&gt;. Though not really required all the time, I did like how it was looking at the &lt;code&gt;package.json&lt;/code&gt; file in general.&lt;/p&gt;

&lt;p&gt;The second guardrail is the type safety from earlier. When the AI guessed an old &lt;code&gt;createServerFn&lt;/code&gt; shape, &lt;code&gt;tsc&lt;/code&gt; and my editor flagged it right away. I didn't have to catch it in review. The types caught it for me.&lt;/p&gt;

&lt;p&gt;This is the same point I made in my Vue in the Age of AI video. AI writes more of our code now, so the frameworks that verify the AI's work for you are worth more than they used to be. &lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;Nothing to tear down for local development. Stop the dev server with Ctrl+C. If you deployed, TanStack Start uses Nitro under the hood, so you can remove whatever Node target you set up. Those hosting resources can incur charges, so tear them down if you were only testing.&lt;/p&gt;

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

&lt;p&gt;So is it kind of a big deal? I'd put it this way. If you want explicit control, fast Vite builds, and type safety that runs from the server function through search params to your links, TanStack Start is genuinely the most compelling React framework I've tried in a while. The server functions and typed search params alone are just really nice to have.&lt;/p&gt;

&lt;p&gt;It's not for everyone yet. The ecosystem is smaller than Next.js, there are fewer plugins and it's young. The TanStack CLI is still marked alpha, there are fewer production references to learn from, and the deployment and debugging knowledge isn't as standardized as Next.js. If you need the hiring pool and the deployment story Next.js has, that's a real reason to wait.&lt;/p&gt;

&lt;p&gt;But "the default React framework is finally in question" is true for the first time in years, and after building with it, I get why people are switching. If you're a Nuxt person like me, the typed search params and server functions will feel like the things you wish you had without reaching for extra modules.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/start/latest" rel="noopener noreferrer"&gt;TanStack Start docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/start-vs-nextjs" rel="noopener noreferrer"&gt;TanStack Start vs Next.js (official)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/router/latest/docs/framework/react/how-to/validate-search-params" rel="noopener noreferrer"&gt;Search params validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://inngest.com/blog/migrating-off-nextjs-tanstack-start" rel="noopener noreferrer"&gt;Inngest: Why we migrated off Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ErikCH/tanstack-start-nextjs-killer-demo" rel="noopener noreferrer"&gt;Demo repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tanstack</category>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mastering the Latest TypeScript: What's New in 6.0 (and a Peek at 7)</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Thu, 04 Jun 2026 17:01:27 +0000</pubDate>
      <link>https://dev.to/erikch/mastering-the-latest-typescript-whats-new-in-60-and-a-peek-at-7-4m8o</link>
      <guid>https://dev.to/erikch/mastering-the-latest-typescript-whats-new-in-60-and-a-peek-at-7-4m8o</guid>
      <description>&lt;p&gt;TypeScript 6.0 has been out for a few months now, and it's a bit of an unusual release. It's the last compiler that will be written in TypeScript and JavaScript. Later this year, TypeScript 7 arrives with the compiler rewritten in Go. Microsoft has seen roughly 10x speed improvements on large codebases. That makes 6.0 the bridge release. &lt;/p&gt;

&lt;p&gt;The good news is that the features I want to show you aren't going anywhere. Everything here is forward compatible with TypeScript 7. They're also just genuinely nice to use. I'll walk through five additions worth adopting today, show the tsconfig.json changes that add them, and finish with what the Go rewrite means for you.&lt;/p&gt;

&lt;p&gt;If you'd rather watch than read, the full video is here.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/koQ-ZCPYDtg"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20 or newer&lt;/li&gt;
&lt;li&gt;TypeScript 6.0 (&lt;code&gt;npm install -D typescript@6&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A code editor with TypeScript support (I'm using Kiro)&lt;/li&gt;
&lt;li&gt;Basic familiarity with &lt;code&gt;tsconfig.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Native Temporal date and time types
&lt;/h2&gt;

&lt;p&gt;The built-in JavaScript &lt;code&gt;Date&lt;/code&gt; API isn't that great. Months are zero-indexed, time zone handling is painful, and most of us reach for an API library to help. TypeScript 6 ships type support for &lt;code&gt;Temporal&lt;/code&gt;, the TC39 date/time API, so you get a modern API with no extra dependency.&lt;/p&gt;

&lt;p&gt;Creating a date is readable now. You pass the year, month, and day as named fields instead of memorizing argument order.&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;launch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;day&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="c1"&gt;// Arithmetic that reads the way you think about it&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;twoWeeksLater&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;weeks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;aMonthEarlier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;months&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time zones are really neat to use. You can format the same instant across several zones without juggling offsets 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zonedDateTimeISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;America/Los_Angeles&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;zones&lt;/span&gt; &lt;span class="o"&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;America/Los_Angeles&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;America/New_York&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;Europe/London&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;Asia/Tokyo&lt;/span&gt;&lt;span class="dl"&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;zone&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;zones&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;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zone&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="nx"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&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;And duration math, the thing that usually sends you to a library, is built in.&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;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plainDateISO&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;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Temporal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-09-01&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;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;largestUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; days until launch`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get all this without a date library, and you avoid the off-by-one month bugs that &lt;code&gt;Date&lt;/code&gt; gives you. This is the feature I expect people to adopt first.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Map.getOrInsert() and getOrInsertComputed()
&lt;/h2&gt;

&lt;p&gt;If you work with &lt;code&gt;Map&lt;/code&gt;, you know the check-then-set code you have to write. You want to insert a value only if the key isn't already there, so you write something like this.&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;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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="c1"&gt;// non-null assertion because get() can return undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things bother me there. The &lt;code&gt;if&lt;/code&gt; guard is boilerplate, and the &lt;code&gt;!&lt;/code&gt; non-null assertion exists only because &lt;code&gt;get()&lt;/code&gt; is typed to possibly return &lt;code&gt;undefined&lt;/code&gt;. TypeScript 6 adds &lt;code&gt;Map.getOrInsert()&lt;/code&gt;, which adds all of that into one call.&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;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrInsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&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="c1"&gt;// returns 1, inserts if missing&lt;/span&gt;
&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrInsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// key exists, so this is ignored&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="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// still 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks for the key, returns the existing value if present, and inserts only when the key is missing. You don't overwrite anything, and the guard and the assertion both go away.&lt;/p&gt;

&lt;p&gt;There's a companion method, &lt;code&gt;getOrInsertComputed()&lt;/code&gt;, that takes a factory function instead of a value. The function only runs when the key is actually missing.&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;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrInsertComputed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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="o"&gt;=&amp;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;computing a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// logs "computing a", inserts 1&lt;/span&gt;

&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrInsertComputed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&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="o"&gt;=&amp;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;this never runs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// key already exists, so the factory is skipped entirely&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second factory never executes. If your value is expensive to produce, a database call, a heavy calculation, parsing something large, &lt;code&gt;getOrInsertComputed()&lt;/code&gt; skips the work entirely when the key already exists. &lt;code&gt;getOrInsert()&lt;/code&gt; evaluates its argument no matter what, so reach for the computed version when the value isn't cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. RegExp.escape() for safe dynamic regex
&lt;/h2&gt;

&lt;p&gt;Building a regular expression from user input is a classic problem that we all run into. Say you take a version string and try to match it.&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;userInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0&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;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version 1.0 and build 1x0 are different&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// matches BOTH "1.0" and "1x0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matches more than you wanted. The &lt;code&gt;.&lt;/code&gt; in the input is a regex wildcard, so it matches any character, including the &lt;code&gt;x&lt;/code&gt; in &lt;code&gt;1x0&lt;/code&gt;. TypeScript 6 adds &lt;code&gt;RegExp.escape()&lt;/code&gt;, which escapes special characters in a string so it's treated literally.&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;userInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0&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;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version 1.0 and build 1x0 are different&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// matches only "1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the dot is a literal dot, and you match exactly what you meant. Any time you build a regex from a value you didn't write yourself, wrap it in &lt;code&gt;RegExp.escape()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The &lt;code&gt;#&lt;/code&gt; subpath imports (and the tsconfig that powers them)
&lt;/h2&gt;

&lt;p&gt;Deep relative imports like &lt;code&gt;../../../utils/format&lt;/code&gt; are hard to read and break when you move files. The common fix is a path alias with &lt;code&gt;@&lt;/code&gt;, but that can collide with real npm packages, since plenty of scoped packages start with &lt;code&gt;@&lt;/code&gt;. TypeScript 6 leans into Node's subpath imports, which use a &lt;code&gt;#&lt;/code&gt; prefix that can't be confused with a package name.&lt;/p&gt;

&lt;p&gt;Setup happens in two files. First, &lt;code&gt;tsconfig.json&lt;/code&gt; needs a module resolution mode that understands subpath imports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bundler"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not using a bundler, the Node-native equivalent works too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodenext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodenext"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you declare the actual mapping in &lt;code&gt;package.json&lt;/code&gt; under &lt;code&gt;imports&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"#utils/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/utils/*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"#components/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/components/*.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your imports are clean.&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;formatDate&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;#utils/format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&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;#components/Button&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;Without one of those module resolution settings, the &lt;code&gt;#&lt;/code&gt; imports won't resolve, so the tsconfig change is the part that actually unlocks the feature. If you watched my earlier &lt;a href="https://youtu.be/WbIz38_54KM?si=699kmcW7B-72Tfk7" rel="noopener noreferrer"&gt;video&lt;/a&gt; on tsconfig paths, this is the modern, standardized version of that same idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Smarter generic inference
&lt;/h2&gt;

&lt;p&gt;This one removes annotations you used to have to write by hand. Take a generic interface like this.&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;CreatePair&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;Before TypeScript 6, you usually had to pin the generic explicitly when you used it.&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;pair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreatePair&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;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;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;value&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;TypeScript 6 infers &lt;code&gt;T&lt;/code&gt; from the value you provide, so the annotation becomes optional.&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;pair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;        &lt;span class="c1"&gt;// T inferred as number&lt;/span&gt;
  &lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;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="nx"&gt;value&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;Because the compiler reads &lt;code&gt;42&lt;/code&gt; and figures out the type, you write less and still get full type safety and autocomplete everywhere. It's a small change you'll feel constantly in real code.&lt;/p&gt;

&lt;h2&gt;
  
  
  tsconfig.json changes worth making now
&lt;/h2&gt;

&lt;p&gt;Beyond the subpath imports config above, TypeScript 6 shifts a few defaults in a direction that TypeScript 7 will push as well. A few minutes auditing your config today saves friction later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;strict&lt;/code&gt; now defaults to &lt;code&gt;true&lt;/code&gt;. If you've explicitly set &lt;code&gt;strict: false&lt;/code&gt;, you're working against where the language is heading. This is a good moment to enable it or write down why you're opting out.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verbatimModuleSyntax: true&lt;/code&gt; is encouraged and replaces the older &lt;code&gt;importsNotUsedAsValues&lt;/code&gt;. It forces explicit &lt;code&gt;import type&lt;/code&gt; for type-only imports, which keeps your emitted output predictable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseUrl&lt;/code&gt; is no longer required for &lt;code&gt;paths&lt;/code&gt; to work. If you only kept &lt;code&gt;baseUrl&lt;/code&gt; around to enable path aliases, you can drop it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;moduleResolution: "node"&lt;/code&gt; is on its way out. If you're still on the legacy resolver, 6.0 is the time to move to &lt;code&gt;nodenext&lt;/code&gt; or &lt;code&gt;bundler&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's coming in TypeScript 7
&lt;/h2&gt;

&lt;p&gt;TypeScript 7 is the headline behind all of this. The compiler is being rewritten in Go (the project codename is Corsa), and the speed gains look impressive. The Go compiler parallelizes across CPU cores, so the bigger your codebase, the more you benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;TypeScript 6 isn't a flashy release, and that's the point. Temporal, the new &lt;code&gt;Map&lt;/code&gt; methods, &lt;code&gt;RegExp.escape()&lt;/code&gt;, subpath imports, and smarter inference all make day-to-day code a little cleaner, and none of it gets in your way when you move to 7. The single most valuable thing you can do today is adopt these APIs and audit your tsconfig.json so the Go-compiler upgrade is a non-event.&lt;/p&gt;

&lt;p&gt;I also built an &lt;a href="https://github.com/ErikCH/typescript-6-whats-new" rel="noopener noreferrer"&gt;experimental skill&lt;/a&gt; that helps you upgrade older TypeScript projects to 6.0. If you use Kiro, Claude Code, or another agentic editor, give it a try on a TypeScript 5 project and let me know how it goes. The link is in the video description.&lt;/p&gt;

&lt;p&gt;Watch the full walkthrough on YouTube, and drop a comment if you've started using any of these in your own projects.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>Your Default Vite Config Is Not Enough (New with Vite 8)</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 25 May 2026 17:14:13 +0000</pubDate>
      <link>https://dev.to/erikch/8-vite-config-options-every-developer-should-know-vite-8-22im</link>
      <guid>https://dev.to/erikch/8-vite-config-options-every-developer-should-know-vite-8-22im</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Last week I was at &lt;a href="https://vueconf.us/" rel="noopener noreferrer"&gt;Vueconf US&lt;/a&gt;. It's one of my favorite conferences and I try to go every year. This year Evan You gave a talk on the future of Vue and tooling. Halfway through he started talking about Vite and some of the new features of Vite 8 that just came out. I was amazed at how far Vite has come and the millions of downloads a month it's getting. What I found especially interesting was how few people really knew about these features. Evan asked the audience by a show of hands who had tried out &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;Vite 8&lt;/a&gt;, and only a few people raised their hands! So when I got home I decided to write this blog post and create a video on some configurations you must try out with Vite, including some new features that just came out with Vite 8.&lt;/p&gt;

&lt;p&gt;If you haven't heard yet, Vite 8 shipped in March 2026 with the most significant architectural change since Vite 2. One of the most significant is the Rust-based bundler called Rolldown that delivers &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;10-30x faster builds&lt;/a&gt;. If you've been using Vite with zero config, that's fine. The defaults are great. However, there are a handful of options that, once you know them, you'll reach for in every project.&lt;/p&gt;

&lt;p&gt;This post covers 8 config options that you should know. Two are new in Vite 8, and six have been around but you probably have not heard of them. All of them go in your &lt;code&gt;vite.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I put this post together using &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an AI-powered IDE. The &lt;code&gt;forwardConsole&lt;/code&gt; option in section 1 is something Kiro benefits from directly. &lt;/p&gt;

&lt;p&gt;I also made a video covering all of these with a live demo. Check it out if you prefer watching over reading:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/WbIz38_54KM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20.19+ or 22.12+ (required by Vite 8)&lt;/li&gt;
&lt;li&gt;A Vite project (any framework)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To upgrade to Vite 8:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. server.forwardConsole
&lt;/h3&gt;

&lt;p&gt;New in Vite 8. When you enable this, browser console errors and warnings show up in your Vite terminal instead of only in DevTools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;forwardConsole&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's useful when you're working with AI coding agents that can't see the browser, or when you just want to keep your eyes on the terminal instead of switching to DevTools. Vite auto-enables it when it detects an AI coding agent via &lt;a href="https://www.npmjs.com/package/@vercel/detect-agent" rel="noopener noreferrer"&gt;&lt;code&gt;@vercel/detect-agent&lt;/code&gt;&lt;/a&gt;. Otherwise it defaults to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can get more granular with it too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;forwardConsole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;unhandledErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logLevels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output includes source-mapped stack traces, so you see the original TypeScript line numbers, not the compiled output.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. resolve.tsconfigPaths
&lt;/h3&gt;

&lt;p&gt;Also new in Vite 8. Before this you had to install the &lt;code&gt;vite-tsconfig-paths&lt;/code&gt; plugin to use TypeScript path aliases. Now it's built in and works in both the dev server and build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tsconfigPaths&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="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;One setup mistake I see a lot: make sure &lt;code&gt;paths&lt;/code&gt; is inside &lt;code&gt;compilerOptions&lt;/code&gt;, not at the top level of the JSON. Easy to get wrong when editing by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wrong&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;top&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;level,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ignored&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Vite&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./src/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.app.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;correct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;inside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;compilerOptions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.app.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./src/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@components/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./src/components/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a small performance cost, so it's opt-in rather than the default. The &lt;a href="https://www.typescriptlang.org/tsconfig/#paths" rel="noopener noreferrer"&gt;TypeScript team also notes&lt;/a&gt; that &lt;code&gt;paths&lt;/code&gt; is intended to inform TypeScript about mappings handled by other tools, not to change emit behavior. Still, I think it's well worth trying out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;resolve.alias vs resolve.tsconfigPaths: which should you use?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;resolve.alias&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;resolve.tsconfigPaths&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Config location&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vite.config.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works with plain JS&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single source of truth&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires Vite 8&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance cost&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;tsconfigPaths&lt;/code&gt; if you want one place to define aliases and you're already on TypeScript. Use &lt;code&gt;alias&lt;/code&gt; for plain JS projects, monorepos, or when you want aliases independent of TypeScript's config.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. server.proxy
&lt;/h3&gt;

&lt;p&gt;I don't see a lot of developers using this one. The proxy isn't really about fixing CORS. It's about making your dev environment behave like production.&lt;/p&gt;

&lt;p&gt;In production, your frontend and API are usually on the same domain. They might have the same origin, so CORS is not needed. For example, in dev, your frontend may be on &lt;code&gt;localhost:5173&lt;/code&gt; and your backend on &lt;code&gt;localhost:8080&lt;/code&gt;. If you have different ports, and different origins, tne you'll get a CORS error. The proxy closes helps fix this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&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;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rewrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;api/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any request to &lt;code&gt;/api/*&lt;/code&gt; gets forwarded to &lt;code&gt;http://localhost:8080/*&lt;/code&gt;. The browser only ever sees &lt;code&gt;localhost:5173&lt;/code&gt;, so no cross-origin request and no CORS error.&lt;/p&gt;

&lt;p&gt;A few other reasons to reach for it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Working against a teammate's service or a third-party API that hasn't added &lt;code&gt;localhost&lt;/code&gt; to their CORS config? You can't change their server, but you can proxy through Vite.&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;Access-Control-Allow-Origin: localhost:5173&lt;/code&gt; to your backend is a dev concern leaking into prod config. The proxy keeps that out entirely.&lt;/li&gt;
&lt;li&gt;You can inject auth headers for external services in the proxy &lt;code&gt;configure&lt;/code&gt; callback, keeping them out of the browser bundle. This is dev-only though. For production, handle auth server-side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;changeOrigin: true&lt;/code&gt; makes the request appear to come from the target host, which some backends require. &lt;code&gt;rewrite&lt;/code&gt; strips the &lt;code&gt;/api&lt;/code&gt; prefix before forwarding.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. server.hmr.overlay
&lt;/h3&gt;

&lt;p&gt;By default, Vite shows a full-screen red overlay when there's a server error. Runtime errors, HMR failures, and transform errors all trigger it. If you find it disruptive while editing, you can turn it off.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hmr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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;Errors still appear in the terminal and browser console. Turn it off when you're doing UI-heavy work and the overlay keeps blocking the component you're editing. I've been turning the overlay off a lot when I work. It's been so much nicer. Leave it on if you like the overlay.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. resolve.alias
&lt;/h3&gt;

&lt;p&gt;While I prefer the &lt;code&gt;tsconfigpaths&lt;/code&gt;, sometimes you have to use &lt;code&gt;resolve.alias&lt;/code&gt; instead. It is the explicit way to set up import shortcuts without touching tsconfig at all. It's great for JS projects, monorepos, or when you want aliases that aren't tied to TypeScript's path resolution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&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="s1"&gt;@components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/components&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="s1"&gt;@utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;path.resolve&lt;/code&gt; (or &lt;code&gt;fileURLToPath&lt;/code&gt; with ESM) to get a proper absolute path. Passing a bare string like &lt;code&gt;'/src'&lt;/code&gt; resolves to the filesystem root on most systems, not your project root.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. server.open
&lt;/h3&gt;

&lt;p&gt;I've been really liking this option. Yes it's small but once you turn it on, you'll never turn it off (at least most of the time). Set &lt;code&gt;server.open: true&lt;/code&gt; and Vite opens your browser automatically when the dev server starts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;open&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass a path string to land on a specific route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&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;Pairs well with conditional config (see the Bonus section) so you can set &lt;code&gt;open&lt;/code&gt; only when running &lt;code&gt;vite dev&lt;/code&gt;, not during build.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. build.sourcemap
&lt;/h3&gt;

&lt;p&gt;By default, Vite doesn't generate sourcemaps for production builds, so errors in production point at minified output. Turn this on and stack traces point at your actual source files. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FYI: Source maps are turned on for dev builds, so don't accidentaly turn this on by accident and leak your source code in production!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&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="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;Run &lt;code&gt;npm run build&lt;/code&gt; then &lt;code&gt;npm run preview&lt;/code&gt; (preview runs &lt;code&gt;vite preview&lt;/code&gt;). Open DevTools, go to Sources, and you'll see your actual &lt;code&gt;.vue&lt;/code&gt; and &lt;code&gt;.ts&lt;/code&gt; files. Click any line in a stack trace and it jumps to the original source. This is perfect for debugging and adding in break points.&lt;/p&gt;

&lt;p&gt;Three modes are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sourcemap&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="c1"&gt;// separate .map files alongside the bundle&lt;/span&gt;
  &lt;span class="nx"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// sourcemap embedded directly in the JS file&lt;/span&gt;
  &lt;span class="nx"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// .map files generated, no reference in the bundle&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;'hidden'&lt;/code&gt; is useful if you're uploading sourcemaps to an error monitoring service like &lt;a href="https://docs.sentry.io/platforms/javascript/sourcemaps/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;. They can decode your stack traces server-side without the browser ever loading the maps. One thing to know: &lt;code&gt;'hidden'&lt;/code&gt; only removes the &lt;code&gt;//# sourceMappingURL=&lt;/code&gt; comment from the bundle. The &lt;code&gt;.map&lt;/code&gt; files are still generated and will be deployed alongside your JS unless you explicitly exclude them. If you want them truly private, add a step to strip &lt;code&gt;*.map&lt;/code&gt; files before uploading. Otherwise your source maps might leak to production!&lt;/p&gt;




&lt;h3&gt;
  
  
  8. envPrefix
&lt;/h3&gt;

&lt;p&gt;Occasionally I look for this one, it's useful when I'm migrating code and the prefixs are different. By default, Vite only exposes env variables prefixed with &lt;code&gt;VITE_&lt;/code&gt; to your client code. Everything else in your &lt;code&gt;.env&lt;/code&gt; file stays server-side. &lt;code&gt;envPrefix&lt;/code&gt; lets you change that prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;envPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUBLIC_&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;With this set, your &lt;code&gt;.env&lt;/code&gt; file might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;PUBLIC_API_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com&lt;/span&gt;
&lt;span class="py"&gt;PUBLIC_APP_NAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;My App&lt;/span&gt;
&lt;span class="py"&gt;SECRET_DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;supersecret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in your components:&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC_API_URL&lt;/span&gt;   &lt;span class="c1"&gt;// "https://api.example.com"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC_APP_NAME&lt;/span&gt;  &lt;span class="c1"&gt;// "My App"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SECRET_DB_PASSWORD&lt;/span&gt; &lt;span class="c1"&gt;// undefined, never sent to the browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only variables matching your prefix make it into the bundle. Everything else is stripped at build time, even if it's in the same &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If you're migrating from Create React App (&lt;code&gt;REACT_APP_&lt;/code&gt;) or Next.js (&lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;), you can match your existing naming convention instead of renaming every variable. You can also pass an array to expose multiple prefixes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;envPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUBLIC_&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="s1"&gt;APP_&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;h3&gt;
  
  
  Bonus: conditional config and define
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This bonus is more for the advanced users out there! If you are let me know in the comments!&lt;/em&gt; You can pass a function to &lt;code&gt;defineConfig&lt;/code&gt; instead of an object. It receives &lt;code&gt;command&lt;/code&gt; (&lt;code&gt;'serve'&lt;/code&gt; or &lt;code&gt;'build'&lt;/code&gt;) and &lt;code&gt;mode&lt;/code&gt; (&lt;code&gt;'development'&lt;/code&gt; or &lt;code&gt;'production'&lt;/code&gt;), so you can change config based on context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serve&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;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;define&lt;/code&gt; bakes global constants into the build at compile time. Values are inlined directly into the output, so there's no runtime cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;__APP_VERSION__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;__BUILD_DATE__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;Add type declarations to &lt;code&gt;vite-env.d.ts&lt;/code&gt; so TypeScript knows about them:&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;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__APP_VERSION__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__BUILD_DATE__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;Most of these are config-only changes. A few touch other files: &lt;code&gt;tsconfigPaths&lt;/code&gt; requires &lt;code&gt;paths&lt;/code&gt; in your &lt;code&gt;tsconfig.json&lt;/code&gt;, &lt;code&gt;envPrefix&lt;/code&gt; pairs with a &lt;code&gt;.env&lt;/code&gt; file, and &lt;code&gt;define&lt;/code&gt; needs type declarations in &lt;code&gt;vite-env.d.ts&lt;/code&gt;. To revert any option, remove it from &lt;code&gt;vite.config.ts&lt;/code&gt; and Vite falls back to its defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why aren't my TypeScript path aliases working in Vite?&lt;/strong&gt;&lt;br&gt;
Make sure &lt;code&gt;paths&lt;/code&gt; is inside &lt;code&gt;compilerOptions&lt;/code&gt; in your &lt;code&gt;tsconfig.json&lt;/code&gt;, not at the top level. Vite 8's built-in &lt;code&gt;resolve.tsconfigPaths: true&lt;/code&gt; reads from &lt;code&gt;compilerOptions.paths&lt;/code&gt; only. A top-level &lt;code&gt;paths&lt;/code&gt; key is silently ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between resolve.alias and resolve.tsconfigPaths in Vite?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;resolve.alias&lt;/code&gt; is defined directly in &lt;code&gt;vite.config.ts&lt;/code&gt; and works for any project including plain JavaScript. &lt;code&gt;resolve.tsconfigPaths&lt;/code&gt; reads aliases from your &lt;code&gt;tsconfig.json&lt;/code&gt; and requires TypeScript. Use &lt;code&gt;tsconfigPaths&lt;/code&gt; if you want a single source of truth; use &lt;code&gt;alias&lt;/code&gt; for JS projects or monorepos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Vite's server.proxy fix CORS errors?&lt;/strong&gt;&lt;br&gt;
Yes, but the real purpose is making dev match production. The proxy routes requests through the Vite dev server so the browser never makes a cross-origin request. No CORS header needed on your backend. It's cleaner than adding &lt;code&gt;Access-Control-Allow-Origin: localhost:5173&lt;/code&gt; to your server config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are Vite production sourcemaps safe to deploy?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;sourcemap: 'hidden'&lt;/code&gt; generates &lt;code&gt;.map&lt;/code&gt; files without referencing them in the bundle, so browsers won't load them. You can upload them to Sentry or similar for private stack trace decoding. Note: the &lt;code&gt;.map&lt;/code&gt; files are still built and will be deployed unless you add a step to strip &lt;code&gt;*.map&lt;/code&gt; files before uploading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What env variables does Vite expose to the browser?&lt;/strong&gt;&lt;br&gt;
Only variables prefixed with &lt;code&gt;VITE_&lt;/code&gt; by default (or your custom &lt;code&gt;envPrefix&lt;/code&gt;). Everything else in &lt;code&gt;.env&lt;/code&gt; is stripped at build time and never sent to the browser. That includes secrets like database passwords or API keys that don't have the prefix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does server.forwardConsole work with all frameworks?&lt;/strong&gt;&lt;br&gt;
Yes, it's a Vite dev server feature, not framework-specific. It works with Vue, React, Svelte, and any other Vite-based setup. Vite auto-enables it when it detects an AI coding agent via &lt;code&gt;@vercel/detect-agent&lt;/code&gt;; otherwise it defaults to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;forwardConsole&lt;/code&gt; and &lt;code&gt;tsconfigPaths&lt;/code&gt; are both opt-in and default to &lt;code&gt;false&lt;/code&gt;. The others (&lt;code&gt;proxy&lt;/code&gt;, &lt;code&gt;hmr.overlay&lt;/code&gt;, &lt;code&gt;resolve.alias&lt;/code&gt;, &lt;code&gt;server.open&lt;/code&gt;, &lt;code&gt;build.sourcemap&lt;/code&gt;, &lt;code&gt;envPrefix&lt;/code&gt;) have been around for a while but are easy to miss if you've never needed them.&lt;/p&gt;

&lt;p&gt;The full demo project is available at &lt;a href="https://github.com/ErikCH/vite-config-tips" rel="noopener noreferrer"&gt;https://github.com/ErikCH/vite-config-tips&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Here is me speaking at VueConf this year! &lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fdmyk36uejpwdmjyeh2u9.jpg" class="article-body-image-wrapper"&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%2Farticles%2Fdmyk36uejpwdmjyeh2u9.jpg" alt="Erik Speaking at VueConf" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Erik Hanchett, AWS Developer Advocate. He covers frontend development, AWS, and AI/agents at &lt;a href="https://programwitherik.com" rel="noopener noreferrer"&gt;programwitherik.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vite</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
