
在 Next.js 与 Vite 共存的混合项目中,jsx: "react-jsx" 配置易被覆盖导致 React is not defined 错误;根本解法是显式导入 React,并配合合理的 TypeScript 和构建工具配置。
在 next.js 与 vite 共存的混合项目中,`jsx: "react-jsx"` 配置易被覆盖导致 `react is not defined` 错误;根本解法是显式导入 react,并配合合理的 typescript 和构建工具配置。
当在同一个项目中同时使用 Next.js(服务端渲染/SSR)和 Vite(客户端开发/测试)时,TypeScript 的 JSX 编译行为容易产生冲突。典型表现为:手动将 tsconfig.json 中的 "jsx" 设为 "react-jsx" 后,运行 npm run dev(触发 Next.js 的类型检查或配置生成)时,该值又被自动重写为 "preserve"——这会导致 Babel 或 Vite 在编译 .tsx 文件时跳过自动注入 React.createElement 调用,进而引发运行时错误:
ReferenceError: React is not defined ❯ Module.Home [as default] src/app/page.tsx:2:3
✅ 正确解决方案:显式导入 + 配置隔离
核心原则:不要依赖自动 React 注入,而应显式声明依赖。
从 React 17+ 开始,"react-jsx" 模式虽支持无须手动 import React from 'react',但该特性高度依赖构建工具对 @jsxImportSource 的完整支持及一致配置。而在 Next.js + Vite 混合环境中,二者对 tsconfig.json 的读取优先级、插件链和 JSX 处理阶段并不统一,极易出现“一处配置、两处解释”的问题。
因此,最稳定、跨工具兼容的写法是:在每个使用 JSX 的文件顶部显式导入 React:
// src/app/page.tsx
import * as React from 'react'; // ✅ 关键:确保 React 命名空间可用
export default function Home() {
return <div>Home of Coach-Next</div>;
}? 注意:使用 import * as React 而非 import React from 'react',可避免默认导出与命名导出混淆(尤其在严格 ESM 环境下),也兼容 react-jsx 和 preserve 两种 JSX 模式。
? 补充配置建议(提升健壮性)
-
分离 tsconfig.json 配置作用域
Next.js 推荐使用 tsconfig.json 作为主配置,而 Vite 测试环境(如 Vitest)应通过 vite.config.ts 显式指定 tsconfig 路径,避免被 Next.js CLI 覆盖:// vite.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { include: ['**/*.test.{ts,tsx}'], environment: 'jsdom', globals: true, // 显式指定测试专用 tsconfig,避免受 next dev 影响 alias: { react: 'react' }, setupFiles: ['./src/test/setup.ts'], }, }); -
为 Vitest 单独维护 tsconfig.test.json(可选但推荐)
创建独立配置,锁定测试环境的 JSX 行为:// tsconfig.test.json { "extends": "./tsconfig.json", "compilerOptions": { "jsx": "react-jsx" } }并在 vitest.config.ts 中引用:
test: { // ... typecheck: { enabled: true, tsconfig: './tsconfig.test.json' } } -
禁用 Next.js 的自动 tsconfig 覆盖(Next.js ≥14.2)
若使用较新版本 Next.js,可在 next.config.js 中添加:module.exports = { typescript: { ignoreBuildErrors: true, // 避免构建时强制修正 tsconfig }, };
? 验证你的测试是否真正可靠
修改后的 home.test.ts 应保持简洁且无需 React 导入(因组件内部已处理):
/**
* @jest-environment jsdom
*/
import { render, screen } from '@testing-library/react';
import { test, expect } from 'vitest';
import Home from '../app/page';
test('Home page', () => {
render(<Home />); // ✅ 推荐:JSX 元素调用,而非 Home()
expect(screen.getByText('Home of Coach-Next')).toBeInTheDocument();
});⚠️ 注意:原测试中 render(Home()) 是函数调用,绕过了 React 渲染器生命周期,无法触发 hooks、上下文等,属于反模式。应始终使用
形式。
✅ 总结
| 场景 | 推荐做法 |
|---|---|
| JSX 编译稳定性 | 所有 .tsx 文件顶部添加 import * as React from 'react' |
| tsconfig 冲突 | 不强依赖 "jsx": "react-jsx" 全局配置;改用显式导入 + 工具链隔离 |
| Vitest 运行 | 使用独立 tsconfig.test.json,并通过 vitest.config.ts 显式指定 |
| Next.js 开发 | 保持 tsconfig.json 中 "jsx": "preserve"(Next.js 默认兼容),无需强行修改 |
这一方案不依赖构建工具“魔法”,符合 TypeScript 类型系统设计本意,也完全适配 ESM、SWC、Babel 及未来可能引入的新编译器,是混合技术栈下的长期可维护实践。










