Skip to content

Commit 882288b

Browse files
authored
Warn when Fast Refresh is disabled (React <16.10) (#15931)
1 parent 2d42b8e commit 882288b

File tree

25 files changed

+247
-11
lines changed

25 files changed

+247
-11
lines changed

errors/react-version.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Minimum React Version
2+
3+
#### Why This Error Occurred
4+
5+
Your project is using an old version of `react` or `react-dom` that does not
6+
meet the suggested minimum version requirement.
7+
8+
Next.js suggests using, at a minimum, `react@16.10.0` and `react-dom@16.10.0`.
9+
Older versions of `react` and `react-dom` do work with Next.js, however, they do
10+
not enable all of Next.js' features.
11+
12+
For example, the following features are not enabled with old React versions:
13+
14+
- [Fast Refresh](https://nextjs.org/docs/basic-features/fast-refresh): instantly
15+
view edits to your app without losing component state
16+
- Component stack trace in development: see the component tree that lead up to
17+
an error
18+
- Hydration mismatch warnings: trace down discrepancies in your React tree that
19+
cause performance problems
20+
21+
This list is not exhaustive, but illustrative in the value of upgrading React!
22+
23+
#### Possible Ways to Fix It
24+
25+
**Via npm**
26+
27+
```bash
28+
npm upgrade react@latest react-dom@latest
29+
```
30+
31+
**Via Yarn**
32+
33+
```bash
34+
yarn add react@latest react-dom@latest
35+
```
36+
37+
**Manually** Open your `package.json` and upgrade `react` and `react-dom`:
38+
39+
```json
40+
{
41+
"dependencies": {
42+
"react": "^16.10.0",
43+
"react-dom": "^16.10.0"
44+
}
45+
}
46+
```
47+
48+
### Useful Links
49+
50+
- [Fast Refresh blog post](https://nextjs.org/blog/next-9-4#fast-refresh)
51+
- [Fast Refresh docs](https://nextjs.org/docs/basic-features/fast-refresh)

packages/next/build/webpack-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export default async function getBaseWebpackConfig(
290290
} as ClientEntries)
291291
: undefined
292292

293-
let typeScriptPath
293+
let typeScriptPath: string | undefined
294294
try {
295295
typeScriptPath = resolveRequest('typescript', `${dir}/`)
296296
} catch (_) {}
@@ -302,7 +302,7 @@ export default async function getBaseWebpackConfig(
302302
let jsConfig
303303
// jsconfig is a subset of tsconfig
304304
if (useTypeScript) {
305-
const ts = (await import(typeScriptPath)) as typeof import('typescript')
305+
const ts = (await import(typeScriptPath!)) as typeof import('typescript')
306306
const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
307307
jsConfig = { compilerOptions: tsConfig.options }
308308
}

packages/next/cli/next-dev.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import arg from 'next/dist/compiled/arg/index.js'
44
import { existsSync } from 'fs'
55
import startServer from '../server/lib/start-server'
66
import { printAndExit } from '../server/lib/utils'
7+
import * as Log from '../build/output/log'
78
import { startedDevelopmentServer } from '../build/output'
89
import { cliCommand } from '../bin/next'
910

@@ -56,6 +57,35 @@ const nextDev: cliCommand = (argv) => {
5657
printAndExit(`> No such directory exists as the project root: ${dir}`)
5758
}
5859

60+
async function preflight() {
61+
const { getPackageVersion } = await import('../lib/get-package-version')
62+
const semver = await import('next/dist/compiled/semver').then(
63+
(res) => res.default
64+
)
65+
66+
const reactVersion: string | null = await getPackageVersion({
67+
cwd: dir,
68+
name: 'react',
69+
})
70+
if (reactVersion && semver.lt(reactVersion, '16.10.0')) {
71+
Log.warn(
72+
'Fast Refresh is disabled in your application due to an outdated `react` version. Please upgrade 16.10 or newer!' +
73+
' Read more: https://err.sh/next.js/react-version'
74+
)
75+
} else {
76+
const reactDomVersion: string | null = await getPackageVersion({
77+
cwd: dir,
78+
name: 'react-dom',
79+
})
80+
if (reactDomVersion && semver.lt(reactDomVersion, '16.10.0')) {
81+
Log.warn(
82+
'Fast Refresh is disabled in your application due to an outdated `react-dom` version. Please upgrade 16.10 or newer!' +
83+
' Read more: https://err.sh/next.js/react-version'
84+
)
85+
}
86+
}
87+
}
88+
5989
const port = args['--port'] || 3000
6090
const appUrl = `http://${args['--hostname'] || 'localhost'}:${port}`
6191

@@ -66,6 +96,9 @@ const nextDev: cliCommand = (argv) => {
6696
)
6797
.then(async (app) => {
6898
startedDevelopmentServer(appUrl)
99+
// Start preflight after server is listening and ignore errors:
100+
preflight().catch(() => {})
101+
// Finalize server bootup:
69102
await app.prepare()
70103
})
71104
.catch((err) => {

packages/next/compiled/conf/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/next/compiled/jsonwebtoken/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
The ISC License
2+
3+
Copyright (c) Isaac Z. Schlueter and Contributors
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
15+
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

packages/next/compiled/semver/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"semver","main":"index.js","license":"ISC"}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// issuer.endsWith(path.posix.sep) || issuer.endsWith(path.win32.sep)
2+
import findUp from 'next/dist/compiled/find-up'
3+
import * as path from 'path'
4+
import { promises as fs } from 'fs'
5+
import JSON5 from 'next/dist/compiled/json5'
6+
import { resolveRequest } from './resolve-request'
7+
8+
export async function getPackageVersion({
9+
cwd,
10+
name,
11+
}: {
12+
cwd: string
13+
name: string
14+
}): Promise<string | null> {
15+
const configurationPath: string | undefined = await findUp('package.json', {
16+
cwd,
17+
})
18+
if (!configurationPath) {
19+
return null
20+
}
21+
22+
const content = await fs.readFile(configurationPath, 'utf-8')
23+
const packageJson: any = JSON5.parse(content)
24+
25+
const { dependencies = {}, devDependencies = {} } = packageJson || {}
26+
if (!(dependencies[name] || devDependencies[name])) {
27+
return null
28+
}
29+
30+
const cwd2 =
31+
cwd.endsWith(path.posix.sep) || cwd.endsWith(path.win32.sep)
32+
? cwd
33+
: `${cwd}/`
34+
35+
try {
36+
const targetPath = resolveRequest(`${name}/package.json`, cwd2)
37+
const targetContent = await fs.readFile(targetPath, 'utf-8')
38+
return JSON5.parse(targetContent).version ?? null
39+
} catch {
40+
return null
41+
}
42+
}

packages/next/lib/resolve-request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import resolve from 'next/dist/compiled/resolve/index.js'
22
import path from 'path'
33

4-
export function resolveRequest(req: string, issuer: string) {
4+
export function resolveRequest(req: string, issuer: string): string {
55
// The `resolve` package is prebuilt through ncc, which prevents
66
// PnP from being able to inject itself into it. To circumvent
77
// this, we simply use PnP directly when available.

0 commit comments

Comments
 (0)