TypeScript

Tailwind upgrade: Could not load the configuration file

Reproduced on Windows 11 with a virtual drive: the exact failing version, why @next still breaks today, and the command that migrates a v3 project cleanly.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

On Windows, npx @tailwindcss/upgrade up to 4.0.0 fails with Could not load the configuration file: Can't resolve '././E:\…\tailwind.config.js' when your project lives on a different drive from the npm cache. Tailwind fixed it in PR #15927, released in 4.0.1. Do not pin @next: on npm that tag still points at an early 4.0 tool, which now crashes for a different reason. Run the untagged command:

bash
npx @tailwindcss/upgrade

The error#

The Stack Overflow question shows a Vite + React project on Tailwind v3 where npx @tailwindcss/upgrade@next --force stopped at the configuration step. The bug report it links, issue #15220, has the full line: the project was on E:, the npm cache on C:.

The same failure, reproduced on Windows 11 with Node 26.7.0, a Tailwind 3.4.19 project on a subst drive Q: and the npm cache on C::

text
$ cd /q/run1 && NO_COLOR=1 npx -y @tailwindcss/upgrade@4.0.0 --force
≈ tailwindcss v4.0.0
 
│ Searching for CSS files in the current directory and its subdirectories… 
 
│ ↳ Linked `.\tailwind.config.js` to `.\src\index.css` 
 
│ Migrating JavaScript configuration files… 
 
│ ↳ Could not load the configuration file: Can't resolve '././Q:\run1\tailwind.config.js' in 'C:\Users\mahdi\AppData\Local\npm-cache\_npx\cdd4d330f43063a9\node_modules\@tailwindcss\upgrade\dist' 
 
exit code: 1

Two details give the cause away: the doubled ././ in front of a full Windows path, and the folder it searched, which is the upgrade tool's own dist folder inside the npx cache, not your project.

Why the path breaks across drives#

To read your v3 config, the tool compiles a small stylesheet containing an @config directive that points at your tailwind.config.js. In 4.0.0, prepare-config.ts builds that path with Node's path.relative() from __dirname, the tool's own install folder, to your config file, adds ./ when the result does not start with a dot, then writes it into a template that starts with another ./.

On a single drive that works, because path.relative() returns something like ..\..\project\tailwind.config.js. Between two drive letters no relative path exists, so Node returns the target's absolute path. The PR description quoted in the accepted answer puts it this way: "The issue on Windows specifically is that file.relative(…) can also return a legit absolute path, e.g. when the file is on a different drive." The tool still prefixed it, and the resolver looked for a file literally named ././Q:\run1\tailwind.config.js inside its own dist folder. A comment in the source gives the reason for starting there: "This is required so that the base for Tailwind CSS can bet inside the @tailwindcss-upgrade package and we can require tailwindcss properly."

The same steps can be replayed on any operating system with Node's Windows path rules. This script copies the 4.0.0 logic:

js
// Same steps as prepare-config.ts in @tailwindcss/upgrade 4.0.0, with Windows path rules
const path = require('node:path').win32
const dist = String.raw`C:\Users\dev\AppData\Local\npm-cache\_npx\0044b4041b3b52e1\node_modules\@tailwindcss\upgrade\dist`
for (const file of [String.raw`Q:\project\tailwind.config.js`, String.raw`C:\project\tailwind.config.js`]) {
  let relative = path.relative(dist, file)
  console.log(`relative: ${relative}  isAbsolute: ${path.isAbsolute(relative)}`)
  if (!relative.startsWith('.')) relative = './' + relative
  console.log(`@config './${relative}';`)
}

Saved as relative.cjs and run with node, it prints the broken path for the second drive and a working one for the same drive:

text
relative: Q:\project\tailwind.config.js  isAbsolute: true
@config '././Q:\project\tailwind.config.js';
relative: ..\..\..\..\..\..\..\..\..\..\..\project\tailwind.config.js  isAbsolute: false
@config './..\..\..\..\..\..\..\..\..\..\..\project\tailwind.config.js';

The fix commit skips the ./ prefix when the path is absolute (a path.isAbsolute check), both in prepare-config.ts and in the stylesheet-linking step of migrate.ts, and it drops the hard-coded ./ from the @config template. It was merged on 28 January 2025 and is listed under 4.0.1 in the changelog as "Ensure JavaScript config files on different drives are correctly migrated". The GitHub compare API places the merge commit after the v4.0.0 tag and inside v4.0.1.

Nothing in your project causes this, so editing tailwind.config.js or the CSS file will not help. The reproduction's negative control confirms it: the same 4.0.0 tool on the same project, copied to C:, did not raise the Can't resolve error. It stopped later on an unrelated error, explained in the next section.

Fix: run the current upgrade tool#

  1. Commit or stash your work first. The current tool stops with "Git directory is not clean" when there are uncommitted changes, unless you pass --force (see index.ts). The upgrade guide also recommends a new branch and Node.js 20 or higher.
bash
git switch -c tailwind-v4
  1. Run the tool without a version tag, from the project root, on whatever drive it is on:
bash
npx @tailwindcss/upgrade

That is the command printed in the upgrade guide. Running the tool from WSL, the workaround in the accepted answer, is no longer needed. Moving the project to C: does not rescue an old pinned version either: on C:, 4.0.0 got past the path bug and then hit the crash described next.

Why @next still fails today#

The question uses the next tag. On 6 November 2026 the npm registry lists next as 4.0.6, published on 10 February 2025, and latest as 4.3.5. Version 4.0.6 contains the drive fix, but it crashes one step later:

text
$ cd /q/run-next && NO_COLOR=1 npx -y @tailwindcss/upgrade@next --force
≈ tailwindcss v4.0.6
 
│ Searching for CSS files in the current directory and its subdirectories… 
 
│ ↳ Linked `.\tailwind.config.js` to `.\src\index.css` 
 
│ Migrating JavaScript configuration files… 
 
Error: Missing field `negated` on ScannerOptions.sources
    at Ml (file:///C:/Users/mahdi/AppData/Local/npm-cache/_npx/0044b4041b3b52e1/node_modules/@tailwindcss/upgrade/dist/index.mjs:227:24)
    at jl (file:///C:/Users/mahdi/AppData/Local/npm-cache/_npx/0044b4041b3b52e1/node_modules/@tailwindcss/upgrade/dist/index.mjs:226:153)
    at Gi (file:///C:/Users/mahdi/AppData/Local/npm-cache/_npx/0044b4041b3b52e1/node_modules/@tailwindcss/upgrade/dist/index.mjs:210:1259)
    at async ca (file:///C:/Users/mahdi/AppData/Local/npm-cache/_npx/0044b4041b3b52e1/node_modules/@tailwindcss/upgrade/dist/index.mjs:229:5368) {
  code: 'InvalidArg'
}
exit code: 1

The registry manifest of 4.0.6 declares its two companion packages, the native @tailwindcss/oxide and @tailwindcss/node, with caret ranges (^4.0.6), so npx installs today's 4.3.5 of both next to the old tool. The reproduction found 4.3.5 of both in the npx installs of 4.0.0, 4.0.1 and 4.0.6, and all three crashed with this error. Changing only that pairing fixes it: the same 4.0.6 tool, run with both companions pinned to 4.0.6, migrated the project and exited with code 0.

This is a known break. Issue #17500 reports the same Missing field error from @tailwindcss/postcss 4.0.0 picking up a newer @tailwindcss/node. A maintainer answered that it was fixed in 4.0.7, and the 4.0.7 changelog lists "Pin exact version of tailwindcss in @tailwindcss/* packages". To someone who hit the error through a @next command, the maintainer replied: "use @latest instead of @next to get the latest version."

On Q:, 4.0.1 no longer raised the Can't resolve error, which confirms the drive fix shipped there, but none of the early 4.0 tools can finish an upgrade any more. Read the first line the tool prints, ≈ tailwindcss v…, to see which version actually ran.

Verify the migration#

On the Q: drive that broke 4.0.0, the untagged command ran 4.3.5 and completed:

text
$ cd /q/run-latest && NO_COLOR=1 npx -y @tailwindcss/upgrade --force
≈ tailwindcss v4.3.5
 
│ ↳ Upgrading from Tailwind CSS `v3.4.19` 
 
│ Searching for CSS files in the current directory and its subdirectories… 
 
│ ↳ Linked `.\tailwind.config.js` to `.\src\index.css` 
 
│ Migrating JavaScript configuration files… 
 
│ ↳ Migrated configuration file: `.\tailwind.config.js` 
 
│ Migrating stylesheets… 
 
│ ↳ Migrated stylesheet: `.\src\index.css` 
 
│ Updating dependencies… 
 
│ ↳ Updated package: `tailwindcss` 
 
│ Migrating templates… 
 
│ ↳ Migrated templates for configuration file: `.\tailwind.config.js` (0 files changed) 
 
│ ↳ No PostCSS config found, skipping migration. 
 
fatal: not a git repository (or any of the parent directories): .git
│ No changes were made to your repository. 
 
exit code: 0

Check three things after the run:

  • the stylesheet now starts with @import 'tailwindcss';, and your theme extensions have become CSS variables in an @theme block (here --color-brand: #1d4ed8;);
  • tailwind.config.js is gone when everything in it could be moved to CSS;
  • package.json lists tailwindcss 4.x.

The test project also printed "fatal: not a git repository", because it was not a Git repository. Inside a repository, run git diff and read the changes before committing.

When the tool succeeds but the build does not#

The test project had no PostCSS config, and the tool printed "No PostCSS config found, skipping migration". A real Vite project usually has one. If the dev server then fails with "It looks like you're trying to use tailwindcss directly as a PostCSS plugin", your PostCSS config still loads the v3 plugin: the PostCSS plugin fix explains it, and the upgrade guide recommends the @tailwindcss/vite plugin for Vite instead. If adding that plugin fails with Failed to resolve "@tailwindcss/vite". This package is ESM only, Vite is reading your config as CommonJS: see the ESM-only fix.

If you skip the tool and keep a JavaScript config, remember that v4 does not detect tailwind.config.js on its own. Custom classes such as border-border then fail, as in the border-border fix, which covers the @config path. A v3 safelist is not restored by @config either: Tailwind v4 @source inline() replaces it.

So the asker's project was never at fault, and neither was --force. A Windows path quirk in an early 4.0 release of the tool caused the error, and the next tag still installs one of those early releases. Run the untagged npx @tailwindcss/upgrade and check the version on its first line of output.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.