diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts
index 4ddb4478f6..eabb4ee55a 100644
--- a/packages/next/server/image-optimizer.ts
+++ b/packages/next/server/image-optimizer.ts
@@ -69,22 +69,20 @@ export async function imageOptimizer(
}
const { headers } = req
- const { url: decodedUrl, w, q } = parsedUrl.query
+ const { url, w, q } = parsedUrl.query
const mimeType = getSupportedMimeType(MODERN_TYPES, headers.accept)
let href: string
- if (!decodedUrl) {
+ if (!url) {
res.statusCode = 400
res.end('"url" parameter is required')
return { finished: true }
- } else if (Array.isArray(decodedUrl)) {
+ } else if (Array.isArray(url)) {
res.statusCode = 400
res.end('"url" parameter cannot be an array')
return { finished: true }
}
- const url = encodeURI(decodedUrl)
-
let isAbsolute: boolean
if (url.startsWith('/')) {
diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts
index d62b6ded0d..f3d90464fb 100644
--- a/packages/next/server/next-server.ts
+++ b/packages/next/server/next-server.ts
@@ -1158,7 +1158,14 @@ export default class Server {
pathParts.splice(0, basePathParts.length)
}
- const path = `/${pathParts.join('/')}`
+ let path = `/${pathParts.join('/')}`
+
+ if (!publicFiles.has(path)) {
+ // In `next-dev-server.ts`, we ensure encoded paths match
+ // decoded paths on the filesystem. So we need do the
+ // opposite here: make sure decoded paths match encoded.
+ path = encodeURI(path)
+ }
if (publicFiles.has(path)) {
await this.serveStatic(
diff --git a/test/integration/image-component/unicode/next.config.js b/test/integration/image-component/unicode/next.config.js
new file mode 100644
index 0000000000..eb554b46c1
--- /dev/null
+++ b/test/integration/image-component/unicode/next.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ images: {
+ domains: ['image-optimization-test.vercel.app'],
+ },
+}
diff --git a/test/integration/image-component/unicode/pages/index.js b/test/integration/image-component/unicode/pages/index.js
new file mode 100644
index 0000000000..bdc4ea840f
--- /dev/null
+++ b/test/integration/image-component/unicode/pages/index.js
@@ -0,0 +1,33 @@
+import React from 'react'
+import Image from 'next/image'
+import img from '../public/äöü.png'
+
+const Page = () => {
+ return (
+
+
Unicode Image URL
+
+
+
+
+
+
+ )
+}
+
+export default Page
diff --git a/test/integration/image-component/unicode/public/hello world.jpg b/test/integration/image-component/unicode/public/hello world.jpg
new file mode 100644
index 0000000000..e14fafc5cf
Binary files /dev/null and b/test/integration/image-component/unicode/public/hello world.jpg differ
diff --git a/test/integration/image-component/unicode/public/äöü.png b/test/integration/image-component/unicode/public/äöü.png
new file mode 100644
index 0000000000..e14fafc5cf
Binary files /dev/null and b/test/integration/image-component/unicode/public/äöü.png differ
diff --git a/test/integration/image-component/unicode/test/index.test.js b/test/integration/image-component/unicode/test/index.test.js
new file mode 100644
index 0000000000..53e224848d
--- /dev/null
+++ b/test/integration/image-component/unicode/test/index.test.js
@@ -0,0 +1,96 @@
+/* eslint-env jest */
+
+import {
+ findPort,
+ killApp,
+ launchApp,
+ nextBuild,
+ nextStart,
+} from 'next-test-utils'
+import webdriver from 'next-webdriver'
+//import fetch from 'node-fetch'
+import { join } from 'path'
+
+jest.setTimeout(1000 * 60)
+
+const appDir = join(__dirname, '../')
+
+let appPort
+let app
+let browser
+
+function runTests() {
+ it('should load static unicode image', async () => {
+ const src = await browser.elementById('static').getAttribute('src')
+ expect(src).toMatch(
+ /_next%2Fstatic%2Fimage%2Fpublic%2F%C3%A4%C3%B6%C3%BC(.+)png/
+ )
+ const res = await fetch(src)
+ expect(res.status).toBe(200)
+ })
+
+ it('should load internal unicode image', async () => {
+ const src = await browser.elementById('internal').getAttribute('src')
+ expect(src).toMatch('/_next/image?url=%2F%C3%A4%C3%B6%C3%BC.png')
+ const res = await fetch(src)
+ expect(res.status).toBe(200)
+ })
+
+ it('should load external unicode image', async () => {
+ const src = await browser.elementById('external').getAttribute('src')
+ expect(src).toMatch(
+ '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2F%C3%A4%C3%B6%C3%BC.png'
+ )
+ const res = await fetch(src)
+ expect(res.status).toBe(200)
+ })
+
+ it('should load internal image with space', async () => {
+ const src = await browser.elementById('internal-space').getAttribute('src')
+ expect(src).toMatch('/_next/image?url=%2Fhello%2520world.jpg')
+ const res = await fetch(src)
+ expect(res.status).toBe(200)
+ })
+
+ it('should load external image with space', async () => {
+ const src = await browser.elementById('external-space').getAttribute('src')
+ expect(src).toMatch(
+ '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2Fhello%2520world.jpg'
+ )
+ const res = await fetch(src)
+ expect(res.status).toBe(200)
+ })
+}
+
+describe('Image Component Unicode Image URL', () => {
+ describe('dev mode', () => {
+ beforeAll(async () => {
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ browser = await webdriver(appPort, '/')
+ })
+ afterAll(() => {
+ killApp(app)
+ if (browser) {
+ browser.close()
+ }
+ })
+ runTests()
+ })
+
+ describe('server mode', () => {
+ beforeAll(async () => {
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ browser = await webdriver(appPort, '/')
+ })
+ afterAll(() => {
+ killApp(app)
+ if (browser) {
+ browser.close()
+ }
+ })
+ runTests()
+ })
+})