diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index 3e644c4203e04..db2d63a9ac242 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -2,13 +2,20 @@ on:
workflow_dispatch:
inputs:
releaseType:
- description: stable or canary (case sensitive)?
+ description: stable or canary?
required: true
- type: string
+ type: choice
+ options:
+ - canary
+ - stable
semverType:
- description: patch, minor, or major (case sensitive)?
- type: string
+ description: semver type?
+ type: choice
+ options:
+ - patch
+ - minor
+ - major
secrets:
RELEASE_BOT_TOKEN:
diff --git a/bench/rendering/pages/stateless-big.js b/bench/rendering/pages/stateless-big.js
index 87f340d66d7e9..fdf9f92eefb3d 100644
--- a/bench/rendering/pages/stateless-big.js
+++ b/bench/rendering/pages/stateless-big.js
@@ -5,7 +5,7 @@ export default () => {
}
const items = () => {
- var out = new Array(10000)
+ const out = new Array(10000)
for (let i = 0; i < out.length; i++) {
out[i] =
This is row {i + 1}
}
diff --git a/errors/react-hydration-error.md b/errors/react-hydration-error.md
index 486112fc43eaf..916e695b2a66f 100644
--- a/errors/react-hydration-error.md
+++ b/errors/react-hydration-error.md
@@ -82,10 +82,18 @@ Common causes with css-in-js libraries:
- When using other css-in-js libraries
- Similar to Styled Components / Emotion css-in-js libraries generally need configuration specified in their examples in the [examples directory](https://github.com/vercel/next.js/tree/canary/examples)
-Local Overrides
+Local Overrides:
It's possible you may have [Local Overrides enabled in Chrome devtools](https://developer.chrome.com/blog/new-in-devtools-65/#overrides). With this enabled, the HTML served will be different from what the SSR emitted. It also won't show up in view-source, so you may be left wondering what is going on.
+Common causes on iOS:
+
+- iOS attempts to detect phone numbers, email addressees and other data in text content and convert them into links, which can [lead to hydration mismatches](https://github.com/vercel/next.js/issues/38290). This can be disabled with the following `meta` tag:
+
+```
+
+```
+
### Useful Links
- [React Hydration Documentation](https://react.dev/reference/react-dom/client/hydrateRoot)
diff --git a/lerna.json b/lerna.json
index 99bc54f482fbe..b1c8d1d5c9225 100644
--- a/lerna.json
+++ b/lerna.json
@@ -16,5 +16,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "13.4.2-canary.2"
+ "version": "13.4.2-canary.3"
}
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index 08e958d992135..6dd425049a3d2 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index b3df50322978d..a5ec589165be9 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "ESLint configuration used by NextJS.",
"main": "index.js",
"license": "MIT",
@@ -9,7 +9,7 @@
"directory": "packages/eslint-config-next"
},
"dependencies": {
- "@next/eslint-plugin-next": "13.4.2-canary.2",
+ "@next/eslint-plugin-next": "13.4.2-canary.3",
"@rushstack/eslint-patch": "^1.1.3",
"@typescript-eslint/parser": "^5.42.0",
"eslint-import-resolver-node": "^0.3.6",
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index 3c63dbea26625..380b7679e1aa4 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "ESLint plugin for NextJS.",
"main": "dist/index.js",
"license": "MIT",
diff --git a/packages/font/package.json b/packages/font/package.json
index e3735a2023806..6d16529ecd502 100644
--- a/packages/font/package.json
+++ b/packages/font/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/font",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 50e254b0ffa9b..e12ba6bcf7f5c 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 1b39fab5b05ba..ba189b45f3180 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index 42485e68f1d74..c432c934399d4 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index 55d2cc453b70b..48ad191a0630d 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index 2193248ad258d..175b537b9fb07 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index af074badb13f8..875c6968691f0 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index a0c023effdf74..f5afd45abd684 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index 3703253fbec34..17e44e52e7fd5 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"private": true,
"scripts": {
"clean": "rm -rf ./native/*",
diff --git a/packages/next/package.json b/packages/next/package.json
index f079d1d4388de..772f70ff8d872 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -83,7 +83,7 @@
]
},
"dependencies": {
- "@next/env": "13.4.2-canary.2",
+ "@next/env": "13.4.2-canary.3",
"@swc/helpers": "0.5.1",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001406",
@@ -143,11 +143,11 @@
"@jest/types": "29.5.0",
"@napi-rs/cli": "2.14.7",
"@napi-rs/triples": "1.1.0",
- "@next/polyfill-module": "13.4.2-canary.2",
- "@next/polyfill-nomodule": "13.4.2-canary.2",
- "@next/react-dev-overlay": "13.4.2-canary.2",
- "@next/react-refresh-utils": "13.4.2-canary.2",
- "@next/swc": "13.4.2-canary.2",
+ "@next/polyfill-module": "13.4.2-canary.3",
+ "@next/polyfill-nomodule": "13.4.2-canary.3",
+ "@next/react-dev-overlay": "13.4.2-canary.3",
+ "@next/react-refresh-utils": "13.4.2-canary.3",
+ "@next/swc": "13.4.2-canary.3",
"@opentelemetry/api": "1.4.1",
"@segment/ajv-human-errors": "2.1.2",
"@taskr/clear": "1.1.0",
diff --git a/packages/next/src/build/webpack/loaders/next-font-loader/index.ts b/packages/next/src/build/webpack/loaders/next-font-loader/index.ts
index 7bacdbe0839a2..87221932b26b1 100644
--- a/packages/next/src/build/webpack/loaders/next-font-loader/index.ts
+++ b/packages/next/src/build/webpack/loaders/next-font-loader/index.ts
@@ -49,6 +49,15 @@ export default async function nextFontLoader(this: any) {
postcss: getPostcss,
} = this.getOptions()
+ if (assetPrefix && !/^\/|https?:\/\//.test(assetPrefix)) {
+ const err = new Error(
+ 'assetPrefix must start with a leading slash or be an absolute URL(http:// or https://)'
+ )
+ err.name = 'NextFontError'
+ callback(err)
+ return
+ }
+
/**
* Emit font files to .next/static/media as [hash].[ext].
*
diff --git a/packages/next/src/client/components/error.tsx b/packages/next/src/client/components/error.tsx
index 94f97386167c0..3b0c133603308 100644
--- a/packages/next/src/client/components/error.tsx
+++ b/packages/next/src/client/components/error.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-const styles: { [k: string]: React.CSSProperties } = {
+const styles: Record = {
error: {
// https://github.com/sindresorhus/modern-normalize/blob/main/modern-normalize.css#L38-L52
fontFamily:
diff --git a/packages/next/src/pages/_error.tsx b/packages/next/src/pages/_error.tsx
index 376fb9536ba68..ca57c82991eb3 100644
--- a/packages/next/src/pages/_error.tsx
+++ b/packages/next/src/pages/_error.tsx
@@ -24,7 +24,7 @@ function _getInitialProps({
return { statusCode }
}
-const styles: { [k: string]: React.CSSProperties } = {
+const styles: Record = {
error: {
// https://github.com/sindresorhus/modern-normalize/blob/main/modern-normalize.css#L38-L52
fontFamily:
diff --git a/packages/next/src/server/node-polyfill-crypto.test.ts b/packages/next/src/server/node-polyfill-crypto.test.ts
new file mode 100644
index 0000000000000..8272642cc2528
--- /dev/null
+++ b/packages/next/src/server/node-polyfill-crypto.test.ts
@@ -0,0 +1,11 @@
+/* eslint-env jest */
+import './node-polyfill-crypto'
+
+describe('node-polyfill-crypto', () => {
+ test('overwrite crypto', async () => {
+ expect(global.crypto).not.toBeUndefined()
+ const a = {} as Crypto
+ global.crypto = a
+ expect(global.crypto).toBe(a)
+ })
+})
diff --git a/packages/next/src/server/node-polyfill-crypto.ts b/packages/next/src/server/node-polyfill-crypto.ts
index 204f165872fee..cec97c6179556 100644
--- a/packages/next/src/server/node-polyfill-crypto.ts
+++ b/packages/next/src/server/node-polyfill-crypto.ts
@@ -1,12 +1,19 @@
// Polyfill crypto() in the Node.js environment
-if (!(global as any).crypto) {
- function getCryptoImpl() {
- return require('node:crypto').webcrypto
- }
+if (!global.crypto) {
+ let webcrypto: Crypto | undefined
+
Object.defineProperty(global, 'crypto', {
+ enumerable: false,
+ configurable: true,
get() {
- return getCryptoImpl()
+ if (!webcrypto) {
+ webcrypto = require('node:crypto').webcrypto
+ }
+ return webcrypto
+ },
+ set(value: Crypto) {
+ webcrypto = value
},
})
}
diff --git a/packages/next/src/shared/lib/router/router.ts b/packages/next/src/shared/lib/router/router.ts
index 3aa2d9e3d2c5c..a17eac086963e 100644
--- a/packages/next/src/shared/lib/router/router.ts
+++ b/packages/next/src/shared/lib/router/router.ts
@@ -221,7 +221,12 @@ function getMiddlewareData(
) {
const parsedSource = getNextPathnameInfo(
parseRelativeUrl(source).pathname,
- { parseData: true }
+ {
+ nextConfig: process.env.__NEXT_HAS_REWRITES
+ ? undefined
+ : nextConfig,
+ parseData: true,
+ }
)
as = addBasePath(parsedSource.pathname)
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index ea6e135738805..14b9fc4c75562 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index 6b1d3dc637ed4..c657875b69e0b 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "13.4.2-canary.2",
+ "version": "13.4.2-canary.3",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0b414247a57e9..ca62165554348 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -464,7 +464,7 @@ importers:
packages/eslint-config-next:
specifiers:
- '@next/eslint-plugin-next': 13.4.2-canary.2
+ '@next/eslint-plugin-next': 13.4.2-canary.3
'@rushstack/eslint-patch': ^1.1.3
'@typescript-eslint/parser': ^5.42.0
eslint: ^7.23.0 || ^8.0.0
@@ -540,12 +540,12 @@ importers:
'@jest/types': 29.5.0
'@napi-rs/cli': 2.14.7
'@napi-rs/triples': 1.1.0
- '@next/env': 13.4.2-canary.2
- '@next/polyfill-module': 13.4.2-canary.2
- '@next/polyfill-nomodule': 13.4.2-canary.2
- '@next/react-dev-overlay': 13.4.2-canary.2
- '@next/react-refresh-utils': 13.4.2-canary.2
- '@next/swc': 13.4.2-canary.2
+ '@next/env': 13.4.2-canary.3
+ '@next/polyfill-module': 13.4.2-canary.3
+ '@next/polyfill-nomodule': 13.4.2-canary.3
+ '@next/react-dev-overlay': 13.4.2-canary.3
+ '@next/react-refresh-utils': 13.4.2-canary.3
+ '@next/swc': 13.4.2-canary.3
'@opentelemetry/api': 1.4.1
'@segment/ajv-human-errors': 2.1.2
'@swc/helpers': 0.5.1
diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts
index b94b72f54ea8c..1297524713c8c 100644
--- a/test/e2e/app-dir/actions/app-action.test.ts
+++ b/test/e2e/app-dir/actions/app-action.test.ts
@@ -12,7 +12,7 @@ createNextDescribe(
{
files: __dirname,
},
- ({ next, isNextDev }) => {
+ ({ next, isNextDev, isNextStart }) => {
it('should handle basic actions correctly', async () => {
const browser = await next.browser('/server')
@@ -59,6 +59,32 @@ createNextDescribe(
}, 'same')
})
+ it('should support headers in client imported actions', async () => {
+ const logs: string[] = []
+ next.on('stdout', (log) => {
+ logs.push(log)
+ })
+ next.on('stderr', (log) => {
+ logs.push(log)
+ })
+
+ const currentTimestamp = Date.now()
+
+ const browser = await next.browser('/client')
+ await browser.elementByCss('#get-header').click()
+ await check(() => {
+ return logs.some((log) =>
+ log.includes('accept header: text/x-component')
+ )
+ ? 'yes'
+ : ''
+ }, 'yes')
+
+ expect(
+ await browser.eval('+document.cookie.match(/test-cookie=(\\d+)/)[1]')
+ ).toBeGreaterThanOrEqual(currentTimestamp)
+ })
+
it('should support setting cookies in route handlers with the correct overrides', async () => {
const res = await next.fetch('/handler')
const setCookieHeader = res.headers.get('set-cookie') as string[]
@@ -172,7 +198,7 @@ createNextDescribe(
await check(() => browser.elementByCss('h1').text(), '3')
})
- if (!isNextDev) {
+ if (isNextStart) {
it('should not expose action content in sourcemaps', async () => {
const sourcemap = (
await fs.readdir(
diff --git a/test/e2e/app-dir/actions/app/client/actions.js b/test/e2e/app-dir/actions/app/client/actions.js
index a1aa005a571d0..9039a73b18b0a 100644
--- a/test/e2e/app-dir/actions/app/client/actions.js
+++ b/test/e2e/app-dir/actions/app/client/actions.js
@@ -1,6 +1,12 @@
'use server'
import { redirect } from 'next/navigation'
+import { headers, cookies } from 'next/headers'
+
+export async function getHeaders() {
+ console.log('accept header:', headers().get('accept'))
+ cookies().set('test-cookie', Date.now())
+}
export async function inc(value) {
return value + 1
diff --git a/test/e2e/app-dir/actions/app/client/page.js b/test/e2e/app-dir/actions/app/client/page.js
index 68043e4c0cfca..bec2e5e4e23f1 100644
--- a/test/e2e/app-dir/actions/app/client/page.js
+++ b/test/e2e/app-dir/actions/app/client/page.js
@@ -2,7 +2,7 @@
import { useState } from 'react'
-import double, { inc, dec, redirectAction } from './actions'
+import double, { inc, dec, redirectAction, getHeaders } from './actions'
export default function Counter() {
const [count, setCount] = useState(0)
@@ -52,6 +52,11 @@ export default function Counter() {
redirect external
+
)
}
diff --git a/test/e2e/app-dir/app/pages/index.js b/test/e2e/app-dir/app/pages/index.js
index 5ee253ffe17b6..b1037a470b719 100644
--- a/test/e2e/app-dir/app/pages/index.js
+++ b/test/e2e/app-dir/app/pages/index.js
@@ -5,8 +5,11 @@ import styles from '../styles/shared.module.css'
export default function Page() {
return (
<>
- hello from pages/index
+
+ hello from pages/index
+
Dashboard
+ {React.version}
>
)
}
diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts
index fddb59590aab2..ff3e2014a6c25 100644
--- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts
+++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts
@@ -1,8 +1,6 @@
import path from 'path'
-import fs from 'fs-extra'
import { check } from 'next-test-utils'
-import { createNext } from 'e2e-utils'
-import { NextInstance } from 'test/lib/next-modes/base'
+import { createNextDescribe } from 'e2e-utils'
import cheerio from 'cheerio'
async function resolveStreamResponse(response: any, onData?: any) {
@@ -16,481 +14,467 @@ async function resolveStreamResponse(response: any, onData?: any) {
return result
}
-describe('app dir - rsc basics', () => {
- let next: NextInstance
- let distDir: string
+createNextDescribe(
+ 'app dir - rsc basics',
+ {
+ files: __dirname,
+ dependencies: {
+ 'styled-components': '6.0.0-beta.5',
+ 'server-only': 'latest',
+ },
+ packageJson: {
+ scripts: {
+ build: 'next build',
+ dev: 'next dev',
+ start: 'next start',
+ },
+ },
+ installCommand: 'yarn',
+ startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start',
+ buildCommand: 'yarn build',
+ },
+ ({ next, isNextDev, isNextStart }) => {
+ it('should correctly render page returning null', async () => {
+ const homeHTML = await next.render('/return-null/page')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-null-layout').html()).toBeEmpty()
+ })
- if ((global as any).isNextDeploy) {
- it('should skip for deploy mode for now', () => {})
- return
- }
+ it('should correctly render component returning null', async () => {
+ const homeHTML = await next.render('/return-null/component')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-null-layout').html()).toBeEmpty()
+ })
- beforeAll(async () => {
- next = await createNext({
- files: __dirname,
- dependencies: {
- 'styled-components': '6.0.0-beta.5',
- react: 'latest',
- 'react-dom': 'latest',
- 'server-only': 'latest',
- },
- packageJson: {
- scripts: {
- build: 'next build',
- dev: 'next dev',
- start: 'next start',
- },
- },
- installCommand: 'yarn',
- startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start',
- buildCommand: 'yarn build',
+ it('should correctly render layout returning null', async () => {
+ const homeHTML = await next.render('/return-null/layout')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-null-layout').html()).toBeEmpty()
})
- distDir = path.join(next.testDir, '.next')
- })
- afterAll(() => next.destroy())
-
- const { isNextDeploy, isNextDev } = global as any
- if (isNextDeploy) {
- it('should skip tests for next-deploy and react 17', () => {})
- return
- }
- it('should correctly render page returning null', async () => {
- const homeHTML = await next.render('/return-null/page')
- const $ = cheerio.load(homeHTML)
- expect($('#return-null-layout').html()).toBeEmpty()
- })
-
- it('should correctly render component returning null', async () => {
- const homeHTML = await next.render('/return-null/component')
- const $ = cheerio.load(homeHTML)
- expect($('#return-null-layout').html()).toBeEmpty()
- })
-
- it('should correctly render layout returning null', async () => {
- const homeHTML = await next.render('/return-null/layout')
- const $ = cheerio.load(homeHTML)
- expect($('#return-null-layout').html()).toBeEmpty()
- })
-
- it('should correctly render page returning undefined', async () => {
- const homeHTML = await next.render('/return-undefined/page')
- const $ = cheerio.load(homeHTML)
- expect($('#return-undefined-layout').html()).toBeEmpty()
- })
-
- it('should correctly render component returning undefined', async () => {
- const homeHTML = await next.render('/return-undefined/component')
- const $ = cheerio.load(homeHTML)
- expect($('#return-undefined-layout').html()).toBeEmpty()
- })
-
- it('should correctly render layout returning undefined', async () => {
- const homeHTML = await next.render('/return-undefined/layout')
- const $ = cheerio.load(homeHTML)
- expect($('#return-undefined-layout').html()).toBeEmpty()
- })
-
- it('should render server components correctly', async () => {
- const homeHTML = await next.render('/', null, {
- headers: {
- 'x-next-test-client': 'test-util',
- },
+ it('should correctly render page returning undefined', async () => {
+ const homeHTML = await next.render('/return-undefined/page')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-undefined-layout').html()).toBeEmpty()
})
- // should have only 1 DOCTYPE
- expect(homeHTML).toMatch(/^')
- expect(homeHTML).toContain(
- ''
- )
-
- expect(homeHTML).toContain('component:index.server')
- expect(homeHTML).toContain('header:test-util')
-
- const inlineFlightContents = []
- const $ = cheerio.load(homeHTML)
- $('script').each((_index, tag) => {
- const content = $(tag).text()
- if (content) inlineFlightContents.push(content)
+ it('should correctly render component returning undefined', async () => {
+ const homeHTML = await next.render('/return-undefined/component')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-undefined-layout').html()).toBeEmpty()
})
- const internalQueries = [
- '__nextFallback',
- '__nextLocale',
- '__nextDefaultLocale',
- '__nextIsNotFound',
- ]
-
- const hasNextInternalQuery = inlineFlightContents.some((content) =>
- internalQueries.some((query) => content.includes(query))
- )
- expect(hasNextInternalQuery).toBe(false)
- })
-
- it('should reuse the inline flight response without sending extra requests', async () => {
- let hasFlightRequest = false
- let requestsCount = 0
- await next.browser('/root', {
- beforePageLoad(page) {
- page.on('request', (request) => {
- requestsCount++
- return request.allHeaders().then((headers) => {
- if (
- headers['RSC'.toLowerCase()] === '1' &&
- // Prefetches also include `RSC`
- headers['Next-Router-Prefetch'.toLowerCase()] !== '1'
- ) {
- hasFlightRequest = true
- }
- })
- })
- },
+ it('should correctly render layout returning undefined', async () => {
+ const homeHTML = await next.render('/return-undefined/layout')
+ const $ = cheerio.load(homeHTML)
+ expect($('#return-undefined-layout').html()).toBeEmpty()
})
- expect(requestsCount).toBeGreaterThan(0)
- expect(hasFlightRequest).toBe(false)
- })
-
- it('should support multi-level server component imports', async () => {
- const html = await next.render('/multi')
- expect(html).toContain('bar.server.js:')
- expect(html).toContain('foo.client')
- })
-
- it('should be able to navigate between rsc routes', async () => {
- const browser = await next.browser('/root')
-
- await browser.waitForElementByCss('#goto-next-link').click()
- await new Promise((res) => setTimeout(res, 1000))
- await check(() => browser.url(), `${next.url}/next-api/link`)
- await browser.waitForElementByCss('#goto-home').click()
- await new Promise((res) => setTimeout(res, 1000))
- await check(() => browser.url(), `${next.url}/root`)
- const content = await browser.elementByCss('body').text()
- expect(content).toContain('component:root.server')
-
- await browser.waitForElementByCss('#goto-streaming-rsc').click()
-
- // Wait for navigation and streaming to finish.
- await check(
- () => browser.elementByCss('#content').text(),
- 'next_streaming_data'
- )
- expect(await browser.url()).toBe(`${next.url}/streaming-rsc`)
- })
-
- it('should handle streaming server components correctly', async () => {
- const browser = await next.browser('/streaming-rsc')
- const content = await browser.eval(
- `document.querySelector('#content').innerText`
- )
- expect(content).toMatchInlineSnapshot('"next_streaming_data"')
- })
-
- it('should support next/link in server components', async () => {
- const $ = await next.render$('/next-api/link')
- const linkText = $('body a[href="/root"]').text()
-
- expect(linkText).toContain('home')
-
- const browser = await next.browser('/next-api/link')
-
- // We need to make sure the app is fully hydrated before clicking, otherwise
- // it will be a full redirection instead of being taken over by the next
- // router. This timeout prevents it being flaky caused by fast refresh's
- // rebuilding event.
- await new Promise((res) => setTimeout(res, 1000))
- await browser.eval('window.beforeNav = 1')
-
- await browser.waitForElementByCss('#next_id').click()
- await check(() => browser.elementByCss('#query').text(), 'query:1')
-
- await browser.waitForElementByCss('#next_id').click()
- await check(() => browser.elementByCss('#query').text(), 'query:2')
-
- if (isNextDev) {
- expect(await browser.eval('window.beforeNav')).toBe(1)
- }
- })
-
- it('should link correctly with next/link without mpa navigation to the page', async () => {
- // Select the button which is not hidden but rendered
- const selector = '#goto-next-link'
- const browser = await next.browser('/root', {})
-
- await browser.eval('window.didNotReloadPage = true')
- await browser.elementByCss(selector).click().waitForElementByCss('#query')
-
- expect(await browser.eval('window.didNotReloadPage')).toBe(true)
-
- const text = await browser.elementByCss('#query').text()
- expect(text).toBe('query:0')
- })
-
- it('should escape streaming data correctly', async () => {
- const browser = await next.browser('/escaping-rsc')
- const manipulated = await browser.eval(`window.__manipulated_by_injection`)
- expect(manipulated).toBe(undefined)
- })
-
- it('should render built-in 404 page for missing route if pagesDir is not presented', async () => {
- const res = await next.fetch('/does-not-exist')
-
- expect(res.status).toBe(404)
- const html = await res.text()
- expect(html).toContain('This page could not be found')
- })
-
- it('should suspense next/legacy/image in server components', async () => {
- const $ = await next.render$('/next-api/image-legacy')
- const imageTag = $('#myimg')
-
- expect(imageTag.attr('src')).toContain('data:image')
- })
-
- it('should suspense next/image in server components', async () => {
- const $ = await next.render$('/next-api/image-new')
- const imageTag = $('#myimg')
-
- expect(imageTag.attr('src')).toMatch(/test.+jpg/)
- })
-
- it('should handle various kinds of exports correctly', async () => {
- const $ = await next.render$('/various-exports')
- const content = $('body').text()
-
- expect(content).toContain('abcde')
- expect(content).toContain('default-export-arrow.client')
- expect(content).toContain('named.client')
-
- const browser = await next.browser('/various-exports')
- const hydratedContent = await browser.waitForElementByCss('body').text()
-
- expect(hydratedContent).toContain('abcde')
- expect(hydratedContent).toContain('default-export-arrow.client')
- expect(hydratedContent).toContain('named.client')
- expect(hydratedContent).toContain('cjs-shared')
- expect(hydratedContent).toContain('cjs-client')
- expect(hydratedContent).toContain('Export All: one, two, two')
- })
-
- it('should support native modules in server component', async () => {
- const $ = await next.render$('/native-module')
- const content = $('body').text()
-
- expect(content).toContain('fs: function')
- expect(content).toContain('foo.client')
- })
-
- it('should resolve different kinds of components correctly', async () => {
- const $ = await next.render$('/shared')
- const main = $('#main').html()
- const content = $('#bar').text()
-
- // Should have 5 occurrences of "client_component".
- expect(Array.from(main.matchAll(/client_component/g)).length).toBe(5)
-
- // Should have 2 occurrences of "shared:server", and 2 occurrences of
- // "shared:client".
- const sharedServerModule = Array.from(main.matchAll(/shared:server:(\d+)/g))
- const sharedClientModule = Array.from(main.matchAll(/shared:client:(\d+)/g))
- expect(sharedServerModule.length).toBe(2)
- expect(sharedClientModule.length).toBe(2)
-
- // Should have 2 modules created for the shared component.
- expect(sharedServerModule[0][1]).toBe(sharedServerModule[1][1])
- expect(sharedClientModule[0][1]).toBe(sharedClientModule[1][1])
- expect(sharedServerModule[0][1]).not.toBe(sharedClientModule[0][1])
- expect(content).toContain('bar.server.js:')
- })
-
- it('should render initial styles of css-in-js in nodejs SSR correctly', async () => {
- const $ = await next.render$('/css-in-js')
- const head = $('head').html()
-
- // from styled-jsx
- expect(head).toMatch(/{color:(\s*)purple;?}/) // styled-jsx/style
- expect(head).toMatch(/{color:(\s*)hotpink;?}/) // styled-jsx/css
-
- // from styled-components
- expect(head).toMatch(/{color:(\s*)blue;?}/)
- })
-
- it('should render initial styles of css-in-js in edge SSR correctly', async () => {
- const $ = await next.render$('/css-in-js/edge')
- const head = $('head').html()
-
- // from styled-jsx
- expect(head).toMatch(/{color:(\s*)purple;?}/) // styled-jsx/style
- expect(head).toMatch(/{color:(\s*)hotpink;?}/) // styled-jsx/css
-
- // from styled-components
- expect(head).toMatch(/{color:(\s*)blue;?}/)
- })
-
- it('should render css-in-js suspense boundary correctly', async () => {
- await next.fetch('/css-in-js/suspense').then(async (response) => {
- const results = []
-
- await resolveStreamResponse(response, (chunk: string) => {
- const isSuspenseyDataResolved =
- /