- {generateMetadata({ params }).title}
+ {(await generateMetadata({ params })).title}
{(await getPosts())
.filter(post =>
post.frontMatter.tags.includes(decodeURIComponent(params.tag))
diff --git a/package.json b/package.json
index c204cbbb30..567cf1ad2a 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"eslint-plugin-deprecation": "3.0.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.2",
+ "eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-sonarjs": "^2.0.4",
"eslint-plugin-tailwindcss": "3.17.3",
diff --git a/packages/esbuild-react-compiler-plugin/package.json b/packages/esbuild-react-compiler-plugin/package.json
new file mode 100644
index 0000000000..fffc8f4157
--- /dev/null
+++ b/packages/esbuild-react-compiler-plugin/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "esbuild-react-compiler-plugin",
+ "version": "0.0.0",
+ "type": "module",
+ "description": "",
+ "author": "Dimitri POSTOLOV",
+ "license": "ISC",
+ "private": true,
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsup --watch"
+ },
+ "dependencies": {
+ "react-compiler-webpack": "0.1.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.0.0"
+ }
+}
diff --git a/packages/esbuild-react-compiler-plugin/src/env.d.ts b/packages/esbuild-react-compiler-plugin/src/env.d.ts
new file mode 100644
index 0000000000..10439f37a1
--- /dev/null
+++ b/packages/esbuild-react-compiler-plugin/src/env.d.ts
@@ -0,0 +1,5 @@
+declare module 'react-compiler-webpack/dist/react-compiler-loader.js' {
+ export default function reactCompilerLoader(
+ source: string | Buffer
+ ): Promise
+}
diff --git a/packages/esbuild-react-compiler-plugin/src/index.ts b/packages/esbuild-react-compiler-plugin/src/index.ts
new file mode 100644
index 0000000000..f0349fa787
--- /dev/null
+++ b/packages/esbuild-react-compiler-plugin/src/index.ts
@@ -0,0 +1,64 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import reactCompilerLoader from 'react-compiler-webpack/dist/react-compiler-loader.js'
+import type { Options } from 'tsup'
+
+const reactCompilerConfig = {
+ sources(_filename: string) {
+ return true
+ },
+ target: '18'
+}
+
+export const reactCompilerPlugin = (
+ filter: RegExp
+): NonNullable[number] => ({
+ name: 'react-compiler',
+ setup(build) {
+ build.onLoad({ filter }, async args => {
+ // Read the file content
+ const code = await fs.readFile(args.path)
+ return new Promise<{
+ contents: string
+ loader: 'ts' | 'tsx'
+ }>((resolve, reject) => {
+ function callback(error: Error | null, result?: string) {
+ if (!result) {
+ reject(error)
+ return
+ }
+ // Mark the file as a ts/tsx file
+ const loader = path.extname(args.path).slice(1) as 'ts' | 'tsx'
+ const relativePath = path.relative(process.cwd(), args.path)
+
+ if (
+ /^import \{ c as _c } from "react-compiler-runtime";/m.test(result)
+ ) {
+ console.info(
+ '🚀 File',
+ relativePath,
+ 'was optimized with react-compiler'
+ )
+ } else if (!/^'use no memo'/m.test(result)) {
+ console.error(
+ '❌ File',
+ relativePath,
+ 'was not optimized with react-compiler'
+ )
+ }
+
+ resolve({ contents: result, loader })
+ }
+
+ reactCompilerLoader.call(
+ {
+ async: () => callback,
+ getOptions: () => reactCompilerConfig,
+ resourcePath: args.path
+ },
+ code
+ )
+ })
+ })
+ }
+})
diff --git a/packages/esbuild-react-compiler-plugin/tsconfig.json b/packages/esbuild-react-compiler-plugin/tsconfig.json
new file mode 100644
index 0000000000..61aa255661
--- /dev/null
+++ b/packages/esbuild-react-compiler-plugin/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "es2022",
+ "module": "ESNext",
+ "declaration": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "lib": ["esnext", "dom"],
+ "moduleResolution": "node",
+ "resolveJsonModule": true
+ },
+ "exclude": ["dist"]
+}
diff --git a/packages/esbuild-react-compiler-plugin/tsup.config.ts b/packages/esbuild-react-compiler-plugin/tsup.config.ts
new file mode 100644
index 0000000000..f598dfe9b4
--- /dev/null
+++ b/packages/esbuild-react-compiler-plugin/tsup.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsup'
+import packageJson from './package.json'
+
+export default defineConfig({
+ name: packageJson.name,
+ entry: ['src/**/*.ts'],
+ format: 'esm',
+ dts: true,
+ splitting: process.env.NODE_ENV === 'production',
+ bundle: false
+})
diff --git a/packages/nextra-theme-blog/package.json b/packages/nextra-theme-blog/package.json
index 1d1e9bb984..89d3282d07 100644
--- a/packages/nextra-theme-blog/package.json
+++ b/packages/nextra-theme-blog/package.json
@@ -31,12 +31,14 @@
},
"dependencies": {
"next-themes": "^0.4.0",
- "next-view-transitions": "^0.3.0"
+ "next-view-transitions": "^0.3.0",
+ "react-compiler-runtime": "19.0.0-beta-df7b47d-20241124"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0-beta.2",
"@tailwindcss/typography": "^0.5.15",
"@types/react": "^18.2.23",
+ "esbuild-react-compiler-plugin": "workspace:*",
"next": "^15.0.2",
"nextra": "workspace:*",
"postcss": "^8.4.33",
diff --git a/packages/nextra-theme-blog/src/components/cusdis.tsx b/packages/nextra-theme-blog/src/components/cusdis.tsx
index 42ee692189..7619834a9d 100644
--- a/packages/nextra-theme-blog/src/components/cusdis.tsx
+++ b/packages/nextra-theme-blog/src/components/cusdis.tsx
@@ -17,7 +17,10 @@ export const Comments: FC<{
useEffect(() => {
try {
// update the theme for the cusdis iframe when theme changed
- window.CUSDIS?.setTheme(resolvedTheme as 'dark' | 'light')
+ if (window.CUSDIS) {
+ // window.CUSDIS? doesn't work with react-compiler
+ window.CUSDIS.setTheme(resolvedTheme as 'dark' | 'light')
+ }
} catch (error) {
console.error(error)
}
@@ -25,10 +28,10 @@ export const Comments: FC<{
if (!appId) {
console.warn('[nextra/cusdis] `appId` is required')
- return
+ return null
}
if (!mounted) {
- return
+ return null
}
return (
diff --git a/packages/nextra-theme-blog/src/components/go-back.tsx b/packages/nextra-theme-blog/src/components/go-back.tsx
index c2808b1b35..5cae4ae503 100644
--- a/packages/nextra-theme-blog/src/components/go-back.tsx
+++ b/packages/nextra-theme-blog/src/components/go-back.tsx
@@ -10,7 +10,7 @@ export const GoBack: FC = () => {
const segments = usePathname().split('/')
const isNestedPage = segments.length > 2
- if (!isNestedPage) return
+ if (!isNestedPage) return null
return (
)
diff --git a/packages/nextra-theme-docs/src/components/footer/index.tsx b/packages/nextra-theme-docs/src/components/footer/index.tsx
index 4e662c142c..ee5cbf1fd2 100644
--- a/packages/nextra-theme-docs/src/components/footer/index.tsx
+++ b/packages/nextra-theme-docs/src/components/footer/index.tsx
@@ -6,7 +6,7 @@ import { Switchers } from './switchers'
export const Footer: FC
> = ({
className,
- children = `MIT ${new Date().getFullYear()} © Nextra.`,
+ children,
...props
}) => {
return (
@@ -17,7 +17,7 @@ export const Footer: FC> = ({