-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(nuxt, vite): inline global and component styles in server response #7160
Conversation
β Deploy Preview for nuxt3-docs canceled.
|
also refactors flag name
let's add if really need it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π―
Inlining of the CSS via nuxt config is still broken
|
Enabling inline style has a negative impact on FCP, LCP, and Speed Index. I tried checking the overall page performance after upgrading to RC.10 in two different environments with exactly the same code base with Multiple runs on Webpagetest. The overall perf was better in the environment with RC.10 with
|
@bhaskarGyan There are a number of follow-on issues from this (most notably nuxt/nuxt#14953). Rather than commenting here, please open a new issue with an example code base where you are detecting negative performance impact, and we can take it into account as we develop this feature. |
The performance effect of inlining global CSS heavily depends on how much of it is inlined. I'm using WindiCSS and the size of generated CSS for my project is about 5Kb since my CSS describes the design system and not actual components styling. Including such an amount in the build output improves not only Lighthouse scores but the actual visual fidelity for the user, fully eliminating FOUC which is even more important than any LH metric, but if you use a different CSS approach that forces you to bundle large CSS chunk (I had cases where people included up to 5MB of global CSS) in such case not inlining it, of course, will be more beneficial since browser won't need to parse through so much styling and your page will load much faster. Long story short both cases have it's application scenarios and Nuxt3/Bridge should cover them same way it was always possible for Nuxt 2 - via the extractCSS option which should be false by default. |
There is an important difference between inlining the styles of components used in the page, and inlining all global CSS. You can track the global CSS question at nuxt/nuxt#14953. Note that if you have a 5Mb global CSS file it will block the rendering of the page just as much as if it is inlined in the page. The key thing is that the 5Mb isn't included in the JS payload and that we don't duplicate it (e.g. download it twice, once in HTML and once in CSS). That is what the linked issue above is tracking. Also I would say that May I request that future comments be placed in the appropriate open issues, or a new one be opened if necessary, so that they aren't missed by being added to a merged pull request π |
π Linked issue
partly resolves nuxt/nuxt#14653
β Type of change
π Description
This PR adds support in Vite (and Nitro) for inlining global + component styles when returning an HTML response. It has the capacity to significantly decrease CLS on initial render.
I tested two other approaches before deciding on this one:
__VITE_ASSET__
hashes and would have to handle this manually.This third approach entirely side-steps
@vitejs/plugin-vue
and emits css files separately. By doing this, we also benefit from rollup code-splitting css files (+ sharing them between chunks), as you can see if you build the test fixture in this PR and inspect the.nuxt/dist/server/styles.mjs
. Moreover, we benefit from Vite's dynamic asset paths when rendering CSS with build assets (like fonts, images, etc.)There are several ways to fine-tune the behaviour:
experimental.renderInlineStyles
to false.experimental.renderInlineStyles
that receives an id and determines whether CSS imports into that file will be inlined. To restrict inlining to vue components only, a function likeid => id.includes('.vue')
would work.ssrContext.noInlineStyles
will disable inline style behaviour. (If it's beneficial, perhaps we could also use Nitro route rules to determine whether to inline styles?)Performance
In my preliminary tests, the server directory increased by the size of the inlined CSS, as expected, but I could detect no appreciable difference in performance.
Reflections on client- vs server-first approach
This PR adopts the server-first approach, which means it currently cannot render styles that are only emitted in the client-bundle (such as for client-only components). Here are some thoughts on the difference, though I think I agree with @pi0 that server-first approach is a better option:
Server-first approach
This approach treats the server-build as the source of truth and seeks to add CSS hints or information into the server build. So, for example, each component that is rendered on the server could inject its CSS and this could end up inlined.
The benefit of this approach is that it can be quite granular; we can render just the CSS of a specific component, even if it gets inlined in a bigger CSS file on client-side. It also pairs very well with server components.
On the other hand, we might miss out on CSS for client-only components. They are technically not rendered on SSR.
Client-first approach
This approach treats the client manifest as source of truth. We can process the manifest (produced from the client build) to produce imports the server can consume to inline CSS.
The benefit here is that we can ensure the CSS exactly matches between server-inlined CSS and client-loaded.
On the other hand, it's less granular. Vite may generate larger CSS files that contain styles for components that are never used in rendering. Moreover, it won't include server-component CSS when rendering, which was one of the main points of this PR.
π Checklist