Skip to content

fix(dev): refresh stale CSS for component <style> block HMR - #17383

Merged
matthewp merged 19 commits into
withastro:mainfrom
thelazylamaGit:astro-css-fix
Jul 28, 2026
Merged

matthewp merged 19 commits into
withastro:mainfrom
thelazylamaGit:astro-css-fix

Conversation

@thelazylamaGit

@thelazylamaGit thelazylamaGit commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Fixes stale dev CSS after editing component style blocks that are compiled into virtual CSS modules.

Astro’s dev CSS collector already treats style block requests like these as CSS:

  • *.astro?astro&type=style...
  • *.svelte?svelte&type=style...
  • *.vue?vue&type=style...

But astro:hmr-reload only recognized real CSS file paths, so these style block modules could skip the per-route virtual:astro:dev-css:* invalidation path. This caused stale SSR/dev CSS after HMR edits.

This PR classifies Astro, Svelte, and Vue style block requests as style modules, invalidates per-route dev CSS when client CSS HMR can handle the update, and preserves SSR invalidation/full reload when no client module exists to apply the update.

Also addresses #17083

Testing

  • Added unit coverage for Astro, Svelte, and Vue style block virtual modules invalidating per-route dev CSS modules.

  • Added coverage for SSR-only style block modules using SSR invalidation/full reload.

  • Added negative coverage for non-style component queries, raw CSS imports, unrelated type=style requests, and component-style-looking requests without the matching compiler marker.

Docs

  • No docs changes. This fixes dev-server HMR behaviour without changing user-facing APIs.

@changeset-bot

changeset-bot Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d365a8c

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the pkg: astro Related to the core `astro` package (scope) label Jul 14, 2026
@codspeed

codspeed Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing thelazylamaGit:astro-css-fix (d365a8c) with main (82bf7e2)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (112d3ea) during the generation of this report, so 82bf7e2 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@felmonon felmonon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Traced this against main to confirm the root cause; the fix direction looks right.

Root cause verified: on main, an Astro component style virtual module (/src/Component.astro?astro&type=style&index=0&lang.css) is never classified as a style module — hasStyleExtension fails on both mod.file (.astro) and mod.id.split('?')[0] (also .astro) — so it falls through to the generic branch. When the module also exists in the client graph, the update is skipped outright (clientModule != null → continue), meaning the hasSkippedStyleModules path never runs and the per-route dev-css virtual modules are never invalidated — so the inline anti-FOUC styles keep serving stale CSS until a dev-server restart. Classifying via parseAstroRequest is the right discriminator, and the query-order-permutation test is a good catch given the params have no guaranteed order.

The SSR-only special case is also justified as written: without it, the new classification would route SSR-only style modules into the skip path, where there is no client-side CSS HMR to actually deliver the update — restoring invalidate + full-reload for exactly that sub-case preserves main's behavior for those modules. A one-line comment on the new branch saying "no client module means nothing will apply the CSS update client-side, so force a reload" would make the asymmetry (one sub-case skips, the other reloads) obvious to the next reader.

Non-blocking nit: isAstroStyleModule parses the query twice per style module (once inside isStyleModule, once in the hotUpdate branch). Trivial cost, but hoisting the parse result would avoid it.

Unit coverage (css/scss/less, query-order permutations, ws expectations) looks solid.

@thelazylamaGit thelazylamaGit changed the title Fix dev CSS HMR for Astro component style blocks fix(dev): refresh stale CSS for component style blocks HMR Jul 19, 2026
@thelazylamaGit

thelazylamaGit commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Noticed this same issue was happening for other framework <style> blocks too so updated this to handle the broader component style block case, not only Astro <style> blocks.

The same stale dev CSS path can happen for Svelte and Vue style block modules like:

  • *.svelte?svelte&type=style...
  • *.vue?vue&type=style...

These are collected by Astro’s dev CSS graph, but were not recognised by astro:hmr-reload as style modules because the filename before the query is .svelte/.vue, not .css.

The fix now classifies Astro/Svelte/Vue style block requests explicitly, keeps the client-CSS-HMR path when a client module exists, and keeps the SSR invalidation/full-reload fallback when no client module exists. I've personally tested and verified that with this fix, component <style> block changes now update as expected in dev.

@thelazylamaGit thelazylamaGit changed the title fix(dev): refresh stale CSS for component style blocks HMR fix(dev): refresh stale CSS for component style-block HMR Jul 19, 2026
@thelazylamaGit thelazylamaGit changed the title fix(dev): refresh stale CSS for component style-block HMR fix(dev): refresh stale CSS for component <style> block HMR Jul 19, 2026
@thelazylamaGit

Copy link
Copy Markdown
Contributor Author

@matthewp would you mind giving this a review when you have a chance?

This builds on my previous PR you approved #16957 and includes some cases I missed.

It addresses a dev CSS HMR regression that’s been around since the Astro 5 to Astro 6 transition, and I think it would be valuable to get into the next release if possible.

@matthewp

Copy link
Copy Markdown
Contributor

@thelazylamaGit Yes, adding it to my review stack, thanks!

@thelazylamaGit

Copy link
Copy Markdown
Contributor Author

Noticed after some more testing that normal CSS files could be swallowed when no client style module was available, requiring a manual reload.

This was because the previous PR logic invalidated virtual:astro:dev-css:* and then always skipped SSR reload for style updates, assuming Vite client CSS HMR would apply the browser update. That works when the style exists in the client graph, but fails for cases like global.css when there is no exact client module to receive the update.

This now does one exact client lookup first, then falls back to getModulesByFile() for query-varied style ids, and only skips reload when a client style module actually exists. Otherwise it invalidates SSR and forces a reload.

@matthewp
matthewp merged commit 296e1b0 into withastro:main Jul 28, 2026
29 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Jul 28, 2026
@thelazylamaGit

Copy link
Copy Markdown
Contributor Author

Note whilst this PR fixes some genuine bugs and makes css hmr better than it was, its still not perfect and can cause some false positives due to hasClientStyleModuleByFile() being a bit too broad which means the css wont update and requires a manual reload, at which point hot-updates will start working again.

The alternative which is doing exact matches only would result in forcing full reloads a lot of the time even when it doesn't necessarily have to.

I'm going to do some more investigating into how vite 8 client modules work and do another PR that hopefully should fix this and prevent false positives whilst still allowing hot-reloads when applicable.

dadezzz pushed a commit to dadezzz/university_notes that referenced this pull request Aug 2, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@astrojs/markdown-remark](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark)) | [`7.2.1` → `7.2.2`](https://renovatebot.com/diffs/npm/@astrojs%2fmarkdown-remark/7.2.1/7.2.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@astrojs%2fmarkdown-remark/7.2.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@astrojs%2fmarkdown-remark/7.2.1/7.2.2?slim=true) |
| [astro](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/astro)) | [`7.1.4` → `7.1.6`](https://renovatebot.com/diffs/npm/astro/7.1.4/7.1.6) | ![age](https://developer.mend.io/api/mc/badges/age/npm/astro/7.1.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/astro/7.1.4/7.1.6?slim=true) |

---

### Release Notes

<details>
<summary>withastro/astro (@&#8203;astrojs/markdown-remark)</summary>

### [`v7.2.2`](https://github.com/withastro/astro/blob/HEAD/packages/markdown/remark/CHANGELOG.md#722)

[Compare Source](https://github.com/withastro/astro/compare/@astrojs/markdown-remark@7.2.1...@astrojs/markdown-remark@7.2.2)

##### Patch Changes

- Updated dependencies \[[`c895b12`](withastro/astro@c895b12)]:
  - [@&#8203;astrojs/internal-helpers](https://github.com/astrojs/internal-helpers)@&#8203;0.10.2

</details>

<details>
<summary>withastro/astro (astro)</summary>

### [`v7.1.6`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#716)

[Compare Source](https://github.com/withastro/astro/compare/astro@7.1.5...astro@7.1.6)

##### Patch Changes

- [#&#8203;17536](withastro/astro#17536) [`ff97b86`](withastro/astro@ff97b86) Thanks [@&#8203;dmgawel](https://github.com/dmgawel)! - Fixes concurrent static builds failing to generate i18n rewrite fallbacks for dynamic routes

- [#&#8203;17383](withastro/astro#17383) [`296e1b0`](withastro/astro@296e1b0) Thanks [@&#8203;thelazylamaGit](https://github.com/thelazylamaGit)! - Fixes stale dev CSS after editing component style blocks and CSS files in dev

- [#&#8203;17543](withastro/astro#17543) [`bbc1ec9`](withastro/astro@bbc1ec9) Thanks [@&#8203;ematipico](https://github.com/ematipico)! - Adds a feature to `experimental.collectionStorage` that allows to change the size of chunks.

  For example, you can reduce the size of chunks to 1MB:

  ```js
  // astro.config.mjs
  import { defineConfig } from 'astro/config';

  export default defineConfig({
    experimental: {
      collectionStorage: {
        type: 'chunked',
        chunkSize: 1024 * 1024,
      },
    },
  });
  ```

- [#&#8203;17545](withastro/astro#17545) [`5214663`](withastro/astro@5214663) Thanks [@&#8203;ematipico](https://github.com/ematipico)! - Bumps the Astro compiler to the latest version. [Changelog](https://github.com/withastro/compiler-rs/releases/tag/%40astrojs%2Fcompiler-rs%400.3.2).

### [`v7.1.5`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#715)

[Compare Source](https://github.com/withastro/astro/compare/astro@7.1.4...astro@7.1.5)

##### Patch Changes

- [#&#8203;17524](withastro/astro#17524) [`7613030`](withastro/astro@7613030) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes a bug where an error while finalizing a request could prevent a response from being sent

- [#&#8203;17480](withastro/astro#17480) [`f61ba9c`](withastro/astro@f61ba9c) Thanks [@&#8203;florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes a case where a custom `logger.entrypoint` failed to load at runtime in a built server bundle.

- [#&#8203;17525](withastro/astro#17525) [`e614b7b`](withastro/astro@e614b7b) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes action path resolution so that properties of a resolved action function are not treated as routable path segments

- [#&#8203;17284](withastro/astro#17284) [`c775c1f`](withastro/astro@c775c1f) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes a bug where the custom 404 (or 500) page was not rendered when a middleware rewrite targeted a route that returned an empty 404/500 response, and a blank page was returned instead

- [#&#8203;17474](withastro/astro#17474) [`c895b12`](withastro/astro@c895b12) Thanks [@&#8203;nicksnyder](https://github.com/nicksnyder)! - Updates dependency `js-yaml` to v4.3.0

- Updated dependencies \[[`c895b12`](withastro/astro@c895b12)]:
  - [@&#8203;astrojs/internal-helpers](https://github.com/astrojs/internal-helpers)@&#8203;0.10.2
  - [@&#8203;astrojs/markdown-remark](https://github.com/astrojs/markdown-remark)@&#8203;7.2.2
  - [@&#8203;astrojs/markdown-satteri](https://github.com/astrojs/markdown-satteri)@&#8203;0.3.5

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI4MS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: astro Related to the core `astro` package (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants