nuxt-booster icon indicating copy to clipboard operation
nuxt-booster copied to clipboard

`nuxt-booster` breaks `url("..")` CSS functions with quotes in Nuxt 4 on static site generation, breaking all styles

Open codeflorist opened this issue 6 months ago â€Ē 11 comments

EDIT: After further debugging, i've identified the source of the issue in this comment: https://github.com/basics/nuxt-booster/issues/1318#issuecomment-3114612976

Original (obsolete) post:

Describe the bug When using nuxt-booster with Tailwind v4 and nuxt generate, all tailwind-classes are gone and everything is unstyled.

This happens with the current v7.0.0-beta.0 version, of @nuxtjs/tailwindcss, as well as with the manual solution outlined here.

To Reproduce Steps to reproduce the behavior:

  1. Download reproduction at https://stackblitz.com/edit/nuxt-starter-r1ta6tdy
  2. Call pnpm install --shamefully-hoist && pnpm generate && pnpm preview
  3. The text should be bold and purple, but it is unstyled.

When removing nuxt-booster, or using pnpm build instead of pnpm generate, or pnpm dev, the text is correctly styled.

Expected behavior Tailwind classes should not get removed.

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser Chrome
  • Version 122.0.6261.129 (Offizieller Build) (64-Bit)

codeflorist avatar Jul 14 '25 16:07 codeflorist

@codeflorist got any hints? i have the same problem with WindiCSS

aletede21 avatar Jul 15 '25 14:07 aletede21

Hello,

I have now had a look at the problem myself. Could narrow it down and reproduce it in a separate repo without nuxt-booster. https://github.com/ThornWalli/nuxt-booster-tailwind-issue

Unfortunately, I haven't found a solution so far, even more irrational than before.

In the repo there is an example module, this deals with wrapping the Vite entry. https://github.com/ThornWalli/nuxt-booster-tailwind-issue/blob/main/modules/test/index.ts

Basically quite simple and should work. The entry from the build is imported there and exported back.

https://github.com/ThornWalli/nuxt-booster-tailwind-issue/blob/0d02aa8dc94c6c13868caff44a9bc98fd0d8272e/modules/test/index.ts#L18-L41

Unfortunately, this is a key location of nuxt-booster. If the feature is deactivated via the performance property (https://basics.github.io/nuxt-booster/guide/options.html#detection) it may result in bad lighthouse values.

ThornWalli avatar Jul 16 '25 15:07 ThornWalli

@ThornWalli Many thanks for researching this!

So this is an upstream bug in Nuxt or Vite? Also interesting is, that you are using v6 of @nuxtjs/tailwindcss in your reproduction. I encountered the problem only after switching to the v7 beta.

If it's a reproducable upstream-bug, would it make sense to open an issue with Nuxt or Vite?

codeflorist avatar Jul 17 '25 07:07 codeflorist

That's a very good question 🙂

When I think about the small problems with nuxt in the last few months, I would rather see the problem there.

If I look at the Tailwind module, nothing more happens than defining a plugin and putting a CSS file in nuxt.options.css.

Pure basics...

ThornWalli avatar Jul 17 '25 15:07 ThornWalli

@ThornWalli

I realized, that i actually have all detection settings of nuxt-booster turned off in my project, which means this is a separate issue. So i dug a little deeper, and found out my actual issue.

The problem happens, if the following criteria are met:

  • Nuxt v4 (or Nuxt v3 with future.compatibilityVersion: 4) is used.
  • nuxt-booster is used.
  • the CSS contains a data-url (e.g. url("data:image...) expression somewhere.

If these 3 criteria are met, nuxt-booster transforms the data-url into url(/_nuxt/"data:image, inserting a /_nuxt/ on static generation. This produces invalid CSS and the browser ignores the whole line. And since all CSS is in one line, no styles remain at all.

I have created a new reproduction here: https://stackblitz.com/edit/nuxt-starter-cjbfhydj

So something must have changed in Nuxt v4, that causes nuxt-booster to insert this /_nuxt/, where it shouldn't.

(This means Tailwind actually has nothing to do with the problem. It just happened, because i was also using the @tailwindcss/forms, which coincidentally had a data-url in its styles.)

@aletede21 Maybe the problem is the same in your case. You can check in the generated HTML-files, if it includes a broken data-url like i mentioned above.

codeflorist avatar Jul 22 '25 21:07 codeflorist

Hello @codeflorist, I added future.compatibilityVersion: 4 to my example.

Unfortunately, I have to say that in my example without nuxt-booster and an entry wrapper, the styling is visible.

This is a good example of what is completely wrong with their update.

With the latest nuxt 3 and nuxt 4 versions. I currently have the problem that even css in a plugin is not being pulled.

Everything is correct in the module itself, but we are having problems on the outside.

😕

ThornWalli avatar Jul 24 '25 15:07 ThornWalli

@ThornWalli

I've now found the source of the problem in nuxt-booster.

In the prepareUrls function here the url gets run through a regex-replace:

const value = url
  .trim()
  .replace(/^url\((.*)\)$/, '$1')
  .trim();

This code does not account for (single or double) quoted url() contents.

If e.g. a data-URL within quotes like url("data:image... is run through this replace function, the resulting value is "data:image... - keeping the quotation mark at the beginning.

Therefore the function isDataURI(value), which checks value.startsWith("data:") will always return false. The same problem is with regular URLs, with url("http://... also failing the isURL(value) check.

This change should account for url-contents in single and double quotation marks:

const value = url
  .trim()
  .replace(/^url\((.*)\)$/, "$1")
  .trim()
  .replace(/^"?(.*)"?$/, "$1")
  .trim()
  .replace(/^'?(.*)'?$/, "$1")
  .trim();

This fixes the problem for me.

There is a second problem with the getUrlValues function:

function getUrlValues(css) {
  return Array.from(css.match(/url\(([^)]+)\)/g) || []);
}

As data-URLs can contain brackets (e.g. rgb(120,240,80), the regex will strip anything after the first closing bracket. But i guess, that this doesn't matter, since it's only used for the isDataURI check. I also wouldn't know how to fix this, since regex in JS doesn't allow recursive patterns.

For some reason in Nuxt 3 the whole prepareLinkStylesheets code doesn't seem to run at all, but in Nuxt 4 it does. That's why this bug suddenly has effects.

codeflorist avatar Jul 24 '25 19:07 codeflorist

@codeflorist I created a Issue.

https://github.com/nuxt/nuxt/issues/32822

It's easy to narrow down, somehow the server entry doesn't like loading asyncs afterwards ðŸĪŠ

But there's no other option, the entry can't be included in the initial build entry, it has to be loaded afterwards.

ThornWalli avatar Jul 30 '25 15:07 ThornWalli

@codeflorist I created a Issue.

nuxt/nuxt#32822

It's easy to narrow down, somehow the server entry doesn't like loading asyncs afterwards ðŸĪŠ

But there's no other option, the entry can't be included in the initial build entry, it has to be loaded afterwards.

@ThornWalli

But i've found the cause (at least of my issue) right in the Nuxt Booster package. See my comment above. This is clearly a bug.

codeflorist avatar Jul 30 '25 18:07 codeflorist

Hello @codeflorist , if you like, you are welcome to create a PR 😉

How about solving it as in the example? Then we don't necessarily need regex calls.

const value = url
  .trim()
  .replace(/^url\((.*)\)$/, '$1')
  .trim();
// remove ticks
if (value.startsWith("'") || value.startsWith('"')) {
  return value.slice(1, -1);
}
if (isURL(value) || isDataURI(value)) {
  return null;
}

ThornWalli avatar Jul 31 '25 17:07 ThornWalli

Hello @codeflorist , if you like, you are welcome to create a PR 😉

How about solving it as in the example? Then we don't necessarily need regex calls.

const value = url .trim() .replace(/^url((.*))$/, '$1') .trim(); // remove ticks if (value.startsWith("'") || value.startsWith('"')) { return value.slice(1, -1); } if (isURL(value) || isDataURI(value)) { return null; }

Sure, i'd be glad to! I've opened a pull request here: https://github.com/basics/nuxt-booster/pull/1329

I've modified your suggestion a bit, so it does not return the value.slice(1, -1), but re-define value with it. Otherwise, it will never run into the isURL and isDataURI check, and i assume it will also stop handling of url('/relative/links...').

codeflorist avatar Aug 02 '25 12:08 codeflorist