<?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: Carlos Precioso</title>
    <description>The latest articles on DEV Community by Carlos Precioso (@cprecioso).</description>
    <link>https://dev.to/cprecioso</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%2F2953999%2Fead15666-6319-478f-964b-954f771057e7.jpeg</url>
      <title>DEV Community: Carlos Precioso</title>
      <link>https://dev.to/cprecioso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cprecioso"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Tue, 28 Jul 2026 10:14:15 +0000</pubDate>
      <link>https://dev.to/cprecioso/-52dk</link>
      <guid>https://dev.to/cprecioso/-52dk</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/made-with-wasp-how-searchcraft-built-a-full-stack-saas-around-their-rust-search-engine-586n" class="crayons-story__hidden-navigation-link"&gt;Made with Wasp: how Searchcraft built a full-stack SaaS around their Rust search engine&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image" width="151" height="151"&gt;
          &lt;/a&gt;

          &lt;a href="/licto" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F1117535%2F9bd79dd4-4b7f-4eb4-bde3-b8e3b78d7cf5.png" alt="licto profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/licto" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Licto
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Licto
                
              
              &lt;div id="story-author-preview-content-4244684" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/licto" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1117535%2F9bd79dd4-4b7f-4eb4-bde3-b8e3b78d7cf5.png" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Licto&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/made-with-wasp-how-searchcraft-built-a-full-stack-saas-around-their-rust-search-engine-586n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 28&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/made-with-wasp-how-searchcraft-built-a-full-stack-saas-around-their-rust-search-engine-586n" id="article-link-4244684"&gt;
          Made with Wasp: how Searchcraft built a full-stack SaaS around their Rust search engine
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rust"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rust&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/saas"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;saas&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/made-with-wasp-how-searchcraft-built-a-full-stack-saas-around-their-rust-search-engine-586n" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;12&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/made-with-wasp-how-searchcraft-built-a-full-stack-saas-around-their-rust-search-engine-586n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

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

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

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Roll your own file-based router in under 50 lines of code</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:04:08 +0000</pubDate>
      <link>https://dev.to/wasp/roll-your-own-file-based-router-in-under-50-lines-of-code-2kb4</link>
      <guid>https://dev.to/wasp/roll-your-own-file-based-router-in-under-50-lines-of-code-2kb4</guid>
      <description>&lt;p&gt;Nowadays, web frameworks come with ✨ magic ✨. Some more than others, but all of them have some. The magic is usually in the form of conventions: do things their way and the framework helps you out; wander off the happy path and you're on your own.&lt;/p&gt;

&lt;p&gt;One of the main tricks they pull is quietly turning your folder structure into a config file. Create &lt;code&gt;app/about-us/page.tsx&lt;/code&gt; in a Next.js app and &lt;code&gt;https://localhost:3000/about-us&lt;/code&gt; magically starts working. Rename the file, and the URL changes with it. The framework is reading your file tree and turning it into a routing table.&lt;/p&gt;

&lt;p&gt;That is called &lt;strong&gt;file-based routing&lt;/strong&gt;, and it's usually magic your framework either hands you or it doesn't; and when it does, you rarely get to customize it, the conventions are baked in. Want a folder name to carry special meaning, or a &lt;code&gt;logged-in.tsx&lt;/code&gt;/&lt;code&gt;logged-out.tsx&lt;/code&gt; split per route? Your only option is to post a feature request and wait, or go without.&lt;/p&gt;

&lt;p&gt;In Wasp, we really like explicitness, so we've resisted shipping a file-based router. That's changing now, but of course, we're not just adding the magic conventions and moving on. We're giving you a way to choose and design your own magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, what's Wasp?
&lt;/h2&gt;

&lt;p&gt;Wasp is a batteries-included full-stack web framework for TypeScript. It covers your frontend, backend, and database as one cohesive whole.&lt;/p&gt;

&lt;p&gt;And one of our core ideas is that the entry point to all this full-stack goodness is through an explicit &lt;strong&gt;Spec&lt;/strong&gt;. Instead of hiding your app's structure behind implicit conventions, you declare its features (like routes, authentication, queries, cron jobs, etc) directly in code. Wasp then compiles and stitches these pieces together:&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;app&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;page&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;@wasp.sh/spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// `type: ref` means we just want to point to these files, not execute them now:&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MainPage&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;./src/pages/MainPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ref&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;UserPage&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;./src/pages/LoginPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ref&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Some example configurations, we'll skip them in the future:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;wasp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.24.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;google&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;// The spec is where you declare your app's features:&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MainRoute&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;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LoginRoute&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;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authRequired&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;We chose this approach because explicit code is easier to reason about than hidden magic. This transparency makes the codebase easier to navigate for your team, and it gives AI code assistants a clear, structured map of your application to work with.&lt;/p&gt;

&lt;p&gt;That explicitness is the whole philosophy, and it's exactly why we've deliberately never shipped file-based routing. We just didn't want to bake that pile of implicit conventions into the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spec is just a program
&lt;/h2&gt;

&lt;p&gt;But here's the part that makes it exciting. We recently &lt;a href="https://wasp.sh/blog/2026/06/15/wasp-typescript-spec" rel="noopener noreferrer"&gt;updated our spec&lt;/a&gt; so that it lives in a &lt;code&gt;main.wasp.ts&lt;/code&gt; file. And instead of being a static JSON file that the framework reads, it is just &lt;strong&gt;a standard Node.js program&lt;/strong&gt;, written in TypeScript. You can pull in libraries from npm, read from disk, call an API, use environment variables, anything! The only rule is that you export an &lt;code&gt;app&lt;/code&gt; at the end. That's all we need.&lt;/p&gt;

&lt;p&gt;So the Spec is explicit, but it's also &lt;em&gt;programmable&lt;/em&gt;. And nothing says those &lt;code&gt;route&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; calls have to be typed out by hand.&lt;/p&gt;

&lt;p&gt;That's the entire trick. You can write a function that walks your &lt;code&gt;src/&lt;/code&gt; folder and returns the same &lt;code&gt;route&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; objects you'd otherwise write manually. File-based routing stops being internal magic, and becomes a few lines of ordinary code that live in your repo, that you can read, and more importantly, customize.&lt;/p&gt;

&lt;p&gt;So even though we never shipped file-based routing, "we didn't ship it" doesn't mean "you can't have it". You add it yourself, with exactly the conventions you want. You get the best of both worlds, and nobody has to argue about defaults. Sorry if you liked the arguing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simplest possible router
&lt;/h2&gt;

&lt;p&gt;Let's build that. Because the route list is just an array of objects, the simplest possible "router" is one we write out by hand:&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;// The `ref` helper is the dynamic version of `with { type: "ref" }` imports, so we'll use it to build programmatically.&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;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@wasp.sh/spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RootRoute&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;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;importDefault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RootPage&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/page.tsx&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AboutUsRoute&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;/about-us&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;importDefault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AboutUsPage&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/about-us/page.tsx&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;That's a functioning router already, but the unsatisfying part is that we had to type it out ourselves. So let's &lt;em&gt;generate&lt;/em&gt; it instead, by looking at the project files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The step up: generate the routes from the filesystem
&lt;/h2&gt;

&lt;p&gt;We'll pick a very simple convention: &lt;strong&gt;a &lt;code&gt;page.tsx&lt;/code&gt; inside a folder makes it a route.&lt;/strong&gt; So &lt;code&gt;src/about-us/page.tsx&lt;/code&gt; serves &lt;code&gt;https://my-app.com/about-us&lt;/code&gt;, and &lt;code&gt;src/page.tsx&lt;/code&gt; serves &lt;code&gt;https://my-app.com/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the whole thing:&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;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&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;@wasp.sh/spec&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;pascalCase&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;es-toolkit&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;globSync&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;node:fs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;node:path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileBasedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseDir&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;globSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/page.tsx&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;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseDir&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Make the list stable between runs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;absoluteFilePath&lt;/span&gt; &lt;span class="o"&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;baseDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&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;urlRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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;// Removes the "page.tsx" part&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pascalCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Root&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="nf"&gt;route&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Route`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;urlRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;importDefault&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Page`&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;absoluteFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire idea. Find every &lt;code&gt;page.tsx&lt;/code&gt;, turn its folder path into a URL path and a name, and produce the &lt;code&gt;route()&lt;/code&gt;s. For the route name we can even &lt;a href="https://es-toolkit.dev/reference/string/pascalCase.html" rel="noopener noreferrer"&gt;lean on an npm package&lt;/a&gt; to get a clean name; it's just an ordinary npm dependency, like in any Node project.&lt;/p&gt;

&lt;p&gt;There's nothing clever going on here, it's plain filesystem glue. An inexperienced dev could write this without much trouble. It never reaches into Wasp's internals, and it only calls the same public &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, and &lt;code&gt;ref&lt;/code&gt; functions you'd use by hand.&lt;/p&gt;

&lt;p&gt;Wiring it into your app is one line:&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;// main.wasp.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;app&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;@wasp.sh/spec&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;fileBasedRoutes&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;./lib/file-based-routes.wasp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// highlight-next-line&lt;/span&gt;
    &lt;span class="nf"&gt;fileBasedRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// Any other Specs your app uses (job(), query(), action(), ...)&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fileBasedRoutes&lt;/code&gt; returns an array of route objects, and we drop it straight into &lt;code&gt;spec&lt;/code&gt;. Voilà, you have file-based routing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything Wasp already does still works
&lt;/h2&gt;

&lt;p&gt;Because we're producing normal &lt;code&gt;route&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; spec objects, the rest of Wasp neither knows nor cares that they were generated. So all the features you'd expect keep working, including &lt;a href="https://wasp.sh/docs/advanced/links" rel="noopener noreferrer"&gt;type-safe links&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&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;wasp/client/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Main page&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      [About us](https://wasp.sh/about-us)
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; component is fully typechecked with the routes your function generated. Generate a route from a folder, and you will get autocomplete and type errors for it everywhere. Nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now make it yours: route groups
&lt;/h2&gt;

&lt;p&gt;Here's where it gets fun. This is &lt;em&gt;your&lt;/em&gt; code, so bend it to your project. Say you want &lt;strong&gt;route groups:&lt;/strong&gt; folders that organize your files without showing up in the URL. The usual convention is to wrap them in parentheses, like &lt;code&gt;(marketing)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's a one-line filter:&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;// highlight-next-line&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROUTE_GROUP_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in parentheses&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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;// Removes the "page.tsx" part&lt;/span&gt;
  &lt;span class="c1"&gt;// highlight-next-line&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_GROUP_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Remove any route groups&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;src/(marketing)/about-us/page.tsx&lt;/code&gt; still serves &lt;code&gt;/about-us&lt;/code&gt;, but you can keep your marketing pages tidily grouped.&lt;/p&gt;

&lt;h2&gt;
  
  
  A convention for prerender and auth
&lt;/h2&gt;

&lt;p&gt;Now let's say that in your project, there are two settings you're reaching for constantly: marking a page as auth-required, or marking it for prerendering. Let's invent a convention for both, reusing the route-group syntax we just added: an &lt;code&gt;(auth)&lt;/code&gt; group makes everything inside require auth, and a &lt;code&gt;(prerender)&lt;/code&gt; group marks pages for prerendering.&lt;/p&gt;

&lt;p&gt;We detect those folders and thread the result into the spec objects:&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;// highlight-start&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPrerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(prerender)&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;isAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(auth)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// highlight-end&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;route&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Route`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;urlRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;importDefault&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Page`&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;absoluteFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// highlight-next-line&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isAuth&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// highlight-next-line&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prerender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isPrerender&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;These are &lt;em&gt;your&lt;/em&gt; conventions. Don't like parentheses for this? Use a &lt;code&gt;.auth.tsx&lt;/code&gt; suffix, a &lt;code&gt;"use auth"&lt;/code&gt; directive, or a config file next to the page; whatever reads best for your team. The spec doesn't dictate the shape, you do!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic and optional segments
&lt;/h2&gt;

&lt;p&gt;Wasp routing also supports &lt;a href="https://wasp.sh/docs/advanced/routing#dynamic-segments" rel="noopener noreferrer"&gt;dynamic&lt;/a&gt; (&lt;code&gt;:id&lt;/code&gt;) and &lt;a href="https://wasp.sh/docs/advanced/routing#optional-segments" rel="noopener noreferrer"&gt;optional&lt;/a&gt; (&lt;code&gt;:id?&lt;/code&gt;) segments. Written out in the spec by hand, they look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ProductRoute&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;/products/:productId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ArticleRoute&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;/articles/:slug?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ArticlePage&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;:&lt;/code&gt; character is not allowed in file paths, so let's copy Next.js's bracket syntax for this: &lt;code&gt;[productId]&lt;/code&gt; becomes &lt;code&gt;:productId&lt;/code&gt;, and &lt;code&gt;[[productId]]&lt;/code&gt; becomes &lt;code&gt;:productId?&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// highlight-start&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DYNAMIC_SEGMENT_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;)\]&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in square brackets&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OPTIONAL_SEGMENT_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;)\]\]&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in two square brackets&lt;/span&gt;
&lt;span class="c1"&gt;// highlight-end&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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;// Removes the "page.tsx" part&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_GROUP_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Remove any route groups&lt;/span&gt;
  &lt;span class="c1"&gt;// highlight-start&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&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;part&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="nx"&gt;OPTIONAL_SEGMENT_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:$1?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Convert optional segments&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&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;part&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="nx"&gt;DYNAMIC_SEGMENT_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:$1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Convert dynamic segments&lt;/span&gt;
  &lt;span class="c1"&gt;// highlight-end&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can invent your own syntax. The point is that it's just code, so you do you.&lt;/p&gt;

&lt;h2&gt;
  
  
  45 lines later
&lt;/h2&gt;

&lt;p&gt;We're done!&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;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&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;@wasp.sh/spec&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;pascalCase&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;es-toolkit&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;globSync&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;node:fs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;node:path&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;ROUTE_GROUP_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in parentheses&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DYNAMIC_SEGMENT_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;)\]&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in square brackets&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OPTIONAL_SEGMENT_REGEX&lt;/span&gt; &lt;span class="o"&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;.*&lt;/span&gt;&lt;span class="se"&gt;)\]\]&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wrapped in two square brackets&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;fileBasedRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseDir&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;globSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/page.tsx&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;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseDir&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Make the list stable between runs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;absoluteFilePath&lt;/span&gt; &lt;span class="o"&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;baseDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&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;isPrerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(prerender)&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;isAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(auth)&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;urlRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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;// Removes the "page.tsx" part&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ROUTE_GROUP_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Remove any route groups&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&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;part&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="nx"&gt;OPTIONAL_SEGMENT_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:$1?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Convert optional segments&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&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;part&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="nx"&gt;DYNAMIC_SEGMENT_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:$1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Convert dynamic segments&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pascalCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Root&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="nf"&gt;route&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Route`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;urlRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;importDefault&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;routeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Page`&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;absoluteFilePath&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;authRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isAuth&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;prerender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isPrerender&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;That's it. You can start using these conventions as you see fit. For example, with the folder layout below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── (prerender)/
│   └── (marketing)/
│       ├── page.tsx
│       └── about-us/
│           └── page.tsx
├── products/
│   └── [productId]
│       └── page.tsx
└── (auth)/
    └── dashboard/
        └── page.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...our function produces this spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;fileBasedRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// the above call ⬆️ will turn into the following specs ⬇️&lt;/span&gt;

    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RootRoute&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;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RootPage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prerender&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="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AboutUsRoute&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;/about-us&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AboutUsPage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prerender&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="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ProductIdRoute&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;/products/:productId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProductIdPage&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DashboardRoute&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;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authRequired&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;Add more files in that &lt;code&gt;(auth)&lt;/code&gt;, and those pages become auth-required; name a folder &lt;code&gt;[[slug]]&lt;/code&gt; and you get an optional segment. We've covered the most useful bits of Wasp's routing and exposed them through an entirely new API surface, and all of it lives in under 50 lines you can read end to end in a minute.&lt;/p&gt;

&lt;p&gt;And the same trick isn't limited to routes. Because the spec is just data your program returns, you can generate any of it the same way: &lt;code&gt;api()&lt;/code&gt;, &lt;code&gt;query()&lt;/code&gt;, &lt;code&gt;job()&lt;/code&gt;, or &lt;code&gt;action()&lt;/code&gt;, all from your own file layout. With other frameworks you might need weird hacks or an escape hatch to do this, but in Wasp there's nothing to escape from.&lt;/p&gt;

&lt;p&gt;This is the whole reason we keep going on about the spec being a first-class, programmable thing. It means that file-based routing didn't have to become a Wasp feature with a dozen options and an opt-out. It just became a small file you can control. &lt;a href="https://wasp.sh/blog/2026/06/15/wasp-typescript-spec" rel="noopener noreferrer"&gt;Meta-meta-framework, anyone?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll your own
&lt;/h2&gt;

&lt;p&gt;Want a more full-featured file-based router than the version above? We are building one you can use or learn from: &lt;a href="https://github.com/wasp-lang/file-based-routing" rel="noopener noreferrer"&gt;wasp-lang/file-based-routing&lt;/a&gt;. We're still working on it, but it has quite a few more features than the one here. Other people might create their own libraries, and you can use those too!&lt;/p&gt;

&lt;p&gt;But the whole point of this post is that you don't have to use anyone's library. Run &lt;code&gt;wasp new&lt;/code&gt;, copy our starting example, and build the conventions that fit &lt;em&gt;your&lt;/em&gt; project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The future
&lt;/h2&gt;

&lt;p&gt;The same "spec is just a program" idea doesn't stop at routing. We're actively working on &lt;a href="https://wasp.sh/blog/2026/06/22/javascript-still-cant-ship-a-full-stack-module" rel="noopener noreferrer"&gt;full-stack modules&lt;/a&gt;: the idea that you'll be able to pull libraries that don't only contain backend or frontend code, but full-stack code, defined as a Spec. File-based routing might be a part of those specs in the future! But it all goes towards giving you the magic of conventions without losing control and explicitness when you want it.&lt;/p&gt;

&lt;p&gt;If you come up with something good, we'd genuinely love to see it, come share it on &lt;a href="https://discord.gg/rzdnErX" rel="noopener noreferrer"&gt;our Discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Tue, 12 May 2026 09:36:12 +0000</pubDate>
      <link>https://dev.to/cprecioso/-4eii</link>
      <guid>https://dev.to/cprecioso/-4eii</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/open-vibe-ship-your-saas-with-ai-without-getting-stuck-e2h" class="crayons-story__hidden-navigation-link"&gt;Open Vibe -- Ship your SaaS with AI. Without getting stuck.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image" width="151" height="151"&gt;
          &lt;/a&gt;

          &lt;a href="/vincanger" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="vincanger profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/vincanger" class="crayons-story__secondary fw-medium m:hidden"&gt;
              vincanger
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                vincanger
                
              
              &lt;div id="story-author-preview-content-3655326" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/vincanger" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;vincanger&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/open-vibe-ship-your-saas-with-ai-without-getting-stuck-e2h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/open-vibe-ship-your-saas-with-ai-without-getting-stuck-e2h" id="article-link-3655326"&gt;
          Open Vibe -- Ship your SaaS with AI. Without getting stuck.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/saas"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;saas&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vibecoding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vibecoding&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/open-vibe-ship-your-saas-with-ai-without-getting-stuck-e2h" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/open-vibe-ship-your-saas-with-ai-without-getting-stuck-e2h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>A gentle intro to npm workspaces, with visuals</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Tue, 07 Apr 2026 15:14:07 +0000</pubDate>
      <link>https://dev.to/wasp/a-gentle-intro-to-npm-workspaces-with-visuals-4jpn</link>
      <guid>https://dev.to/wasp/a-gentle-intro-to-npm-workspaces-with-visuals-4jpn</guid>
      <description>&lt;p&gt;I am a founding engineer at &lt;a href="https://wasp.sh/" rel="noopener noreferrer"&gt;Wasp&lt;/a&gt; - a full-stack TypeScript framework based on React, Node.js, and Prisma. Like Rails, but for JavaScript. A Wasp app is built as a multi-package setup - frontend, backend and shared code all live in a single repo as separate packages managed by npm workspaces.&lt;/p&gt;

&lt;p&gt;Getting this right was harder than expected, and this post is what we learned. We'll dig into how npm workspaces actually work: how dependencies get resolved and hoisted, how Node.js finds modules at runtime, and help you build intuition/mental model that will come in useful when debugging this setup.&lt;/p&gt;




&lt;p&gt;Your side project started as a single folder with a hundred lines of code. Now it's a full-stack app with a React frontend, an Express backend, and a shared library of types and helpers that both sides need. You might be copy-pasting files between folders like it's 2005, and the cracks are showing: a type definition got updated in one place but not the other, and now your API responses don't match what the frontend expects. Something has to change.&lt;/p&gt;

&lt;p&gt;The first step would probably be splitting your code into multiple packages. But without the right tools, you'll quickly find yourself drowning in running npm install in five different folders, or fighting runtime errors because React is installed three times.To fix this, modern package managers introduced &lt;strong&gt;workspaces&lt;/strong&gt;. Intended to allow you to manage multiple packages within a single repository without the dependency headaches, workspaces are widely used in many projects, but their inner workings are often misunderstood.&lt;/p&gt;

&lt;p&gt;In this post, we'll dig into the magic that makes them tick: how package managers resolve shared dependencies across packages, and how Node.js finds them at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter workspaces
&lt;/h2&gt;

&lt;p&gt;Workspaces are a feature in npm, Yarn, pnpm, and others that let you organize multiple JavaScript packages in a single project. And while each package acts as its own encapsulated unit, they can still depend on one another with no extra ceremony.&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%2Fd6txwuqma3pegbkpma1f.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.amazonaws.com%2Fuploads%2Farticles%2Fd6txwuqma3pegbkpma1f.png" alt="Diagram comparing dependency management with and without workspaces. Without workspaces two separate projects each install their own copy of their dependencies, and are isolated from one another. With workspaces the same two projects share their common dependencies, can call each other." width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two main behaviors make this work: shared dependencies (if two packages depend on &lt;code&gt;react&lt;/code&gt;, they both use the same installed version) and cross-package imports (a &lt;code&gt;pkg-a&lt;/code&gt; can just import &lt;code&gt;pkg-b&lt;/code&gt; by name).&lt;/p&gt;

&lt;p&gt;The building blocks are interesting and require a dive into how package managers and Node.js work. The feature also comes after a long evolution of tooling and JS development practices, so let's start with some history.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief history
&lt;/h2&gt;

&lt;p&gt;It seems like the second half of the 2010s was when the professionalization of JS development started to trickle down to the average developer. The rise of &lt;a href="https://babeljs.io/" rel="noopener noreferrer"&gt;Babel&lt;/a&gt; and &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt; gave us a modern development experience, one that more closely resembled what other languages had enjoyed for years. We finally had a proper module system, and the ability to target newer language versions, while still supporting older runtimes.&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%2F7pix2ala3sxrplad2r6e.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.amazonaws.com%2Fuploads%2Farticles%2F7pix2ala3sxrplad2r6e.png" alt="Timeline of monorepo tooling milestones: October 2015 — Babel turns into a monorepo; March 2017 — Lerna 1.0; September 2017 — Yarn with workspaces; February 2018 — pnpm with workspaces; October 2020 — npm with workspaces." width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our history of workspaces starts in 2015, when the 6to5 project &lt;a href="https://babeljs.io/blog/2015/02/15/not-born-to-die" rel="noopener noreferrer"&gt;realized&lt;/a&gt; they didn't want to work only on ES6 code, but also on everything that came after it. They renamed themselves to Babel and started working on &lt;a href="https://babeljs.io/blog/2015/10/29/6.0.0#modularization" rel="noopener noreferrer"&gt;modularizing the codebase&lt;/a&gt;, knowing they'd need well-separated concerns to keep up with the ever-evolving language.&lt;/p&gt;

&lt;p&gt;Babel maintainers wanted to distribute the new version as a collection of plugins you could pick and choose, but couldn't afford to manage each one independently. So when they split all their code into different packages, they did so in the shape of &lt;a href="https://github.com/babel/babel/blob/main/doc/design/monorepo.md" rel="noopener noreferrer"&gt;a monorepo&lt;/a&gt;. That decision kickstarted the development of what would eventually be called workspaces.&lt;/p&gt;

&lt;p&gt;Their internal tooling was soon released as &lt;a href="https://github.com/lerna/lerna/blob/v1.0.0/README.md" rel="noopener noreferrer"&gt;Lerna&lt;/a&gt;. Lerna's main job was to allow packages in a monorepo to seamlessly &lt;code&gt;require()&lt;/code&gt; one another as if they were already published, instead of having to edit, publish, and reinstall each one independently.&lt;/p&gt;

&lt;p&gt;A few months later in 2017, &lt;a href="https://classic.yarnpkg.com/blog/2016/10/11/introducing-yarn/" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt; came along. Yarn was created to solve some problematic behaviors of npm at the time, and dramatically improved performance and reliability. Alongside features like reproducible lockfiles and heavy caching, Yarn also introduced &lt;a href="https://classic.yarnpkg.com/blog/2017/08/02/introducing-workspaces/" rel="noopener noreferrer"&gt;workspaces as a first-class citizen&lt;/a&gt;. This was no surprise, as Yarn was created by some of the same people who had worked on Babel and Lerna.&lt;/p&gt;

&lt;p&gt;Soon, Yarn exploded in popularity, and in time, other package managers followed suit. pnpm added workspaces support in 2018, and npm followed in 2020. Nowadays, after a long evolution, workspaces are supported in all the mainstream package managers and widely used in both open and closed source projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems and solutions
&lt;/h2&gt;

&lt;p&gt;The exact definition of what a workspace is depends on the package manager you're using. The definition I gave above is more of an observation of what the main package managers have in common than a prescriptive specification. There isn't a common "workspaces spec". Each package manager has its own take, with different configuration formats, commands, and even different ways to declare dependencies.&lt;/p&gt;

&lt;p&gt;That said, there's a core of functionality you can expect across package managers: you declare a root folder that contains multiple sub-packages. Each sub-package is called a &lt;em&gt;workspace&lt;/em&gt;, and the whole set is a &lt;em&gt;project&lt;/em&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. From there, three features usually become available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Each workspace is its own full package&lt;/strong&gt;, as if it were an independent project. Each workspace has its own &lt;code&gt;package.json&lt;/code&gt;, where it declares dependencies, configuration, and scripts without worrying about the insides of other packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;All dependencies are resolved together&lt;/strong&gt; across the project, as if it were a single package. If two workspaces depend on the same package, it gets installed once and shared (assuming compatible version ranges).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workspaces can depend on each other by name&lt;/strong&gt;, as if they were regular dependencies. In a project with many workspaces, &lt;code&gt;pkg-a&lt;/code&gt; can import &lt;code&gt;pkg-b&lt;/code&gt; and everything works.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workspaces give us the best of both worlds: the encapsulation of small packages with the convenience of shared dependencies.&lt;/p&gt;

&lt;p&gt;The first feature is just the natural way of working with multiple packages, so it doesn't require much explanation. Let's look at the other two: why they're useful and how they're implemented. The exact implementation details depend on the package manager, so we'll use npm as a reference here, since it's the most widely used. The general concepts apply across the board.&lt;/p&gt;

&lt;h3&gt;
  
  
  The dependencies of the whole project are resolved together
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The problem
&lt;/h4&gt;

&lt;p&gt;Imagine testing a project with many interconnected packages ...say, &lt;a href="https://github.com/babel/babel/tree/main/packages" rel="noopener noreferrer"&gt;157 of them&lt;/a&gt;. You'd have to find out which packages depend on which others, run &lt;code&gt;npm install&lt;/code&gt; in each folder, and deal with the ongoing headache of keeping dependency versions in sync. Worse: if each package had its own &lt;code&gt;node_modules&lt;/code&gt;, you'd end up with tons of copies of the same dependencies installed in different places.&lt;/p&gt;

&lt;p&gt;Multiple copies aren't just a waste of disk space, they can cause bugs. In JavaScript, the same class defined in two different files is considered &lt;em&gt;different&lt;/em&gt;, even if the code is identical&lt;sup id="fnref2"&gt;2&lt;/sup&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyClass&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;foo&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;./foo/my-class&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;bar&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;./bar/my-class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For shared dependencies to work correctly, they need to be installed once, in a single place. That way, when different pieces of code import the same dependency, they get the same object.&lt;/p&gt;
&lt;h4&gt;
  
  
  The solution
&lt;/h4&gt;

&lt;p&gt;Most package managers already deduplicate dependencies for a single project. So to get this behaviour for free with workspaces, they cheat a bit: they treat all workspaces as dependencies of a single, "fake" root package. The existing resolution algorithm gets reused without changes.&lt;/p&gt;

&lt;p&gt;You can see this in any workspaces lockfile. Here's a simplified example for a regular package with no workspaces:&lt;/p&gt;


&lt;div class="ltag-row"&gt;
  &lt;div class="ltag-col"&gt;
  

&lt;p&gt;&lt;strong&gt;Graph&lt;/strong&gt;&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%2Fdetvvqbck5mvfpzbsjhn.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.amazonaws.com%2Fuploads%2Farticles%2Fdetvvqbck5mvfpzbsjhn.png" alt="Dependency graph showing how a shared dependency resolves to a single version. " width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;/div&gt;

&lt;div class="ltag-col"&gt;
  

&lt;p&gt;&lt;strong&gt;Lockfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;my-simple-package"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;left-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.0.0&lt;/span&gt;
    &lt;span&gt;right-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.0.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;left-pad@^1.0.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.1.0&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;core-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.1.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;right-pad@^1.0.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.4.0&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;core-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.4.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;core-pad@^1.1.0,^1.4.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.4.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;






&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The package depends on both &lt;code&gt;left-pad&lt;/code&gt; and &lt;code&gt;right-pad&lt;/code&gt;, which both depend on &lt;code&gt;core-pad&lt;/code&gt; with different version ranges. The package manager found a single version of &lt;code&gt;core-pad&lt;/code&gt; that satisfies both and installed it once.&lt;/p&gt;

&lt;p&gt;Here's the same scenario in a workspaces project:&lt;/p&gt;


&lt;div class="ltag-row"&gt;
  &lt;div class="ltag-col"&gt;
  

&lt;p&gt;&lt;strong&gt;Graph&lt;/strong&gt;&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%2Fgxulg14kx3cqn6soviak.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.amazonaws.com%2Fuploads%2Farticles%2Fgxulg14kx3cqn6soviak.png" alt="Dependency graph of a monorepo with workspaces. A root node " width="800" height="924"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;/div&gt;


&lt;div class="ltag-col"&gt;
  

&lt;p&gt;&lt;strong&gt;Lockfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;"&lt;/span&gt;&lt;span&gt;##fake-project-root##"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;workspace-a&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;local&lt;/span&gt;
    &lt;span&gt;workspace-b&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;local&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;workspace-a@local"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;left-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.0.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;workspace-b@local"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;right-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.0.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;left-pad@^1.0.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.1.0&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;core-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.1.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;right-pad@^1.0.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.4.0&lt;/span&gt;
  &lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;core-pad&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;^1.4.0&lt;/span&gt;

&lt;span&gt;"&lt;/span&gt;&lt;span&gt;core-pad@^1.1.0,^1.4.0"&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1.4.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;







&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;By pretending all workspaces are dependencies of a single root package, the package manager reuses its existing algorithm. It finds one version of &lt;code&gt;core-pad&lt;/code&gt; that works for both, installs it once, and any workspace that imports it gets the same object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workspaces can depend on each other by name
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The problem
&lt;/h4&gt;

&lt;p&gt;If you have two separate packages and want one to depend on the other, you have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publish the dependency&lt;/strong&gt; to the registry, then install it in the other package. Straightforward, but if you're iterating quickly, the publish-install cycle kills your flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a local file dependency.&lt;/strong&gt; Edit the imports to point to the local path. This works, but it's brittle: if you move packages around or share the code with someone else, paths break. If the dependency has its own dependencies, you have to install them manually. And you need to remember to switch paths back before publishing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is great. What you actually want is to say "my package depends on &lt;code&gt;pkg-b&lt;/code&gt;" and have the package manager figure out that &lt;code&gt;pkg-b&lt;/code&gt; is just in the folder next to you. That's exactly what workspaces do!&lt;/p&gt;

&lt;h4&gt;
  
  
  The solution
&lt;/h4&gt;

&lt;p&gt;This seems straightforward to implement. The package manager knows where all workspaces are, so it could point to the right folder when it finds a matching dependency name. But there's a catch: the dependency resolution of the package manager (npm, Yarn) is separate from the import resolution of the runtime (Node.js).&lt;/p&gt;

&lt;p&gt;If you've ever looked into your &lt;code&gt;node_modules&lt;/code&gt; and found dependencies nested inside other dependencies instead of a flat structure, this is why. The package manager has no influence on how Node.js finds imports&lt;sup id="fnref3"&gt;3&lt;/sup&gt;, all it can do is lay them out on disk in a way that Node.js will understand.&lt;/p&gt;

&lt;h5&gt;
  
  
  How Node.js finds packages
&lt;/h5&gt;

&lt;p&gt;The high-level idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the import is a relative or absolute path (&lt;code&gt;./&lt;/code&gt;, &lt;code&gt;../&lt;/code&gt;, or &lt;code&gt;/&lt;/code&gt;), Node.js follows that path directly.&lt;/li&gt;
&lt;li&gt;Otherwise, it looks for the import name in the nearest &lt;code&gt;node_modules&lt;/code&gt; folder, starting from the current file's directory, walking up the folder tree until it finds a match or reaches the filesystem root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if we want to import e.g. &lt;code&gt;left-pad&lt;/code&gt;, Node.js checks these folders in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From &lt;code&gt;&lt;b&gt;~/projects/app/src/&lt;/b&gt;utils.js&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&lt;b&gt;~/projects/app/src/&lt;/b&gt;node_modules/left-pad&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&lt;b&gt;~/projects/app/&lt;/b&gt;node_modules/left-pad&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&lt;b&gt;~/projects/&lt;/b&gt;node_modules/left-pad&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&lt;b&gt;~/&lt;/b&gt;node_modules/left-pad&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  Symlinks tie it all together
&lt;/h5&gt;

&lt;p&gt;We know we don't want multiple copies of the same workspace in different &lt;code&gt;node_modules&lt;/code&gt; folders, because that would bring back all the problems we discussed. Instead, we can use &lt;a href="https://en.wikipedia.org/wiki/Symbolic_link" rel="noopener noreferrer"&gt;symlinks&lt;/a&gt;. As a refresher: they are special files that just "redirect" to another file or folder when accessed.&lt;/p&gt;

&lt;p&gt;So now, this just becomes a problem of how to arrange the symlinks in the &lt;code&gt;node_modules&lt;/code&gt; folders for Node.js. And the solution: we'll create a symlink to each workspace in the top-level &lt;code&gt;node_modules&lt;/code&gt; folder. Since Node.js walks up the directory tree looking for that folder, every workspace will eventually find the root one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package-root/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node_modules/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sub-a&lt;/code&gt; &lt;em&gt;(symlink to &lt;code&gt;sub-a&lt;/code&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub-b&lt;/code&gt; &lt;em&gt;(symlink to &lt;code&gt;sub-b&lt;/code&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub-c&lt;/code&gt; &lt;em&gt;(symlink to &lt;code&gt;sub-c&lt;/code&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;sub-a/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;sub-b/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;sub-c/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;foo&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;sub-b&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="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from sub-b!&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;How does Node.js resolve the import in &lt;code&gt;sub-a/index.js&lt;/code&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package-root/sub-a/node_modules/sub-b&lt;/code&gt;
→ doesn't exist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package-root/node_modules/sub-b&lt;/code&gt;
→ symlink to &lt;code&gt;package-root/sub-b/&lt;/code&gt;, follow it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package-root/sub-b&lt;/code&gt;
→ found it!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By placing symlinks in the top-level &lt;code&gt;node_modules&lt;/code&gt;, any workspace can import any other workspace by name, and Node.js finds it correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  When (not) to use workspaces
&lt;/h2&gt;

&lt;p&gt;While workspaces are pretty useful, they are not a silver bullet. That is, you shouldn't go link every project in your &lt;code&gt;~/dev&lt;/code&gt; folder just yet. Instead, they should be treated as a scoped feature, with a clear sweet spot.&lt;/p&gt;

&lt;p&gt;In general, workspaces work best for projects that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consist of multiple packages&lt;/li&gt;
&lt;li&gt;Will be developed in tandem&lt;/li&gt;
&lt;li&gt;Share many dependencies&lt;/li&gt;
&lt;li&gt;Call each other frequently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why workspaces are mostly used in monorepos: almost by definition, they satisfy all of these criteria. Wasp uses them outside the monorepo context, but that's because we generate packages for each part of your app, that are tightly coupled and evolve together.&lt;/p&gt;

&lt;p&gt;On the other hand, I'd discourage using workspaces when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't have a good reason to split a single codebase or repo into multiple packages. Workspaces solve problems, but it's better if you don't have those problems in the first place.&lt;/li&gt;
&lt;li&gt;Your packages are mostly independent and don't share many dependencies. I wouldn't use workspaces to link all the random libraries in my &lt;code&gt;~/dev&lt;/code&gt; folder. It could lead to random interference between dependencies, or implicit relationships.&lt;/li&gt;
&lt;li&gt;You have cyclic dependencies. Without a clear story of which packages depend on which&lt;sup id="fnref4"&gt;4&lt;/sup&gt;, workspaces can allow any package depend on any other carelessly, and that can lead to a tangled web of dependencies.&lt;/li&gt;
&lt;li&gt;Your project is split across multiple repositories. Workspaces would make them no longer self-contained. Unless you're very sure of your use case, either unlink the repos altogether or merge them into a single monorepo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why does Wasp care about workspaces?
&lt;/h2&gt;

&lt;p&gt;When you run the Wasp compiler, it generates app code into a &lt;code&gt;.wasp/&lt;/code&gt; folder inside your project. And part of that generated code is three different packages: one for the SDK we generate from your Wasp spec, and a frontend and backend package that contain the final codebase for your deployed apps.&lt;/p&gt;

&lt;p&gt;Those three packages are tightly coupled, with frequent imports across packages, and also to your own code. They also share many dependencies, and we want to avoid any chance of mismatches or duplication between them.&lt;/p&gt;

&lt;p&gt;As part of our push for Wasp 1.0, we wanted to make this situation easier to work with, and we realized workspaces were a perfect fit for our problems. We worked on a project internally called &lt;a href="https://github.com/wasp-lang/wasp/issues/3119" rel="noopener noreferrer"&gt;Wasp Citizen&lt;/a&gt;, so we could capitalize on workspaces to ease our dependency mismatch issues.&lt;/p&gt;

&lt;p&gt;Since Wasp v0.19, this is at work in every Wasp project. Your app still works as a single-package project and you don't need to learn anything new; but internally, the generated packages are structured as workspaces. This translates to a better experience for you, fewer weird bugs for us, and quicker installs for everyone.&lt;/p&gt;

&lt;p&gt;And with this knowledge, if you want, now you can add your own workspaces to your Wasp project too, and have them play nicely with the generated ones!&lt;/p&gt;

&lt;h2&gt;
  
  
  Go off!
&lt;/h2&gt;

&lt;p&gt;Workspaces are a powerful tool that help you organize your code, avoid dependency errors, and speed up your development workflow. I hope this post has given you a good understanding of how they work under the hood and when to use them. If you find yourself in a situation where workspaces could help you, give them a try!&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Yarn has a great &lt;a href="https://yarnpkg.com/advanced/lexicon" rel="noopener noreferrer"&gt;glossary&lt;/a&gt; that you can check out if you want to get specific definitions for a package-manager-related term, and pointers to which feature they're a part of.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;This is one of the reasons React requires a single copy in your app. React relies on a single instance to track internal state (hooks, context, and reconciler). If you had two copies, components created with one wouldn't be recognized by the other, leading to cryptic errors.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Some newer runtimes have the package manager integrated directly into execution, so they can control both installation and resolution, and simply point to the right folder. For example, Deno doesn't even install dependencies in your project folder. Instead, it keeps a single global copy in an internal cache that it can programmatically reference.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Yarn has some great features that force you to be exhaustive about how your workspaces are structured. By default, you have to &lt;a href="https://yarnpkg.com/features/workspaces#cross-references" rel="noopener noreferrer"&gt;explicitly declare which other workspaces you depend on&lt;/a&gt;. They also have a &lt;a href="https://yarnpkg.com/features/constraints" rel="noopener noreferrer"&gt;workspace constraints feature&lt;/a&gt; that lets you enforce rules on which packages can depend on which others. This one has a steep learning curve, but it's worth it if you're working on a very big monorepo.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>npm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Thu, 26 Mar 2026 10:01:16 +0000</pubDate>
      <link>https://dev.to/cprecioso/-2j9m</link>
      <guid>https://dev.to/cprecioso/-2j9m</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/whats-the-best-way-to-vibe-code-a-saas-in-2026-9fh" class="crayons-story__hidden-navigation-link"&gt;What's the Best Way to Vibe Code a SaaS in 2026?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image" width="151" height="151"&gt;
          &lt;/a&gt;

          &lt;a href="/vincanger" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="vincanger profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/vincanger" class="crayons-story__secondary fw-medium m:hidden"&gt;
              vincanger
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                vincanger
                
              
              &lt;div id="story-author-preview-content-3407834" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/vincanger" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;vincanger&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/whats-the-best-way-to-vibe-code-a-saas-in-2026-9fh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/whats-the-best-way-to-vibe-code-a-saas-in-2026-9fh" id="article-link-3407834"&gt;
          What's the Best Way to Vibe Code a SaaS in 2026?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/sass"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;sass&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tooling"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tooling&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/whats-the-best-way-to-vibe-code-a-saas-in-2026-9fh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/whats-the-best-way-to-vibe-code-a-saas-in-2026-9fh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            15 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>sass</category>
      <category>ai</category>
      <category>tooling</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Mon, 02 Mar 2026 16:36:41 +0000</pubDate>
      <link>https://dev.to/cprecioso/-3pfl</link>
      <guid>https://dev.to/cprecioso/-3pfl</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/wasp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" alt="Wasp" width="151" height="151"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/wasp/what-are-the-best-full-stack-web-app-frameworks-in-2026-3d9m" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;What Are the Best Full-stack Web App Frameworks in 2026?&lt;/h2&gt;
      &lt;h3&gt;vincanger for Wasp ・ Mar 2&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vibecoding&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#fullstack&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>ai</category>
      <category>vibecoding</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>The best framework for spec-driven workflows 🔥</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Fri, 13 Feb 2026 13:28:08 +0000</pubDate>
      <link>https://dev.to/cprecioso/the-best-framework-for-spec-driven-workflows-n57</link>
      <guid>https://dev.to/cprecioso/the-best-framework-for-spec-driven-workflows-n57</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/wasp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" alt="Wasp" width="151" height="151"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/wasp/claude-code-for-fullstack-development-the-3-things-you-actually-need-1p6p" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Claude Code for Fullstack Development: The 3 Things You Actually Need&lt;/h2&gt;
      &lt;h3&gt;vincanger for Wasp ・ Feb 13&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#vibecoding&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#fullstack&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>vibecoding</category>
      <category>fullstack</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Mon, 13 Oct 2025 14:01:09 +0000</pubDate>
      <link>https://dev.to/cprecioso/-296e</link>
      <guid>https://dev.to/cprecioso/-296e</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/wasp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" alt="Wasp" width="151" height="151"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F331796%2F5d3fd56d-440c-437c-bcda-152c482774b9.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/wasp/join-wasp-design-ai-thon-pimp-our-website-and-win-trmnl-playdate-and-ob-4-3gj8" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;🎨 Join Wasp Design-AI-Thon - Pimp our website and win TRMNL, Playdate, and OB-4! 🤖&lt;/h2&gt;
      &lt;h3&gt;Matija Sosic for Wasp ・ Oct 13&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#vibecoding&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#design&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#hackathon&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>vibecoding</category>
      <category>design</category>
      <category>webdev</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Tue, 29 Jul 2025 12:15:38 +0000</pubDate>
      <link>https://dev.to/cprecioso/-58h</link>
      <guid>https://dev.to/cprecioso/-58h</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/open-saas-v20-shadcn-ui-llm-friendly-mors-and-more-ga" class="crayons-story__hidden-navigation-link"&gt;Open SaaS v2.0 -- ShadCN UI, LLM-friendly, MoRs, and more.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/vincanger" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="vincanger profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/vincanger" class="crayons-story__secondary fw-medium m:hidden"&gt;
              vincanger
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                vincanger
                
              
              &lt;div id="story-author-preview-content-2730573" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/vincanger" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;vincanger&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/open-saas-v20-shadcn-ui-llm-friendly-mors-and-more-ga" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 29 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/open-saas-v20-shadcn-ui-llm-friendly-mors-and-more-ga" id="article-link-2730573"&gt;
          Open SaaS v2.0 -- ShadCN UI, LLM-friendly, MoRs, and more.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/saas"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;saas&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/boilerplate"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;boilerplate&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/open-saas-v20-shadcn-ui-llm-friendly-mors-and-more-ga" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;100&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/open-saas-v20-shadcn-ui-llm-friendly-mors-and-more-ga#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>saas</category>
      <category>boilerplate</category>
      <category>react</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Tue, 29 Apr 2025 12:29:13 +0000</pubDate>
      <link>https://dev.to/cprecioso/-546c</link>
      <guid>https://dev.to/cprecioso/-546c</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/from-0-to-10k-how-open-saas-became-the-free-boilerplate-devs-love-45hb" class="crayons-story__hidden-navigation-link"&gt;From 0 to 10K ⭐: How Open SaaS became the Free Boilerplate Devs Love&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/vincanger" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="vincanger profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/vincanger" class="crayons-story__secondary fw-medium m:hidden"&gt;
              vincanger
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                vincanger
                
              
              &lt;div id="story-author-preview-content-2429632" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/vincanger" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;vincanger&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/from-0-to-10k-how-open-saas-became-the-free-boilerplate-devs-love-45hb" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 29 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/from-0-to-10k-how-open-saas-became-the-free-boilerplate-devs-love-45hb" id="article-link-2429632"&gt;
          From 0 to 10K ⭐: How Open SaaS became the Free Boilerplate Devs Love
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/from-0-to-10k-how-open-saas-became-the-free-boilerplate-devs-love-45hb" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;223&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/from-0-to-10k-how-open-saas-became-the-free-boilerplate-devs-love-45hb#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>softwaredevelopment</category>
      <category>webdev</category>
      <category>development</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Wed, 23 Apr 2025 15:05:59 +0000</pubDate>
      <link>https://dev.to/cprecioso/-4ome</link>
      <guid>https://dev.to/cprecioso/-4ome</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/wasp-a-full-stack-laravel-for-js-framework-vibe-coding-approved-473g" class="crayons-story__hidden-navigation-link"&gt;🐝 Wasp: a full-stack, "Laravel for JS" framework. Vibe-coding approved 🕹️&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/matijasos" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F331796%2F5d3fd56d-440c-437c-bcda-152c482774b9.jpeg" alt="matijasos profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/matijasos" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Matija Sosic
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Matija Sosic
                
              
              &lt;div id="story-author-preview-content-2427068" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/matijasos" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F331796%2F5d3fd56d-440c-437c-bcda-152c482774b9.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Matija Sosic&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/wasp-a-full-stack-laravel-for-js-framework-vibe-coding-approved-473g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 23 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/wasp-a-full-stack-laravel-for-js-framework-vibe-coding-approved-473g" id="article-link-2427068"&gt;
          🐝 Wasp: a full-stack, "Laravel for JS" framework. Vibe-coding approved 🕹️
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vibecoding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vibecoding&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/wasp-a-full-stack-laravel-for-js-framework-vibe-coding-approved-473g" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;177&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/wasp-a-full-stack-laravel-for-js-framework-vibe-coding-approved-473g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>react</category>
      <category>vibecoding</category>
      <category>ai</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Carlos Precioso</dc:creator>
      <pubDate>Wed, 16 Apr 2025 12:37:25 +0000</pubDate>
      <link>https://dev.to/cprecioso/-5037</link>
      <guid>https://dev.to/cprecioso/-5037</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l" class="crayons-story__hidden-navigation-link"&gt;A Structured Workflow for "Vibe Coding" Full-Stack Apps&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/wasp"&gt;
            &lt;img alt="Wasp logo" 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%2Forganization%2Fprofile_image%2F3369%2Fc86918f8-76a9-4b01-accf-cc257f9ee56f.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/vincanger" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" alt="vincanger profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/vincanger" class="crayons-story__secondary fw-medium m:hidden"&gt;
              vincanger
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                vincanger
                
              
              &lt;div id="story-author-preview-content-2409125" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/vincanger" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F982343%2F99b7a039-13b3-4eb5-820b-da74b180ddf6.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;vincanger&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/wasp" class="crayons-story__secondary fw-medium"&gt;Wasp&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 16 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l" id="article-link-2409125"&gt;
          A Structured Workflow for "Vibe Coding" Full-Stack Apps
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vibecoding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vibecoding&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/fullstack"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;fullstack&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;425&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/wasp/a-structured-workflow-for-vibe-coding-full-stack-apps-352l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

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

&lt;/div&gt;


</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>fullstack</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
