Skip to content

Commit e1eea2e

Browse files
committed
fix(next): resolve HMR flashing by using stable cache files
Replace the `data:text/css;base64` injection with a file-system-based caching mechanism. By generating physical CSS files in `node_modules/.rawstyle/<hash>.css`, the CSS import paths remain stable during development, which significantly improves Turbopack HMR performance without flashing.
1 parent 217dd89 commit e1eea2e

2 files changed

Lines changed: 16 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default {
5959
turbopack: { rules: { ...rawstyleTurboRule } },
6060
} satisfies NextConfig
6161
```
62-
> The loader extracts CSS and injects it as a base64 CSS import.
62+
> The loader extracts CSS into locally cached files and injects them as relative imports.
6363
6464
#### Vite
6565

packages/next/src/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { mkdirSync, writeFileSync } from 'node:fs'
2+
import { join, resolve, relative, dirname } from 'node:path'
3+
import { createHash } from 'node:crypto'
14
import { TRANSFORMABLE_EXT } from 'rawstyle'
25
import { transform } from 'rawstyle/transformer'
36
import type { NextConfig } from 'next'
@@ -12,6 +15,16 @@ export const rawstyleTurboRule: Required<Required<NextConfig>['turbopack']>['rul
1215
export default function (this: { resourcePath: string }, source: string): string {
1316
const { transformed, css } = transform(this.resourcePath, source)
1417
if (!css) return transformed
15-
const base64Css = Buffer.from(css, 'utf8').toString('base64')
16-
return transformed.replace(/^\w/m, `import 'data:text/css;base64,${base64Css}';$&`)
18+
19+
const fileHash = createHash('md5').update(this.resourcePath).digest('hex')
20+
const cacheDir = resolve('node_modules', '.rawstyle')
21+
const cssFilePath = join(cacheDir, `${fileHash}.css`)
22+
23+
mkdirSync(cacheDir, { recursive: true })
24+
writeFileSync(cssFilePath, css, 'utf8')
25+
26+
let importPath = relative(dirname(this.resourcePath), cssFilePath).replace(/\\/g, '/')
27+
if (!importPath.startsWith('.')) importPath = `./${importPath}`
28+
29+
return transformed.replace(/^\w/m, `import '${importPath}';\n$&`)
1730
}

0 commit comments

Comments
 (0)