diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index ae0737173e3..00000000000
--- a/.eslintignore
+++ /dev/null
@@ -1,5 +0,0 @@
-scripts
-plugins
-next.config.js
-.claude/
-worker-bundle.dist.js
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index 7bc6ab9333b..00000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "root": true,
- "extends": "next/core-web-vitals",
- "parser": "@typescript-eslint/parser",
- "plugins": ["@typescript-eslint", "eslint-plugin-react-compiler", "local-rules"],
- "rules": {
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
- "react-hooks/exhaustive-deps": "error",
- "react/no-unknown-property": ["error", {"ignore": ["meta"]}],
- "react-compiler/react-compiler": "error",
- "local-rules/lint-markdown-code-blocks": "error"
- },
- "env": {
- "node": true,
- "commonjs": true,
- "browser": true,
- "es6": true
- },
- "overrides": [
- {
- "files": ["src/content/**/*.md"],
- "parser": "./eslint-local-rules/parser",
- "parserOptions": {
- "sourceType": "module"
- },
- "rules": {
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": "off",
- "react-hooks/exhaustive-deps": "off",
- "react/no-unknown-property": "off",
- "react-compiler/react-compiler": "off",
- "local-rules/lint-markdown-code-blocks": "error"
- }
- }
- ]
-}
diff --git a/eslint-local-rules/rules/metadata.js b/eslint-local-rules/rules/metadata.js
index fb58a37c290..90b75681f43 100644
--- a/eslint-local-rules/rules/metadata.js
+++ b/eslint-local-rules/rules/metadata.js
@@ -84,7 +84,9 @@ function parseExpectedErrorsEntries(rawEntries) {
if (parsed && typeof parsed === 'object') {
for (const [key, value] of Object.entries(parsed)) {
- entries[key] = normalizeEntryValues(Array.isArray(value) ? value.flat() : value);
+ entries[key] = normalizeEntryValues(
+ Array.isArray(value) ? value.flat() : value
+ );
}
}
@@ -92,7 +94,9 @@ function parseExpectedErrorsEntries(rawEntries) {
}
function parseExpectedErrorsToken(tokenText) {
- const match = tokenText.match(/^\{\s*expectedErrors\s*:\s*(\{[\s\S]*\})\s*\}$/);
+ const match = tokenText.match(
+ /^\{\s*expectedErrors\s*:\s*(\{[\s\S]*\})\s*\}$/
+ );
if (!match) {
return null;
}
@@ -103,7 +107,7 @@ function parseExpectedErrorsToken(tokenText) {
try {
entries = parseExpectedErrorsEntries(entriesSource);
- } catch (error) {
+ } catch (_error) {
parseError = true;
entries = {};
}
@@ -203,7 +207,9 @@ function cloneMetadata(metadata) {
}
function findExpectedErrorsToken(metadata) {
- return metadata.tokens.find((token) => token.type === 'expectedErrors') || null;
+ return (
+ metadata.tokens.find((token) => token.type === 'expectedErrors') || null
+ );
}
function getCompilerExpectedLines(metadata) {
diff --git a/eslint-local-rules/rules/react-compiler.js b/eslint-local-rules/rules/react-compiler.js
index 26d3878ee8e..4c6cadfac8e 100644
--- a/eslint-local-rules/rules/react-compiler.js
+++ b/eslint-local-rules/rules/react-compiler.js
@@ -82,7 +82,7 @@ function runReactCompiler(code, filename) {
configFile: false,
babelrc: false,
});
- } catch (error) {
+ } catch (_error) {
return {...result, diagnostics: []};
}
@@ -98,17 +98,19 @@ function runReactCompiler(code, filename) {
continue;
}
- const loc = typeof detail.primaryLocation === 'function'
- ? detail.primaryLocation()
- : null;
+ const loc =
+ typeof detail.primaryLocation === 'function'
+ ? detail.primaryLocation()
+ : null;
if (loc == null || typeof loc === 'symbol') {
continue;
}
- const message = typeof detail.printErrorMessage === 'function'
- ? detail.printErrorMessage(result.sourceCode, {eslint: true})
- : detail.description || 'Unknown React Compiler error';
+ const message =
+ typeof detail.printErrorMessage === 'function'
+ ? detail.printErrorMessage(result.sourceCode, {eslint: true})
+ : detail.description || 'Unknown React Compiler error';
diagnostics.push({detail, loc, message});
}
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 00000000000..5bfef20f451
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,87 @@
+import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
+import reactCompiler from "eslint-plugin-react-compiler";
+import localRules from "eslint-plugin-local-rules";
+import globals from "globals";
+import tsParser from "@typescript-eslint/parser";
+import parser from "./eslint-local-rules/parser.js";
+
+// Extract plugin instances from next's config to avoid
+// "Cannot redefine plugin" errors in flat config.
+function getNextPlugin(name) {
+ return nextCoreWebVitals.find((c) => c.plugins?.[name])?.plugins[name];
+}
+const tsPlugin = getNextPlugin("@typescript-eslint");
+const reactHooksPlugin = getNextPlugin("react-hooks");
+
+export default [
+ {
+ ignores: [
+ "**/scripts",
+ "**/plugins",
+ "**/next.config.js",
+ "**/.claude/",
+ "**/worker-bundle.dist.js",
+ ],
+ },
+ ...nextCoreWebVitals,
+ {
+ plugins: {
+ "@typescript-eslint": tsPlugin,
+ "react-hooks": reactHooksPlugin,
+ "react-compiler": reactCompiler,
+ "local-rules": localRules,
+ },
+
+ languageOptions: {
+ globals: Object.fromEntries(
+ Object.entries({
+ ...globals.node,
+ ...globals.commonjs,
+ ...globals.browser,
+ }).map(([key, value]) => [key.trim(), value])
+ ),
+
+ parser: tsParser,
+ },
+
+ rules: {
+ "no-unused-vars": "off",
+
+ "@typescript-eslint/no-unused-vars": ["error", {
+ varsIgnorePattern: "^_",
+ caughtErrorsIgnorePattern: "^_",
+ }],
+
+ "react-hooks/exhaustive-deps": "error",
+
+ "react/no-unknown-property": ["error", {
+ ignore: ["meta"],
+ }],
+
+ // New rules in react-hooks plugin — pre-existing patterns need refactoring
+ "react-hooks/set-state-in-effect": "warn",
+ "react-hooks/refs": "warn",
+
+ "react-compiler/react-compiler": "error",
+ "local-rules/lint-markdown-code-blocks": "error",
+ },
+ },
+ {
+ files: ["src/content/**/*.md"],
+
+ languageOptions: {
+ parser: parser,
+ ecmaVersion: 5,
+ sourceType: "module",
+ },
+
+ rules: {
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "react-hooks/exhaustive-deps": "off",
+ "react/no-unknown-property": "off",
+ "react-compiler/react-compiler": "off",
+ "local-rules/lint-markdown-code-blocks": "error",
+ },
+ },
+];
diff --git a/next-env.d.ts b/next-env.d.ts
index 52e831b4342..552b2a0c7fe 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+import './.next/types/routes.d.ts';
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/next.config.js b/next.config.js
index f8ec196e131..5600454ffa7 100644
--- a/next.config.js
+++ b/next.config.js
@@ -13,12 +13,29 @@
* @type {import('next').NextConfig}
**/
const nextConfig = {
+ turbopack: {
+ resolveAlias: {
+ // Don't bundle the shim unnecessarily.
+ 'use-sync-external-store/shim': 'react',
+ // ESLint depends on the CommonJS version of esquery,
+ // but Webpack loads the ESM version by default. This
+ // alias ensures the correct version is used.
+ //
+ // More info:
+ // https://github.com/reactjs/react.dev/pull/8115
+ esquery: 'esquery/dist/esquery.min.js',
+ // Replace transitive dependencies with lightweight shims.
+ raf: './src/utils/rafShim.js',
+ process: './src/utils/processShim.js',
+ },
+ },
pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'],
reactStrictMode: true,
experimental: {
scrollRestoration: true,
- reactCompiler: true,
+ turbopackImportTypeText: true,
},
+ reactCompiler: true,
async rewrites() {
return {
beforeFiles: [
@@ -44,58 +61,6 @@ const nextConfig = {
};
},
env: {},
- webpack: (config, {dev, isServer, ...options}) => {
- if (process.env.ANALYZE) {
- const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
- config.plugins.push(
- new BundleAnalyzerPlugin({
- analyzerMode: 'static',
- reportFilename: options.isServer
- ? '../analyze/server.html'
- : './analyze/client.html',
- })
- );
- }
-
- // Don't bundle the shim unnecessarily.
- config.resolve.alias['use-sync-external-store/shim'] = 'react';
-
- // ESLint depends on the CommonJS version of esquery,
- // but Webpack loads the ESM version by default. This
- // alias ensures the correct version is used.
- //
- // More info:
- // https://github.com/reactjs/react.dev/pull/8115
- config.resolve.alias['esquery'] = 'esquery/dist/esquery.min.js';
-
- const {IgnorePlugin, NormalModuleReplacementPlugin} = require('webpack');
- config.plugins.push(
- new NormalModuleReplacementPlugin(
- /^raf$/,
- require.resolve('./src/utils/rafShim.js')
- ),
- new NormalModuleReplacementPlugin(
- /^process$/,
- require.resolve('./src/utils/processShim.js')
- ),
- new IgnorePlugin({
- checkResource(resource, context) {
- if (
- /\/eslint\/lib\/rules$/.test(context) &&
- /\.\/[\w-]+(\.js)?$/.test(resource)
- ) {
- // Skips imports of built-in rules that ESLint
- // tries to carry into the bundle by default.
- // We only want the engine and the React rules.
- return true;
- }
- return false;
- },
- })
- );
-
- return config;
- },
};
module.exports = nextConfig;
diff --git a/package.json b/package.json
index 359f30d3e21..057f9698087 100644
--- a/package.json
+++ b/package.json
@@ -4,12 +4,11 @@
"private": true,
"license": "CC",
"scripts": {
- "analyze": "ANALYZE=true next build",
+ "analyze": "next experimental-analyze",
"dev": "next-remote-watch ./src/content",
- "prebuild:rsc": "node scripts/buildRscWorker.mjs",
"build": "node scripts/buildRscWorker.mjs && next build && node --experimental-modules ./scripts/downloadFonts.mjs",
- "lint": "next lint && eslint \"src/content/**/*.md\"",
- "lint:fix": "next lint --fix && eslint \"src/content/**/*.md\" --fix",
+ "lint": "eslint . && eslint \"src/content/**/*.md\"",
+ "lint:fix": "eslint --fix . && eslint \"src/content/**/*.md\" --fix",
"format:source": "prettier --config .prettierrc --write \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"",
"nit:source": "prettier --config .prettierrc --list-different \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"",
"prettier": "yarn format:source",
@@ -36,13 +35,12 @@
"classnames": "^2.2.6",
"debounce": "^1.2.1",
"github-slugger": "^1.3.0",
- "next": "15.1.12",
+ "next": "16.2.0",
"next-remote-watch": "^1.0.0",
"parse-numeric-range": "^1.2.0",
- "raw-loader": "^4.0.2",
- "react": "^19.0.0",
+ "react": "19.2.4",
"react-collapsed": "4.0.4",
- "react-dom": "^19.0.0",
+ "react-dom": "19.2.4",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1"
},
@@ -58,8 +56,9 @@
"@types/mdx-js__react": "^1.5.2",
"@types/node": "^14.6.4",
"@types/parse-numeric-range": "^0.0.1",
- "@types/react": "^19.0.0",
- "@types/react-dom": "^19.0.0",
+ "@types/prop-types": "^15.7.15",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2",
"asyncro": "^3.0.0",
@@ -68,8 +67,8 @@
"babel-plugin-react-compiler": "^1.0.0",
"chalk": "4.1.2",
"esbuild": "^0.24.0",
- "eslint": "7.x",
- "eslint-config-next": "12.0.3",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.0",
"eslint-config-react-app": "^5.2.1",
"eslint-plugin-flowtype": "4.x",
"eslint-plugin-import": "2.x",
@@ -90,7 +89,7 @@
"postcss": "^8.4.5",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-preset-env": "^6.7.0",
- "prettier": "^2.5.1",
+ "prettier": "^3.8.1",
"react-server-dom-webpack": "^19.2.4",
"reading-time": "^1.2.0",
"remark": "^12.0.1",
@@ -104,20 +103,18 @@
"rss": "^1.2.2",
"tailwindcss": "^3.4.1",
"typescript": "^5.7.2",
- "unist-util-visit": "^2.0.3",
- "webpack-bundle-analyzer": "^4.5.0"
+ "unist-util-visit": "^2.0.3"
},
"engines": {
"node": ">=16.8.0"
},
- "nextBundleAnalysis": {
- "budget": null,
- "budgetPercentIncreaseRed": 10,
- "showDetails": true
- },
"lint-staged": {
"*.{js,ts,jsx,tsx,css}": "yarn prettier",
"src/**/*.md": "yarn fix-headings"
},
- "packageManager": "yarn@1.22.22"
+ "packageManager": "yarn@1.22.22",
+ "resolutions": {
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3"
+ }
}
diff --git a/src/components/Icon/IconCanary.tsx b/src/components/Icon/IconCanary.tsx
index 97b9f7cef21..19dad08aea0 100644
--- a/src/components/Icon/IconCanary.tsx
+++ b/src/components/Icon/IconCanary.tsx
@@ -9,10 +9,10 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import {memo} from 'react';
+import React, {memo} from 'react';
export const IconCanary = memo<
- JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'}
+ React.JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'}
>(function IconCanary(
{className, title, size} = {
className: undefined,
diff --git a/src/components/Icon/IconChevron.tsx b/src/components/Icon/IconChevron.tsx
index 15f34e15351..c6a9ec46e23 100644
--- a/src/components/Icon/IconChevron.tsx
+++ b/src/components/Icon/IconChevron.tsx
@@ -9,11 +9,11 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import {memo} from 'react';
+import React, {memo} from 'react';
import cn from 'classnames';
export const IconChevron = memo<
- JSX.IntrinsicElements['svg'] & {
+ React.JSX.IntrinsicElements['svg'] & {
/**
* The direction the arrow should point.
* `start` and `end` are relative to the current locale.
diff --git a/src/components/Icon/IconClose.tsx b/src/components/Icon/IconClose.tsx
index dc4ad7c72df..b2ccdb3c0b7 100644
--- a/src/components/Icon/IconClose.tsx
+++ b/src/components/Icon/IconClose.tsx
@@ -12,23 +12,23 @@
import {memo} from 'react';
import type {SVGProps} from 'react';
-export const IconClose = memo>(function IconClose(
- props
-) {
- return (
-
- );
-});
+export const IconClose = memo>(
+ function IconClose(props) {
+ return (
+
+ );
+ }
+);
diff --git a/src/components/Icon/IconCodeBlock.tsx b/src/components/Icon/IconCodeBlock.tsx
index ba61f237ed6..0af2200aec2 100644
--- a/src/components/Icon/IconCodeBlock.tsx
+++ b/src/components/Icon/IconCodeBlock.tsx
@@ -9,9 +9,9 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import {memo} from 'react';
+import React, {memo} from 'react';
-export const IconCodeBlock = memo(
+export const IconCodeBlock = memo(
function IconCodeBlock({className}) {
return (