diff --git a/e2e/cases/server/overlay-type-errors/index.test.ts b/e2e/cases/server/overlay-type-errors/index.test.ts
new file mode 100644
index 0000000000..18fed604b8
--- /dev/null
+++ b/e2e/cases/server/overlay-type-errors/index.test.ts
@@ -0,0 +1,33 @@
+import { dev, proxyConsole } from '@e2e/helper';
+import { expect, test } from '@playwright/test';
+
+const cwd = __dirname;
+
+test('should display type errors on overlay correctly', async ({ page }) => {
+ const { restore } = proxyConsole();
+
+ const rsbuild = await dev({
+ cwd,
+ page,
+ });
+
+ const errorOverlay = page.locator('rsbuild-error-overlay');
+
+ await expect(errorOverlay.locator('.title')).toHaveText('Build failed');
+
+ // The first span is "TS2322: "
+ const firstSpan = errorOverlay.locator('span').first();
+ expect(await firstSpan.textContent()).toEqual('TS2322: ');
+ expect(await firstSpan.getAttribute('style')).toEqual('color:#888');
+
+ // The first link is "/src/index.ts:3:1"
+ const firstLink = errorOverlay.locator('.file-link').first();
+ expect(await firstLink.getAttribute('class')).toEqual('file-link');
+ expect(
+ (await firstLink.textContent())?.endsWith('/src/index.ts:3:1'),
+ ).toBeTruthy();
+
+ await rsbuild.close();
+
+ restore();
+});
diff --git a/e2e/cases/server/overlay-type-errors/rsbuild.config.ts b/e2e/cases/server/overlay-type-errors/rsbuild.config.ts
new file mode 100644
index 0000000000..6e1e061f9e
--- /dev/null
+++ b/e2e/cases/server/overlay-type-errors/rsbuild.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from '@rsbuild/core';
+import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
+
+export default defineConfig({
+ plugins: [pluginTypeCheck()],
+});
diff --git a/e2e/cases/server/overlay-type-errors/src/index.ts b/e2e/cases/server/overlay-type-errors/src/index.ts
new file mode 100644
index 0000000000..9a0048bf68
--- /dev/null
+++ b/e2e/cases/server/overlay-type-errors/src/index.ts
@@ -0,0 +1,3 @@
+// This is a type error
+let num = 1;
+num = '2';
diff --git a/e2e/cases/server/overlay-type-errors/tsconfig.json b/e2e/cases/server/overlay-type-errors/tsconfig.json
new file mode 100644
index 0000000000..0cad3cc272
--- /dev/null
+++ b/e2e/cases/server/overlay-type-errors/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["DOM", "ES2020"],
+ "module": "ESNext",
+ "strict": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "moduleResolution": "Bundler"
+ },
+ "include": ["src"]
+}
diff --git a/e2e/package.json b/e2e/package.json
index 0589741676..ebe399dc31 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -35,6 +35,7 @@
"@rsbuild/plugin-stylus": "workspace:*",
"@rsbuild/plugin-svelte": "workspace:*",
"@rsbuild/plugin-svgr": "workspace:*",
+ "@rsbuild/plugin-type-check": "^1.2.0",
"@rsbuild/plugin-webpack-swc": "workspace:*",
"@rsbuild/plugin-vue": "workspace:*",
"@rsbuild/plugin-vue-jsx": "^1.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a21e1772ab..e8182b4900 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -126,6 +126,9 @@ importers:
'@rsbuild/plugin-svgr':
specifier: workspace:*
version: link:../packages/plugin-svgr
+ '@rsbuild/plugin-type-check':
+ specifier: ^1.2.0
+ version: 1.2.0(@rsbuild/core@packages+core)(@rspack/core@1.1.8(@swc/helpers@0.5.15))(typescript@5.7.2)
'@rsbuild/plugin-vue':
specifier: workspace:*
version: link:../packages/plugin-vue
@@ -2533,6 +2536,14 @@ packages:
peerDependencies:
'@rsbuild/core': 1.x
+ '@rsbuild/plugin-type-check@1.2.0':
+ resolution: {integrity: sha512-bx+WmtK7K5Jc07IQn2cBDqcP/Kt98u16NiW3EyxqJGhQ1OgFvK6ewc70+AJnBvtjE+MMB70NAXEl8MNOtSxz6g==}
+ peerDependencies:
+ '@rsbuild/core': 1.x
+ peerDependenciesMeta:
+ '@rsbuild/core':
+ optional: true
+
'@rsbuild/plugin-vue-jsx@1.1.0':
resolution: {integrity: sha512-ZvYXPs00bG5mMhAFsJ5cVbHgF7Kz+rRpMlRHOJQhKGihFEiUK1Wx8Qfmn5ZrMBIgIGp0BaOnHoGSPH7fVQikCA==}
peerDependencies:
@@ -6289,6 +6300,16 @@ packages:
resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==}
engines: {node: '>=6'}
+ ts-checker-rspack-plugin@1.1.0:
+ resolution: {integrity: sha512-nhUzSuSjfgVAJjc+vJa9q8uE7MxAbustG9InRp4ylMfIbkqyJjh7gSuEcL//l76ZSwoKcCod+5lv2mNO0Ugh8g==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ '@rspack/core': ^1.0.0
+ typescript: '>=3.8.0'
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -8187,6 +8208,18 @@ snapshots:
reduce-configs: 1.1.0
sass-embedded: 1.83.0
+ '@rsbuild/plugin-type-check@1.2.0(@rsbuild/core@packages+core)(@rspack/core@1.1.8(@swc/helpers@0.5.15))(typescript@5.7.2)':
+ dependencies:
+ deepmerge: 4.3.1
+ json5: 2.2.3
+ reduce-configs: 1.1.0
+ ts-checker-rspack-plugin: 1.1.0(@rspack/core@1.1.8(@swc/helpers@0.5.15))(typescript@5.7.2)
+ optionalDependencies:
+ '@rsbuild/core': link:packages/core
+ transitivePeerDependencies:
+ - '@rspack/core'
+ - typescript
+
'@rsbuild/plugin-vue-jsx@1.1.0(@babel/core@7.26.0)(@rsbuild/core@packages+core)':
dependencies:
'@rsbuild/plugin-babel': 1.0.3(@rsbuild/core@packages+core)
@@ -12322,6 +12355,18 @@ snapshots:
dependencies:
matchit: 1.1.0
+ ts-checker-rspack-plugin@1.1.0(@rspack/core@1.1.8(@swc/helpers@0.5.15))(typescript@5.7.2):
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@rspack/lite-tapable': 1.0.1
+ chokidar: 3.6.0
+ memfs: 4.14.0
+ minimatch: 9.0.5
+ picocolors: 1.1.1
+ typescript: 5.7.2
+ optionalDependencies:
+ '@rspack/core': 1.1.8(@swc/helpers@0.5.15)
+
ts-interface-checker@0.1.13: {}
tsconfig-paths-webpack-plugin@4.2.0: