diff --git a/.changeset/angry-apricots-invite.md b/.changeset/angry-apricots-invite.md
new file mode 100644
index 0000000000..18db7ab252
--- /dev/null
+++ b/.changeset/angry-apricots-invite.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes HMR of CSS that is imported from astro, when using the static build flag
diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts
index 421e2dcce7..e7299d20a0 100644
--- a/packages/astro/src/core/ssr/index.ts
+++ b/packages/astro/src/core/ssr/index.ts
@@ -229,6 +229,18 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
 		  )
 		: new Set<SSRElement>();
 
+	// Inject HMR scripts
+	if (mode === 'development' && astroConfig.buildOptions.experimentalStaticBuild) {
+		scripts.add({
+			props: { type: 'module', src: '/@vite/client' },
+			children: '',
+		});
+		scripts.add({
+			props: { type: 'module', src: new URL('../../runtime/client/hmr.js', import.meta.url).pathname },
+			children: '',
+		});
+	}
+
 	const result = createResult({ astroConfig, logging, origin, params, pathname, renderers, scripts });
 	// Resolves specifiers in the inline hydrated scripts, such as "@astrojs/renderer-preact/client.js"
 	result.resolve = async (s: string) => {
@@ -249,7 +261,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
 	const tags: vite.HtmlTagDescriptor[] = [];
 
 	// dev only: inject Astro HMR client
-	if (mode === 'development') {
+	if (mode === 'development' && !astroConfig.buildOptions.experimentalStaticBuild) {
 		tags.push({
 			tag: 'script',
 			attrs: { type: 'module' },
diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts
index 4c6ceccd7a..d99c96f4a6 100644
--- a/packages/astro/src/vite-plugin-astro/compile.ts
+++ b/packages/astro/src/vite-plugin-astro/compile.ts
@@ -50,6 +50,13 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
 		experimentalStaticExtraction: config.buildOptions.experimentalStaticBuild,
 		// TODO add experimental flag here
 		preprocessStyle: async (value: string, attrs: Record<string, string>) => {
+			// When using this flag CSS is added via <link> and therefore goes
+			// through Vite's CSS pipeline. We don't need to transform here, it will be
+			// transformed on CSS requests.
+			if (config.buildOptions.experimentalStaticBuild) {
+				return { code: value };
+			}
+
 			const lang = `.${attrs?.lang || 'css'}`.toLowerCase();
 			try {
 				const result = await transformWithVite({
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 418c7925a6..a252a8e309 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -3,6 +3,7 @@ import type { AstroConfig } from '../@types/astro';
 import type { LogOptions } from '../core/logger';
 
 import esbuild from 'esbuild';
+import npath from 'path';
 import { fileURLToPath } from 'url';
 import { AstroDevServer } from '../core/dev/index.js';
 import { getViteTransform, TransformHook } from './styles.js';
@@ -29,6 +30,11 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
 	}
 
 	let viteTransform: TransformHook;
+
+	// Variables for determing if an id starts with /src...
+	const srcRootWeb = config.src.pathname.slice(config.projectRoot.pathname.length - 1);
+	const isBrowserPath = (path: string) => path.startsWith(srcRootWeb);
+
 	return {
 		name: '@astrojs/vite-plugin-astro',
 		enforce: 'pre', // run transforms before other plugins can
@@ -38,7 +44,16 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
 		// note: don’t claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.globEager, etc.)
 		async resolveId(id) {
 			// serve sub-part requests (*?astro) as virtual modules
-			if (parseAstroRequest(id).query.astro) {
+			const { query } = parseAstroRequest(id);
+			if (query.astro) {
+				// Convert /src/pages/index.astro?astro&type=style to /Users/name/
+				// Because this needs to be the id for the Vite CSS plugin to property resolve
+				// relative @imports.
+				if (query.type === 'style' && isBrowserPath(id)) {
+					const outId = npath.posix.join(config.projectRoot.pathname, id);
+					return outId;
+				}
+
 				return id;
 			}
 		},
diff --git a/packages/astro/vendor/vite/dist/client/client.mjs b/packages/astro/vendor/vite/dist/client/client.mjs
index 8a10e7ee1d..53609982f9 100755
--- a/packages/astro/vendor/vite/dist/client/client.mjs
+++ b/packages/astro/vendor/vite/dist/client/client.mjs
@@ -199,6 +199,16 @@ function warnFailedFetch(err, path) {
 socket.addEventListener('message', async ({ data }) => {
     handleMessage(JSON.parse(data));
 });
+
+/**
+ * This cleans up the query params and removes the `direct` param which is internal.
+ *  Other query params are preserved.
+ */
+function cleanUrl(pathname) {
+  let url = new URL(pathname, location);
+  url.searchParams.delete('direct');
+  return url.pathname + url.search;
+}
 let isFirstUpdate = true;
 async function handleMessage(payload) {
     switch (payload.type) {
@@ -230,11 +240,13 @@ async function handleMessage(payload) {
                     // css-update
                     // this is only sent when a css file referenced with <link> is updated
                     let { path, timestamp } = update;
-                    path = path.replace(/\?.*/, '');
+                    let searchUrl = cleanUrl(path);
                     // can't use querySelector with `[href*=]` here since the link may be
                     // using relative paths so we need to use link.href to grab the full
                     // URL for the include check.
-                    const el = [].slice.call(document.querySelectorAll(`link`)).find((e) => e.href.includes(path));
+                    const el = [].slice.call(document.querySelectorAll(`link`)).find((e) => {
+                      return cleanUrl(e.href).includes(searchUrl)
+                    });
                     if (el) {
                         const newPath = `${base}${path.slice(1)}${path.includes('?') ? '&' : '?'}t=${timestamp}`;
                         el.href = new URL(newPath, el.href).href;