Skip to content

Conversation

@hi-ogawa
Copy link
Contributor

@hi-ogawa hi-ogawa commented Sep 14, 2025

Description

Currently Vite reuses server rendered <style data-vite-dev-id="..."> when the same css is imported on client. This PR adds the same logic for <link data-vite-dev-id="...">.

I haven't thought through the whole scenario, but this seems reasonable to have since Vite handles link stylesheet with special behavior such as:

  • for dev, <link rel="stylesheet" /> supports HMR by rewriting href="...?t=..."

// css-update
// this is only sent when a css file referenced with <link> is updated

  • for build, client preload checks existing links and skips re-inserting

// check if the file is already preloaded by SSR markup

additional context

As far as I know, only Astro makes use of <style data-vite-dev-id="..."> for SSR to avoids duplicate css when the module on client imports the same css. Frameworks not using this trick but still injecting css from SSR to avoid FOUC have duplicate CSS on browser momentarily and then remove the server rendered <style> on hydration (Sveltekit, Nuxt, React router work like this). This behavior seems mostly harmless, but there's one issue about potential font glitch (cf. unjs/fontaine#665).

I'm trying to see if the same idea can be implemented for <link data-vite-dev-id="...">. The advantage of link over style is following:

  • to generate <style> during SSR, it requires ssrLoadModule("...css?inline") to get the css content whereas <link> requires only module url.
  • rendering entire inline css <style> can SSR output.
  • on production, style is <link> by default (unless framework has some logic to inline them).

Btw, I'm currently using custom plugin to transform @vite/client to have this patch in hi-ogawa/vite-plugins#1168.


One more thought: it would be simpler if updateStyle/removeStyle handles href url directly instead of extra data-vite-dev-id. I haven't tried this yet though.

@hi-ogawa hi-ogawa changed the title fix(css): avoid duplicate style for server rendered stylesheet link fix(css): avoid duplicate style for server rendered stylesheet link and client inline style Sep 14, 2025
@hi-ogawa hi-ogawa changed the title fix(css): avoid duplicate style for server rendered stylesheet link and client inline style fix(css): avoid duplicate style for server rendered stylesheet link and client inline style during dev Sep 14, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 17, 2025

Open in StackBlitz

npm i https://pkg.pr.new/vite@20767

commit: 07abca2

@hi-ogawa hi-ogawa marked this pull request as ready for review October 2, 2025 08:24
@hi-ogawa hi-ogawa requested review from bluwy and sapphi-red October 3, 2025 05:16
@sapphi-red
Copy link
Member

This means there'll be two requests for each CSS imports (one for <link> and one for the JS script that injects <style> when <link> doesn't exist). I wonder if that affects performance when the application is large.

@hi-ogawa
Copy link
Contributor Author

hi-ogawa commented Oct 6, 2025

Right, obviously it's not perfect either, but I think it's worth the try. I don't think this change itself has downside.
For ?assets import, we can mostly switch internal implementation and probably experiment with the difference and see trade off too.

Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The life cycle feels strange to me, but I think that shouldn't be a blocker for this PR.
If <link> works better than <style>, we can migrate to it, rewriting import './foo.css' to client.injectStyle('./foo.css') which will inject a link tag.

@hi-ogawa
Copy link
Contributor Author

hi-ogawa commented Oct 8, 2025

The life cycle feels strange to me, but I think that shouldn't be a blocker for this PR. If <link> works better than <style>, we can migrate to it, rewriting import './foo.css' to client.injectStyle('./foo.css') which will inject a link tag.

Will think about it. There's something tricky with css module though (and likely other build-time css-in-js like solution which generates css). Js needs to know class name mapping, so import styles from "./foo.module.css" needs to be a separate request.

<link> centric approach is mostly verified to work on @vitejs/plugin-rsc including css module, hmr, etc. So, I'm somewhat optimistic.

@hi-ogawa hi-ogawa merged commit 3a92bc7 into vitejs:main Oct 8, 2025
16 checks passed
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Oct 15, 2025
| datasource | package | from  | to     |
| ---------- | ------- | ----- | ------ |
| npm        | vite    | 7.1.9 | 7.1.10 |


## [v7.1.10](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-7110-2025-10-14-small)

##### Bug Fixes

- **css:** avoid duplicate style for server rendered stylesheet link and client inline style during dev ([#20767](vitejs/vite#20767)) ([3a92bc7](vitejs/vite@3a92bc7))
- **css:** respect emitAssets when cssCodeSplit=false ([#20883](vitejs/vite#20883)) ([d3e7eee](vitejs/vite@d3e7eee))
- **deps:** update all non-major dependencies ([879de86](vitejs/vite@879de86))
- **deps:** update all non-major dependencies ([#20894](vitejs/vite#20894)) ([3213f90](vitejs/vite@3213f90))
- **dev:** allow aliases starting with `//` ([#20760](vitejs/vite#20760)) ([b95fa2a](vitejs/vite@b95fa2a))
- **dev:** remove timestamp query consistently ([#20887](vitejs/vite#20887)) ([6537d15](vitejs/vite@6537d15))
- **esbuild:** inject esbuild helpers correctly for esbuild 0.25.9+ ([#20906](vitejs/vite#20906)) ([446eb38](vitejs/vite@446eb38))
- normalize path before calling `fileToBuiltUrl` ([#20898](vitejs/vite#20898)) ([73b6d24](vitejs/vite@73b6d24))
- preserve original sourcemap file field when combining sourcemaps ([#20926](vitejs/vite#20926)) ([c714776](vitejs/vite@c714776))

##### Documentation

- correct `WebSocket` spelling ([#20890](vitejs/vite#20890)) ([29e98dc](vitejs/vite@29e98dc))

##### Miscellaneous Chores

- **deps:** update rolldown-related dependencies ([#20923](vitejs/vite#20923)) ([a5e3b06](vitejs/vite@a5e3b06))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants