@@ -322,7 +444,7 @@ describe('using a single matcher with i18n and basePath', () => {
props: { message: \`(\${locale}) Hello from /\` }
})
`,
- 'pages/[...route].js': `
+ 'pages/[...route].js': `
export default function Page({ message }) {
return
catchall page
@@ -333,7 +455,7 @@ describe('using a single matcher with i18n and basePath', () => {
props: { message: \`(\${locale}) Hello from /\` + params.route.join("/") }
})
`,
- 'middleware.js': `
+ 'middleware.js': `
import { NextResponse } from 'next/server'
export const config = { matcher: '/' };
export default (req) => {
@@ -342,8 +464,9 @@ describe('using a single matcher with i18n and basePath', () => {
return res;
}
`,
- 'next.config.js': `
+ 'next.config.js': `
module.exports = {
+ ${trailingSlash ? 'trailingSlash: true,' : ''}
basePath: '/root',
i18n: {
localeDetection: false,
@@ -352,47 +475,57 @@ describe('using a single matcher with i18n and basePath', () => {
}
}
`,
- },
- dependencies: {},
+ },
+ dependencies: {},
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it(`adds the header for a matched path`, async () => {
+ const res1 = await fetchViaHTTP(next.url, `/root`)
+ expect(await res1.text()).toContain(`(en) Hello from /`)
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(next.url, `/root/es`)
+ expect(await res2.text()).toContain(`(es) Hello from /`)
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- })
- afterAll(() => next.destroy())
-
- it(`adds the header for a matched path`, async () => {
- const res1 = await fetchViaHTTP(next.url, `/root`)
- expect(await res1.text()).toContain(`(en) Hello from /`)
- expect(res1.headers.get('X-From-Middleware')).toBe('true')
- const res2 = await fetchViaHTTP(next.url, `/root/es`)
- expect(await res2.text()).toContain(`(es) Hello from /`)
- expect(res2.headers.get('X-From-Middleware')).toBe('true')
- })
- it(`adds the headers for a matched data path`, async () => {
- const res1 = await fetchViaHTTP(
- next.url,
- `/root/_next/data/${next.buildId}/en.json`,
- undefined,
- { headers: { 'x-nextjs-data': '1' } }
- )
- expect(await res1.json()).toMatchObject({
- pageProps: { message: `(en) Hello from /` },
+ it('adds the header for a mathed root path with /index', async () => {
+ const res1 = await fetchViaHTTP(next.url, `/root/index`)
+ expect(await res1.text()).toContain(`(en) Hello from /`)
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(next.url, `/root/es/index`)
+ expect(await res2.text()).toContain(`(es) Hello from /`)
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- expect(res1.headers.get('X-From-Middleware')).toBe('true')
- const res2 = await fetchViaHTTP(
- next.url,
- `/root/_next/data/${next.buildId}/es.json`,
- undefined,
- { headers: { 'x-nextjs-data': '1' } }
- )
- expect(await res2.json()).toMatchObject({
- pageProps: { message: `(es) Hello from /` },
+
+ it(`adds the headers for a matched data path`, async () => {
+ const res1 = await fetchViaHTTP(
+ next.url,
+ `/root/_next/data/${next.buildId}/en.json`,
+ undefined,
+ { headers: { 'x-nextjs-data': '1' } }
+ )
+ expect(await res1.json()).toMatchObject({
+ pageProps: { message: `(en) Hello from /` },
+ })
+ expect(res1.headers.get('X-From-Middleware')).toBe('true')
+ const res2 = await fetchViaHTTP(
+ next.url,
+ `/root/_next/data/${next.buildId}/es.json`,
+ undefined,
+ { headers: { 'x-nextjs-data': '1' } }
+ )
+ expect(await res2.json()).toMatchObject({
+ pageProps: { message: `(es) Hello from /` },
+ })
+ expect(res2.headers.get('X-From-Middleware')).toBe('true')
})
- expect(res2.headers.get('X-From-Middleware')).toBe('true')
- })
- it(`does not add the header for an unmatched path`, async () => {
- const response = await fetchViaHTTP(next.url, `/root/about/me`)
- expect(await response.text()).toContain('Hello from /about/me')
- expect(response.headers.get('X-From-Middleware')).toBeNull()
- })
-})
+ it(`does not add the header for an unmatched path`, async () => {
+ const response = await fetchViaHTTP(next.url, `/root/about/me`)
+ expect(await response.text()).toContain('Hello from /about/me')
+ expect(response.headers.get('X-From-Middleware')).toBeNull()
+ })
+ }
+)
diff --git a/test/integration/custom-routes/next.config.js b/test/integration/custom-routes/next.config.js
index 3f9fc9bfcd23..1a06c9778588 100644
--- a/test/integration/custom-routes/next.config.js
+++ b/test/integration/custom-routes/next.config.js
@@ -12,6 +12,11 @@ module.exports = {
},
]
: []),
+ {
+ source: '/to-websocket',
+ destination:
+ 'http://localhost:__EXTERNAL_PORT__/_next/webpack-hmr?page=/about',
+ },
{
source: '/to-nowhere',
destination: 'http://localhost:12233',
diff --git a/test/integration/custom-routes/test/index.test.js b/test/integration/custom-routes/test/index.test.js
index 55b60e85546a..cd5fc57f5ddb 100644
--- a/test/integration/custom-routes/test/index.test.js
+++ b/test/integration/custom-routes/test/index.test.js
@@ -5,6 +5,7 @@ import url from 'url'
import stripAnsi from 'strip-ansi'
import fs from 'fs-extra'
import { join } from 'path'
+import WebSocket from 'ws'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'
import escapeRegex from 'escape-string-regexp'
@@ -39,6 +40,29 @@ let appPort
let app
const runTests = (isDev = false) => {
+ it('should successfully rewrite a WebSocket request', async () => {
+ const messages = []
+ const ws = await new Promise((resolve, reject) => {
+ let socket = new WebSocket(`ws://localhost:${appPort}/to-websocket`)
+ socket.on('message', (data) => {
+ messages.push(data.toString())
+ })
+ socket.on('open', () => resolve(socket))
+ socket.on('error', (err) => {
+ console.error(err)
+ socket.close()
+ reject()
+ })
+ })
+
+ await check(
+ () => (messages.length > 0 ? 'success' : JSON.stringify(messages)),
+ 'success'
+ )
+ ws.close()
+ expect([...externalServerHits]).toEqual(['/_next/webpack-hmr?page=/about'])
+ })
+
it('should not rewrite for _next/data route when a match is found', async () => {
const initial = await fetchViaHTTP(appPort, '/overridden/first')
expect(initial.status).toBe(200)
@@ -1809,6 +1833,11 @@ const runTests = (isDev = false) => {
},
],
afterFiles: [
+ {
+ destination: `http://localhost:${externalServerPort}/_next/webpack-hmr?page=/about`,
+ regex: normalizeRegEx('^\\/to-websocket(?:\\/)?$'),
+ source: '/to-websocket',
+ },
{
destination: 'http://localhost:12233',
regex: normalizeRegEx('^\\/to-nowhere(?:\\/)?$'),
@@ -2235,6 +2264,14 @@ describe('Custom routes', () => {
const externalHost = req.headers['host']
res.end(`hi ${nextHost} from ${externalHost}`)
})
+ const wsServer = new WebSocket.Server({ noServer: true })
+
+ externalServer.on('upgrade', (req, socket, head) => {
+ externalServerHits.add(req.url)
+ wsServer.handleUpgrade(req, socket, head, (client) => {
+ client.send('hello world')
+ })
+ })
await new Promise((resolve, reject) => {
externalServer.listen(externalServerPort, (error) => {
if (error) return reject(error)
@@ -2244,7 +2281,7 @@ describe('Custom routes', () => {
nextConfigRestoreContent = await fs.readFile(nextConfigPath, 'utf8')
await fs.writeFile(
nextConfigPath,
- nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/, externalServerPort)
+ nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/g, externalServerPort)
)
})
afterAll(async () => {
diff --git a/test/production/fatal-render-errror/app/pages/_app.js b/test/production/fatal-render-errror/app/pages/_app.js
new file mode 100644
index 000000000000..904b7838ceeb
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/_app.js
@@ -0,0 +1,15 @@
+export default function App({ Component, pageProps }) {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ if (!window.renderAttempts) {
+ window.renderAttempts = 0
+ }
+ window.renderAttempts++
+ throw new Error('error in custom _app')
+ }
+ return (
+ <>
+
from _app
+
+ >
+ )
+}
diff --git a/test/production/fatal-render-errror/app/pages/_error.js b/test/production/fatal-render-errror/app/pages/_error.js
new file mode 100644
index 000000000000..9b8ff8ee0587
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/_error.js
@@ -0,0 +1,6 @@
+export default function Error() {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ throw new Error('error in custom _app')
+ }
+ return
Error encountered!
+}
diff --git a/test/production/fatal-render-errror/app/pages/index.js b/test/production/fatal-render-errror/app/pages/index.js
new file mode 100644
index 000000000000..08263e34c35f
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/index.js
@@ -0,0 +1,3 @@
+export default function Page() {
+ return
index page
+}
diff --git a/test/production/fatal-render-errror/app/pages/with-error.js b/test/production/fatal-render-errror/app/pages/with-error.js
new file mode 100644
index 000000000000..2a240431ba18
--- /dev/null
+++ b/test/production/fatal-render-errror/app/pages/with-error.js
@@ -0,0 +1,6 @@
+export default function Error() {
+ if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
+ throw new Error('error in pages/with-error')
+ }
+ return
with-error
+}
diff --git a/test/production/fatal-render-errror/index.test.ts b/test/production/fatal-render-errror/index.test.ts
new file mode 100644
index 000000000000..1037cda593f2
--- /dev/null
+++ b/test/production/fatal-render-errror/index.test.ts
@@ -0,0 +1,55 @@
+import { createNext, FileRef } from 'e2e-utils'
+import { NextInstance } from 'test/lib/next-modes/base'
+import { check, renderViaHTTP, waitFor } from 'next-test-utils'
+import webdriver from 'next-webdriver'
+import { join } from 'path'
+
+describe('fatal-render-errror', () => {
+ let next: NextInstance
+
+ beforeAll(async () => {
+ next = await createNext({
+ files: new FileRef(join(__dirname, 'app')),
+ dependencies: {},
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it('should render page without error correctly', async () => {
+ const html = await renderViaHTTP(next.url, '/')
+ expect(html).toContain('index page')
+ expect(html).toContain('from _app')
+ })
+
+ it('should handle fatal error in _app and _error without loop on direct visit', async () => {
+ const browser = await webdriver(next.url, '/with-error')
+
+ // wait a bit to see if we are rendering multiple times unexpectedly
+ await waitFor(500)
+ expect(await browser.eval('window.renderAttempts')).toBeLessThan(10)
+
+ const html = await browser.eval('document.documentElement.innerHTML')
+ expect(html).not.toContain('from _app')
+ expect(html).toContain(
+ 'Application error: a client-side exception has occurred'
+ )
+ })
+
+ it('should handle fatal error in _app and _error without loop on client-transition', async () => {
+ const browser = await webdriver(next.url, '/')
+ await browser.eval('window.renderAttempts = 0')
+
+ await browser.eval('window.next.router.push("/with-error")')
+ await check(() => browser.eval('location.pathname'), '/with-error')
+
+ // wait a bit to see if we are rendering multiple times unexpectedly
+ await waitFor(500)
+ expect(await browser.eval('window.renderAttempts')).toBeLessThan(10)
+
+ const html = await browser.eval('document.documentElement.innerHTML')
+ expect(html).not.toContain('from _app')
+ expect(html).toContain(
+ 'Application error: a client-side exception has occurred'
+ )
+ })
+})
diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts
index a8316fc530da..17c5df1c80df 100644
--- a/test/production/required-server-files-i18n.test.ts
+++ b/test/production/required-server-files-i18n.test.ts
@@ -135,6 +135,27 @@ describe('should set-up next', () => {
if (server) await killApp(server)
})
+ it('should not apply locale redirect in minimal mode', async () => {
+ const res = await fetchViaHTTP(appPort, '/', undefined, {
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'fr',
+ },
+ })
+ expect(res.status).toBe(200)
+ expect(await res.text()).toContain('index page')
+
+ const resCookie = await fetchViaHTTP(appPort, '/', undefined, {
+ redirect: 'manual',
+ headers: {
+ 'accept-language': 'en',
+ cookie: 'NEXT_LOCALE=fr',
+ },
+ })
+ expect(resCookie.status).toBe(200)
+ expect(await resCookie.text()).toContain('index page')
+ })
+
it('should output required-server-files manifest correctly', async () => {
expect(requiredFilesManifest.version).toBe(1)
expect(Array.isArray(requiredFilesManifest.files)).toBe(true)