This document tracks known bugs, quirks, and workarounds in the project.
Tailwind CSS v4 arbitrary value utilities fail to generate when there is a directory name collision between the project root storage directory and an app/pages/ route directory.
- Arbitrary Tailwind utilities (e.g.,
grid-cols-[110px_1fr],w-[200px]) fail to generate in the final CSS - Standard Tailwind utilities (e.g.,
grid-cols-2,w-20,bg-red-500) work correctly in the same file - The issue appears to be path-specific, affecting only certain route files
- HMR (Hot Module Replacement) may not properly trigger rebuilds for the affected files
Path collision between root-level storage directory and route directory.
When a directory exists at the project root (e.g., books/) AND a route directory exists with the same name (e.g., app/pages/books/), Vite's file watcher and/or Tailwind v4's content scanner becomes confused about which path to track, causing:
- Incomplete or failed content scanning for arbitrary values
- HMR cache corruption or invalidation failures
- File watcher glitches that prevent proper rebuilds
- Initial state: Books stored in
data/books/- everything worked fine - Change made: Moved book storage from
data/books/to root-levelbooks/directory (to prepare for Docker) - Bug appeared: Immediately after the move,
app/pages/books/[id].vuestopped generating arbitrary Tailwind utilities - Debugging: Spent significant time checking syntax, cache, HMR, Tailwind config - all correct
- Accidental discovery: Renamed route from
app/pages/books/toapp/pages/book/→ bug instantly resolved - Confirmation: The path collision between
books/(storage) andapp/pages/books/(routes) was the cause
To reproduce this bug in a Nuxt 4 + Tailwind v4 + Vite project:
- Create a directory at project root:
mkdir books - Create a route directory:
mkdir -p app/pages/books - Create a route file:
app/pages/books/[id].vuewith arbitrary Tailwind utilities:<template> <div class="grid grid-cols-[200px_1fr] gap-4"> <div>Fixed column</div> <div>Flexible column</div> </div> </template>
- Run dev server and navigate to the route
- Inspect the element in DevTools - the
grid-cols-[200px_1fr]class will not be in the generated CSS - Standard utilities like
grid,gap-4will work correctly
To confirm the fix:
- Rename
app/pages/books/toapp/pages/book/ - The arbitrary utilities will now generate correctly
- Nuxt: 4.x
- Tailwind CSS: v4.x (via
@tailwindcss/viteplugin) - Vite: Latest (bundled with Nuxt 4)
- Node: 18+
- Package Manager: pnpm
Option 1: Avoid path collisions (recommended)
- Use different names for storage directories and route directories
- Examples:
- Storage:
library/orbook-storage/instead ofbooks/ - Routes:
app/pages/book/instead ofapp/pages/books/
- Storage:
- This is the most reliable solution
Option 2: Use alternative CSS patterns
- Replace
grid grid-cols-[110px_1fr]withflex+ fixed width utilities - Example:
flex gap-2withw-28 shrink-0on the label andflex-1on the content - Avoids arbitrary values entirely while achieving the same visual result
Option 3: Add explicit Tailwind config
- Create a
tailwind.config.tswith explicitcontentpaths and asafelistfor the needed arbitrary values - Forces generation but is more manual and defeats the purpose of arbitrary values
This bug likely exists in one of these projects:
-
Vite (https://github.com/vitejs/vite)
- File watcher and HMR implementation
- May have issues with similar path patterns
-
@tailwindcss/vite (https://github.com/tailwindlabs/tailwindcss/tree/next/packages/%40tailwindcss-vite)
- Tailwind v4's Vite plugin
- Content scanner and arbitrary value extraction
-
Nuxt (https://github.com/nuxt/nuxt)
- May be related to how Nuxt integrates Vite and handles route resolution
Recommended approach: File an issue with @tailwindcss/vite first, as the content scanning is most directly related. Include:
- This reproduction case
- The timeline showing it started after creating the root-level
books/directory - Environment details listed above
Resolved in this project - Storage directory renamed to library/ and routes use app/pages/book/ to avoid any path collisions.
January 2025 (when the root cause was identified)
Additional known issues will be documented here as they are discovered.
If you see hydration mismatch warnings involving the header collection switcher (e.g. server renders All but the client expects Personal), it’s because persisted Pinia state is restored from localStorage on the client, but SSR renders with the store’s default state.
Implementation detail: components that depend on persisted localStorage state should be wrapped in ClientOnly (example: app/components/AppHeader.vue).
Alternative pattern: render SSR-stable defaults until onMounted and then switch to persisted state on the client (example: the Books filters highlight in app/pages/books/index.vue).