Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent CSS assets between client and SSR builds with @tailwindcss/vite #16389

Closed
mkdynamic opened this issue Feb 9, 2025 · 11 comments · Fixed by #16631
Closed

Inconsistent CSS assets between client and SSR builds with @tailwindcss/vite #16389

mkdynamic opened this issue Feb 9, 2025 · 11 comments · Fixed by #16631
Labels

Comments

@mkdynamic
Copy link

What version of Tailwind CSS are you using?

4.0.5

What build tool (or framework if it abstracts the build tool) are you using?

Vite 6.1.0

What version of Node.js are you using?

Bun 1.2.2

What browser are you using?

N/A

What operating system are you using?

macOS

Reproduction URL

Describe your issue

When using @tailwindcss/vite, and creating an SSR build with Vite, the CSS asset paths differ between the SSR and client builds. This is not true with @tailwindcss/postcss.

The above repos contain a minimal reproduction case, starting from a fresh React Vite project. Here are the reproduction steps below showing the bug:

With the @tailwindcss/vite plugin:

$ bun build:client
$ vite build --outDir build/client --manifest
vite v6.1.0 building for production...
✓ 29 modules transformed.
build/client/.vite/manifest.json         0.38 kB │ gzip:  0.21 kB
build/client/index.html                  0.39 kB │ gzip:  0.27 kB
build/client/assets/App-DWNkNxeI.css     6.10 kB │ gzip:  1.79 kB
build/client/assets/index-DO9bOB2u.js  185.88 kB │ gzip: 58.73 kB
✓ built in 463ms

$ bun build:server
$ vite build --outDir build/server --ssr=src/entry-ssr.tsx
vite v6.1.0 building SSR bundle for production...
✓ 4 modules transformed.
build/server/entry-ssr.js  0.46 kB
✓ built in 55ms

$ grep App- build/server/entry-ssr.js
const stylesheet = "/assets/App-DX11wV_z.css";

Note the asset path of the built App.css differs between the SSR and client builds (i.e. App-BYq-i2yi.css !== App-DWNkNxeI.css). This means that SSR rendered responses have the incorrect asset URL (which leads to a hydration error, if you are hydrating – or a missing CSS asset if you are only SSR rendering).

With the @tailwindcss/postcss based plugin:

$ bun build:client
$ vite build --outDir build/client --manifest
vite v6.1.0 building for production...
✓ 29 modules transformed.
build/client/index.html                  0.39 kB │ gzip:  0.27 kB
build/client/.vite/manifest.json         0.40 kB │ gzip:  0.21 kB
build/client/assets/App-BYq-i2yi.css     5.48 kB │ gzip:  1.69 kB
build/client/assets/index-BjxNUz_i.js  185.88 kB │ gzip: 58.73 kB
✓ built in 594ms

$ bun build:server
$ vite build --outDir build/server --ssr=src/entry-ssr.tsx
vite v6.1.0 building SSR bundle for production...
✓ 4 modules transformed.
build/server/entry-ssr.js  0.46 kB
✓ built in 193ms

$ grep App- build/server/entry-ssr.js
const stylesheet = "/assets/App-BYq-i2yi.css";

Note the asset path of the built App.css matches the SSR and client builds (i.e. App-BYq-i2yi.css === App-BYq-i2yi.css).

Let me know if I can provide any more information.

@mkdynamic
Copy link
Author

Forgot to mention, this behavior is identical with Node v20.11.0 – not Bun specific.

@philipp-spiess
Copy link
Member

Hey! The way the Vite extension works is by using the Vite module graph to find out what class names you are actually using and since you're running two separate builds with two separate module graphs, we will also yield two separate CSS files.

You can make sure the same CSS files are generated by opting-out of automatic content scanning: https://tailwindcss.com/docs/detecting-classes-in-source-files#disabling-automatic-detection

Will have to see what we can do about this, thanks for the report!

@mkdynamic
Copy link
Author

mkdynamic commented Feb 10, 2025

@philipp-spiess Roger. Thanks, I will try that.

For what it's worth the client and server bundles, although obviously different, include precisely the same set of files that reference any tailwind classes. So my expectation was that the resulting tailwind CSS file would be deterministic and identical. But this is definitely above my pay grade in terms of how Vite module graphs works :)

@philipp-spiess
Copy link
Member

@mkdynamic Yeah, I agree that this is super confusing. We are seeing similar issues across other popular frameworks as well so this is not the end of the story yet. Do you use Vite with a framework like React Router/Astro or just the config you provided as-is?

@mkdynamic
Copy link
Author

@philipp-spiess 👍 We are using a custom framework. But I do observe the same behavior when using the most "vanilla" SSR setup with Vite in the above repos. Our framework does build the client and SSR bundles in the same way.

@mkdynamic
Copy link
Author

mkdynamic commented Feb 10, 2025

You can make sure the same CSS files are generated by opting-out of automatic content scanning: https://tailwindcss.com/docs/detecting-classes-in-source-files#disabling-automatic-detection

@philipp-spiess Thanks for this – doing this solved the issue, I now see consistent builds between client and SSR. This is a fine solution for us, much better than the sketchy Vite plugin I wrote to "fix" the asset paths after the build – very happy to throw that away :)

@philipp-spiess
Copy link
Member

Glad it works now! What we're thinking of is a Vite plugin API to disable the module graph based lookups for cases like this. Will report back once we have made some decisions and something for you to test 👍

@SoraKumo001
Copy link

SoraKumo001 commented Feb 14, 2025

The following settings ensure consistent class names.

  • vite.config.ts
export default defineConfig(() => ({

  css: {
    transformer: "lightningcss",
  },}));

In this repository, the class name outputs correctly with that setting.
https://github.com/SoraKumo001/react-router7-hono

@philipp-spiess
Copy link
Member

@mkdynamic Hey! We decided to land some changes in the Vite plugin that will ensure that both builds now have access to the same class name list. We plan to release this in a patch very soon (hopefully today!). Thanks for the bug report. 🙇

@mkdynamic
Copy link
Author

@philipp-spiess Woot! Excited to try once it ships.

@jasonsilberman
Copy link

hey! just came across this issue and i have been facing the same problem with tanstack start. any chance we could get that version release so we can update and take advantage of this fix?

thanks! tailwind v4 has been great so far otherwise 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants