Astro Info
- Astro: `7.3.3`
- `@astrojs/react`: `6.0.6`
- React / React DOM: `19.2.6`
- Vite, resolved by Astro: `8.2.2`
- Node.js: `24.11.0`
- OS: Linux x64
- Browser: Chromium, tested using Playwright
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
When a React component using a CSS Module is rendered both without hydration and inside a hydrated island, editing the CSS updates the hydrated instance but leaves t he server-rendered instance with stale class names.
The stylesheet is updated successfully, but its generated class names change. The non-hydrated HTML still references the previous class names, so it loses its stylin
g. Manually refreshing the page fixes it.
This was reproduced in a minimal Astro project without custom PostCSS plugins or application-specific integrations.
Actual behavior
| Stage |
Non-hydrated card |
Hydrated card |
| Initial load |
Red background |
Red background |
| After CSS edit |
Old class name; transparent background |
New class name; blue background |
| After manual refresh |
New class name; blue background |
New class name; blue background |
The browser receives a js-update for Card.tsx, and Vite logs:
[vite] hot updated: /src/components/Card.tsx
No full page reload occurs.
Inspecting the DOM confirms that only the hydrated card receives the updated CSS Module class name. The stylesheet contains the new selector but no longer contains the selector referenced by the non-hydrated card.
Possible cause
The default CSS Module scoped-name generator incorporates stylesheet contents, so even a declaration-only edit changes the exported class names.
In the installed version, astro:hmr-reload appears to skip the server-side full reload when the stylesheet also exists in the client module graph. Client HMR updates the hydrated instance, but cannot update the non-hydrated HTML.
The presence of a stylesheet in the client graph does not necessarily mean all HTML using its exported class names is client-managed.
Workaround
Using stable CSS Module names in development fixes ordinary declaration edits. For example, in astro.config.mjs, keep production defaults and configure stable names for development:
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
export default defineConfig({
integrations: [react()],
vite: {
css: {
modules: import.meta.env.DEV
? {
generateScopedName: "[name]__[local]__[hash:base64:5]",
}
: undefined,
},
},
});
With this scoped-name pattern, both cards updated correctly without a reload in the same reproduction.
This is not a complete fix: changes to exported class mappings, such as class renames or composition changes, can still require refreshing the server-rendered HTML.
What's the expected result?
Both cards should reflect the updated CSS.
If updating the non-hydrated instance requires regenerating its HTML, a full page reload would also be an acceptable fallback.
Link to Minimal Reproducible Example
https://github.com/forberg/astro-hmr-bug
Participation
Astro Info
If this issue only occurs in one browser, which browser is a problem?
No response
Describe the Bug
When a React component using a CSS Module is rendered both without hydration and inside a hydrated island, editing the CSS updates the hydrated instance but leaves t he server-rendered instance with stale class names.
The stylesheet is updated successfully, but its generated class names change. The non-hydrated HTML still references the previous class names, so it loses its stylin
g. Manually refreshing the page fixes it.
This was reproduced in a minimal Astro project without custom PostCSS plugins or application-specific integrations.
Actual behavior
The browser receives a
js-updateforCard.tsx, and Vite logs:No full page reload occurs.
Inspecting the DOM confirms that only the hydrated card receives the updated CSS Module class name. The stylesheet contains the new selector but no longer contains the selector referenced by the non-hydrated card.
Possible cause
The default CSS Module scoped-name generator incorporates stylesheet contents, so even a declaration-only edit changes the exported class names.
In the installed version,
astro:hmr-reloadappears to skip the server-side full reload when the stylesheet also exists in the client module graph. Client HMR updates the hydrated instance, but cannot update the non-hydrated HTML.The presence of a stylesheet in the client graph does not necessarily mean all HTML using its exported class names is client-managed.
Workaround
Using stable CSS Module names in development fixes ordinary declaration edits. For example, in
astro.config.mjs, keep production defaults and configure stable names for development:With this scoped-name pattern, both cards updated correctly without a reload in the same reproduction.
This is not a complete fix: changes to exported class mappings, such as class renames or composition changes, can still require refreshing the server-rendered HTML.
What's the expected result?
Both cards should reflect the updated CSS.
If updating the non-hydrated instance requires regenerating its HTML, a full page reload would also be an acceptable fallback.
Link to Minimal Reproducible Example
https://github.com/forberg/astro-hmr-bug
Participation