Ask
104
@two_calendars ·

vite 6 dev cold start is 41s in a 22-package workspace, deps re-optimize every reload Vite

22 packages in a pnpm workspace, one Vite app that consumes about 9 of them directly. pnpm dev from a cold dep cache takes 41 seconds to first paint, and during that it prints the "new dependencies optimized" message twice and full-page reloads each time.

Warm restart is 1.9s so the cache works, but any lockfile change or a --force and I am back to 41s. It also happens on every fresh CI preview and for anyone who just cloned the repo.

The workspace packages are consumed as source - main points at src/index.ts and Vite compiles them. That was a deliberate choice so we do not have to build 22 packages to run the app, and I would rather not give it up.

Is 41s just the price of consuming source, or am I doing something obviously wrong?

10 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @whetstone_wu · 2mo ago · 2 replies

    Do the export conditions properly and you get to keep source-in-dev and built-output everywhere else:

    "exports": {
      ".": {
        "development": "./src/index.ts",
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    }
    

    Vite resolves the development condition in dev, so your dev server reads source and HMR works across package boundaries. Everything else - your test runner, your build, anything consuming the package from outside - gets dist. You stop needing main to point at TypeScript, which quietly breaks a lot of tooling that expects main to be JavaScript.

    Make sure resolve.conditions in your Vite config has not been overridden to something that drops development. If you have set that array yourself you probably clobbered the defaults.

    58
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @durable_ines · 2mo ago

      Complementary trick: set optimizeDeps.exclude for your own workspace packages. You do not want esbuild pre-bundling your own source, you want Vite serving it so HMR still works. If a linked package accidentally ends up in the optimized bundle you lose hot reload for it and spend an hour confused about why saving a file does nothing.

      22
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @kraut_corner · 2mo ago · 3 replies

    You are not paying for consuming source, you are paying for discovering dependencies twice.

    Here is what happens. Vite scans your entry point, finds the bare imports it can see, pre-bundles those with esbuild, and starts serving. Then the browser requests a file from one of your workspace packages, that file imports date-fns or whatever, and Vite has never seen it - so it stops, optimizes the new dependency, and hard-reloads the page. Each of those round trips is expensive and you are getting two.

    The fix is to tell it up front:

    optimizeDeps: {
      include: ['date-fns', 'zod', 'lodash-es/debounce', '@scope/ui > react-aria'],
      entries: ['index.html', 'src/**/*.tsx'],
    }
    

    The > syntax handles transitive deps of a linked package, which is the case the scanner is worst at. Getting the list right took me one pass of reading the reload messages and writing down every package it named.

    That took us from 41s to about 13s. The rest of the way was the export condition thing someone else will inevitably mention.

    One more: the scanner does not follow deps of linked workspace packages by default, which is precisely why a monorepo hits this and a single-package app almost never does.

    81
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @two_calendars · 2mo ago

      13.4s with the include list, from 41.2. The two reloads are gone. I had read about optimizeDeps.include and assumed it was a micro-optimisation rather than the actual fix.

      24
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @whetstone_wu · 2mo ago

      It is also the fix for the maddening version of this where the page reloads while you are typing because you just imported something new from a shared package. Same mechanism, just spread out over your day instead of concentrated at startup.

      20
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @dartfit_dana · 2mo ago

    server.warmup.clientFiles is worth a look once you have done the above. You give it a glob of the modules your app requests first and Vite transforms them before the browser asks. It does nothing for dep optimization but it removes the transform stall right after the page loads, which is a decent chunk of perceived cold start on a big app.

    40
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @durable_ines · 2mo ago

    Check your barrel files while you are in there. One index.ts that re-exports 300 modules means importing a single component pulls the whole package through the transform pipeline. We deleted the barrels from our two biggest shared packages and dev-server work dropped noticeably - and tree shaking in the production build improved as a bonus.

    26
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @corwin_ashby · 2mo ago · 2 replies

    Honestly at 22 packages I would just move to Bun. The dev server is effectively instant and you delete the entire optimizeDeps concept because it does not need a pre-bundling step.

    11
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @kraut_corner · 2mo ago

      Not a drop-in if you have any real Vite plugin chain - and in a 22-package workspace you do. Also "no pre-bundling" trades one cost for another rather than removing it. Worth evaluating on a greenfield app, not worth it as an answer to a 40 second cold start that has a 20 minute fix.

      8
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @barre_chord_blues · 2mo ago

    41 seconds is roughly one coffee, which is why nobody on the team ever reported it as a bug.

    5
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report