diff --git a/packages/next/build/webpack/loaders/css-loader/src/utils.js b/packages/next/build/webpack/loaders/css-loader/src/utils.js
index d3e1198e5fcf..0ab83b2fd9ec 100644
--- a/packages/next/build/webpack/loaders/css-loader/src/utils.js
+++ b/packages/next/build/webpack/loaders/css-loader/src/utils.js
@@ -43,6 +43,10 @@ function normalizePath(file) {
return path.sep === '\\' ? file.replace(/\\/g, '/') : file
}
+function fixedEncodeURIComponent(str) {
+ return str.replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16)}`)
+}
+
function normalizeUrl(url, isStringValue) {
let normalizedUrl = url
@@ -51,10 +55,28 @@ function normalizeUrl(url, isStringValue) {
}
if (matchNativeWin32Path.test(url)) {
- return decodeURIComponent(normalizedUrl)
+ try {
+ normalizedUrl = decodeURIComponent(normalizedUrl)
+ } catch (error) {
+ // Ignores invalid and broken URLs and try to resolve them as is
+ }
+
+ return normalizedUrl
+ }
+
+ normalizedUrl = unescape(normalizedUrl)
+
+ if (isDataUrl(url)) {
+ return fixedEncodeURIComponent(normalizedUrl)
+ }
+
+ try {
+ normalizedUrl = decodeURI(normalizedUrl)
+ } catch (error) {
+ // Ignores invalid and broken URLs and try to resolve them as is
}
- return decodeURIComponent(unescape(normalizedUrl))
+ return normalizedUrl
}
function requestify(url, rootContext) {
diff --git a/test/integration/css-fixtures/data-url/pages/index.js b/test/integration/css-fixtures/data-url/pages/index.js
new file mode 100644
index 000000000000..2a5dc77aec1b
--- /dev/null
+++ b/test/integration/css-fixtures/data-url/pages/index.js
@@ -0,0 +1,9 @@
+import { className } from './index.module.css'
+
+export default function Home() {
+ return (
+
+ Background
+
+ )
+}
diff --git a/test/integration/css-fixtures/data-url/pages/index.module.css b/test/integration/css-fixtures/data-url/pages/index.module.css
new file mode 100644
index 000000000000..fb722e140e34
--- /dev/null
+++ b/test/integration/css-fixtures/data-url/pages/index.module.css
@@ -0,0 +1,4 @@
+.className {
+ background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20649%2087.5%22%20xmlns%3Av%3D%22https%3A%2F%2Fvecta.io%2Fnano%22%3E%3Cpath%20d%3D%22M0%200h649v87.5H0z%22%20fill%3D%22%230b3647%22%2F%3E%3Cpath%20d%3D%22M0%204.03h649v76.22H0z%22%20fill%3D%22%23104f68%22%2F%3E%3Cpath%20d%3D%22M0%200h649v8.07H0z%22%20fill%3D%22%231a6184%22%2F%3E%3C%2Fsvg%3E')
+ 0 0 no-repeat;
+}
diff --git a/test/integration/css/test/index.test.js b/test/integration/css/test/index.test.js
index 4b3345cac2df..09cef727dd03 100644
--- a/test/integration/css/test/index.test.js
+++ b/test/integration/css/test/index.test.js
@@ -1852,4 +1852,26 @@ describe('CSS Support', () => {
}
})
})
+
+ describe('Data URLs', () => {
+ const workDir = join(fixturesDir, 'data-url')
+
+ it('should compile successfully', async () => {
+ await remove(join(workDir, '.next'))
+ const { code } = await nextBuild(workDir)
+ expect(code).toBe(0)
+ })
+
+ it('should have emitted expected files', async () => {
+ const cssFolder = join(workDir, '.next/static/css')
+ const files = await readdir(cssFolder)
+ const cssFiles = files.filter((f) => /\.css$/.test(f))
+
+ expect(cssFiles.length).toBe(1)
+ const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
+ expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
+ /background:url\("data:[^"]+"\)/
+ )
+ })
+ })
})