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

Component styles are not inlined to the page #14653

Closed
pi0 opened this issue Aug 18, 2022 · 13 comments · Fixed by nuxt/framework#7160
Closed

Component styles are not inlined to the page #14653

pi0 opened this issue Aug 18, 2022 · 13 comments · Fixed by nuxt/framework#7160

Comments

@pi0
Copy link
Member

pi0 commented Aug 18, 2022

Context: With Nuxt 2, bundled components in the server had matching styles that could make it possible to inline <style> tags to the HTML response of the server for faster content paint.

In Nuxt 3 and using vite, the server bundle has no information about styles but only matching chunk ids. Therefore best we could do is to add <link rel="preload" as="style"... to trigger browser prefetching styles but this is not best for performance because the network request is cascaded:

image

With Nuxt 2, with (opt-in) css-extraction, we could enable HTTP 2/Push as an alternative but well.. It is dead

Another alternative is that we could somehow integrate bundle-renderer (or use a nitro render:html hook) to fetch styles (off the client public/ assets) and inline them on the fly.

Another limitation is with Component Islands (nuxt/framework#5689) that are essentially server-side components. In order to allow components having style, we have to use a hack to use a plugin (like this) that extract styles/imports and build them to the client bundle and use prefetch links.

Ideal solution, is to make vite server build containing styles in the bundle and able to directly render them.


Remarks: Webpack 5 builder with Nuxt 3 seems actually has this information in server bundle (but we don't use it)

image

@danielroe danielroe changed the title Component styles aren not inlined to the page Component styles are not inlined to the page Aug 18, 2022
@danielroe
Copy link
Member

It would be nice to have the ability to do this as part of the renderer, although it might make sense for this to be part of @vitejs/plugin-vue rather than built into nuxt itself. We might be able to approximate the effect of doing this upstream in vite but I worry we would miss edge cases that can only really be handled by the vue plugin.

As for webpack, linking nuxt/framework#4388 for (some of) the (webpack) background. FWIW, webpack recommends extracting CSS rather than inlining it in any case as a more performant choice.

For production builds it's recommended to extract the CSS from your bundle being able to use parallel loading of CSS/JS resources later on.

https://webpack.js.org/loaders/css-loader/#extract

However, my reading of that is that it is in response to a binary choice (either inlined or separate assets), and the concept of this issue is (I believe) effectively a hybrid approach (CSS assets for subsequent client-side navigation) on top of inlined styles on initial load.

Also linking here https://github.com/nuxt-community/critters-module and some of the css loading strategies that critters offers (though note significant reservations with it in its current form - it needs to be considerably faster for anything but pre-rendering).

@pi0
Copy link
Member Author

pi0 commented Aug 18, 2022

although it might make sense for this to be part of @vitejs/plugin-vue rather than built into nuxt itself.

Indeed if there is an issue is there shall be fixed upstream (did I mention otherwise?)

As for webpack, linking nuxt/framework#4388 for (some of) the (webpack) background. FWIW, webpack recommends extracting CSS rather than inlining it in any case as a more performant choice.

It is obviously not always the performant choice with style extraction if you read cascading issue above. Indeed it might be sometimes better with a hybrid approach or give an option to use either but by default, inlining styles always have proven to be better as default. Also, Next.js does this by default with inlining.

Also linking here https://github.com/nuxt-community/critters-module and some of the CSS loading strategies that critters offers (though note significant reservations with it in its current form - it needs to be considerably faster for anything but pre-rendering).

As mentioned in the alternative method we can fetch styles probably with a much lighter approach to fetch or use fs (in fact I tried it before for nuxt 3 bundle renderer) that can be embedded in the production server but it has a performance downside of fetching and also i still consider it a hack because we are partially depending on client-build + it is not fixing the main issue with islands to be pure server.

@pi0
Copy link
Member Author

pi0 commented Aug 19, 2022

Some really interesting finds! It seems the vite vue plugin not only supports style queries but they are already being requested when building the server for styles.

This means currently we are adding a build-time penalty to compile them while not adding them to the final (server) bundle. But also the good news is that the vue plugin is almost supported if we could inject those queries.

https://stackblitz.com/edit/github-krrf7r

Build output (plugin logs):

/Users/pooya/tmp/nuxt3-app/components/FooBar.vue?vue&type=style&index=0&scoped=2b212ec5&lang.css {"ssr":true}                                      
.foobar[data-v-2b212ec5]{color:red}   

Build output: (.nuxt/dist/server/server.mjs - no styles)

/cc @antfu to be in context. Please share if you have any ideas or suggestions.

@pi0
Copy link
Member Author

pi0 commented Aug 19, 2022

Late-night POC:

image

https://stackblitz.com/edit/github-krrf7r-trxckt?file=components%2FFooBar.vue,app.vue,nuxt.config.ts

We probably could add style addition bits to the vite upstream with a special option/matcher enabled for inlining behavior.

Note: We might find a way to remove bundle-renderer items for CSS chunks in this mode. They are added because we have an import statement still.

@516310460
Copy link
Contributor

Is it possible to borrow Astro mode, non-interactive and interactive

@516310460
Copy link
Contributor

@pi0
Copy link
Member Author

pi0 commented Aug 19, 2022

Hi dear @516310460. This issue is not dedicated to the component islands feature. Initial support of component islands in Nuxt (#5689) is static server (with props-based interactivity). In the future we plan to introduce more variants of (client) interactive islands.

@516310460
Copy link
Contributor

516310460 commented Aug 19, 2022

Hi dear @516310460. This issue is not dedicated to the component islands feature. Initial support of component islands in Nuxt (#5689) is static server (with props-based interactivity). In the future we plan to introduce more variants of (client) interactive islands.

Is nice

For example:

// client:load or weight......
<ClientOnly client:load>

</ClientOnly>

@mirumirumi
Copy link

@pi0 @danielroe
Thanks as always for the great Nuxt!
I have a question after reading this thread.

I understand the following: both of the two options have their pros and cons, but you all have decided to go for inlining. It is also good to see that Next.js follows the same style.

My question is about the browser cache.

Browsers distinguish what to cache by network location (URL) and if the same thing is requested, they use the cached resource.

If I am not mistaken, inlining within HTML will not allow this benefit (HTML should not be cached by many developers to prepare for content changes).

So, I am guessing that this is one of the following.

  • Inlined, but in fact the browser cache is working.
  • Even without browser caching, inlining is more beneficial overall

Am I right? I would be happy to receive your opinion.

Copy link
Member

In this implementation, we inline the styles required for the first render, but subsequent navigations access CSS in separated .css files that can be cached.

@mirumirumi
Copy link

Thanks!
I got it :)

@danielroe danielroe added the 3.x label Jan 19, 2023
@danielroe danielroe transferred this issue from nuxt/framework Jan 19, 2023
@AndrewBogdanovTSS
Copy link

Correct me if I'm wrong but from what I remember caching of the asset is dependant on the size of the asset so if you have a .css file of 0.3kb it will never be cached which is defeats the purpose and for projects that use functional css this is a common situation

@antonyfrancis
Copy link

antonyfrancis commented Mar 17, 2024

@danielroe

In this implementation, we inline the styles required for the first render, but subsequent navigations access CSS in separated .css files that can be cached.

This makes sense but the separated css files are included in the <head> and impacting the LCP because they are render blocking.

Is there an option to add these css files using js on the client side or any other alternatives?

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

Successfully merging a pull request may close this issue.

6 participants