diff --git a/.changeset/calm-bats-create.md b/.changeset/calm-bats-create.md
new file mode 100644
index 000000000000..3d11c5ec15dc
--- /dev/null
+++ b/.changeset/calm-bats-create.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/preact': patch
+---
+
+Fixed an issue where the Preact integration would incorrectly intercept React 19 components, triggering "Invalid hook call" error logs.
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/astro.config.mjs b/packages/astro/e2e/fixtures/react19-preact-hook-error/astro.config.mjs
new file mode 100644
index 000000000000..7640254b068b
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/astro.config.mjs
@@ -0,0 +1,12 @@
+import preact from '@astrojs/preact';
+import react from '@astrojs/react';
+import { defineConfig } from 'astro/config';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [
+ // This issue only reproduces when the Preact integration is placed before the React integration.
+ preact({ include: ['**/preact/*'] }),
+ react({ include: ['**/react/*'] }),
+ ],
+});
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/package.json b/packages/astro/e2e/fixtures/react19-preact-hook-error/package.json
new file mode 100644
index 000000000000..3b1fc64a4d6e
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@e2e/react19-preact-hook-error",
+ "version": "0.0.0",
+ "private": true,
+ "devDependencies": {
+ "@astrojs/preact": "workspace:*",
+ "@astrojs/react": "workspace:*",
+ "astro": "workspace:*"
+ },
+ "dependencies": {
+ "preact": "^10.28.3",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4"
+ }
+}
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/preact/PreactCounter.tsx b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/preact/PreactCounter.tsx
new file mode 100644
index 000000000000..ace9c01ba428
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/preact/PreactCounter.tsx
@@ -0,0 +1,20 @@
+import type { ComponentChildren } from 'preact';
+import { useState } from 'preact/hooks';
+
+/** A counter written with Preact */
+export function PreactCounter({ children }: { children?: ComponentChildren }) {
+ const [count, setCount] = useState(0);
+ const add = () => setCount((i) => i + 1);
+ const subtract = () => setCount((i) => i - 1);
+
+ return (
+ <>
+
+ {children}
+ >
+ );
+}
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/react/ReactCounter.tsx b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/react/ReactCounter.tsx
new file mode 100644
index 000000000000..02eb1953907d
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/components/react/ReactCounter.tsx
@@ -0,0 +1,19 @@
+import { useState } from 'react';
+
+/** a counter written in React */
+export function Counter({ children, id }) {
+ const [count, setCount] = useState(0);
+ const add = () => setCount((i) => i + 1);
+ const subtract = () => setCount((i) => i - 1);
+
+ return (
+ <>
+
+ {children}
+ >
+ );
+}
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/src/pages/index.astro b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/pages/index.astro
new file mode 100644
index 000000000000..7dfd9f20de49
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/pages/index.astro
@@ -0,0 +1,28 @@
+---
+// Style Imports
+import '../styles/global.css';
+
+import { PreactCounter } from '../components/preact/PreactCounter';
+import * as react from '../components/react/ReactCounter';
+---
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello from React!
+
+
+
+ Hello from Preact!
+
+
+
+
diff --git a/packages/astro/e2e/fixtures/react19-preact-hook-error/src/styles/global.css b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/styles/global.css
new file mode 100644
index 000000000000..4912b4c399a4
--- /dev/null
+++ b/packages/astro/e2e/fixtures/react19-preact-hook-error/src/styles/global.css
@@ -0,0 +1,21 @@
+html,
+body {
+ font-family: system-ui;
+ margin: 0;
+}
+
+body {
+ padding: 2rem;
+}
+
+.counter {
+ display: grid;
+ font-size: 2em;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ margin-top: 2em;
+ place-items: center;
+}
+
+.counter-message {
+ text-align: center;
+}
diff --git a/packages/astro/e2e/react19-preact-hook-error.test.js b/packages/astro/e2e/react19-preact-hook-error.test.js
new file mode 100644
index 000000000000..3a28375cbcec
--- /dev/null
+++ b/packages/astro/e2e/react19-preact-hook-error.test.js
@@ -0,0 +1,42 @@
+import { expect } from '@playwright/test';
+import { testFactory } from './test-utils.js';
+
+const test = testFactory(import.meta.url, { root: './fixtures/react19-preact-hook-error/' });
+
+function hookError() {
+ const error = console.error;
+ const errors = [];
+ console.error = function (...args) {
+ errors.push(args);
+ };
+ return () => {
+ console.error = error;
+ return errors;
+ };
+}
+
+let devServer;
+let unhook;
+
+test.beforeAll(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+ unhook = hookError();
+});
+
+test.afterAll(async () => {
+ await devServer.stop();
+});
+
+// See: https://github.com/withastro/astro/issues/15341
+test.describe('React v19 and preact hook issue', () => {
+ test('should not have "Invalid hook call" errors', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ const errors = unhook();
+ const hasInvalidHookCallErrorLog = errors
+ .flat()
+ .some((log) => log.includes('Invalid hook call'));
+
+ expect(hasInvalidHookCallErrorLog).toBe(false);
+ });
+});
diff --git a/packages/integrations/preact/src/server.ts b/packages/integrations/preact/src/server.ts
index fde181343c75..193bb87f6600 100644
--- a/packages/integrations/preact/src/server.ts
+++ b/packages/integrations/preact/src/server.ts
@@ -136,8 +136,11 @@ function filteredConsoleError(msg: string, ...rest: any[]) {
// When attempting this on a React component, React may output
// the following error, which we can safely filter out:
const isKnownReactHookError =
- msg.includes('Warning: Invalid hook call.') &&
- msg.includes('https://reactjs.org/link/invalid-hook-call');
+ msg.includes('Invalid hook call.') &&
+ // for React v18 and earlier
+ (msg.includes('https://reactjs.org/link/invalid-hook-call') ||
+ // for React v19 and later
+ msg.includes('https://react.dev/link/invalid-hook-call'));
if (isKnownReactHookError) return;
}
originalConsoleError(msg, ...rest);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 11e30a5c919c..aff0187ef242 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1558,6 +1558,28 @@ importers:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
+ packages/astro/e2e/fixtures/react19-preact-hook-error:
+ dependencies:
+ preact:
+ specifier: ^10.28.3
+ version: 10.28.4
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.4
+ version: 19.2.4(react@19.2.4)
+ devDependencies:
+ '@astrojs/preact':
+ specifier: workspace:*
+ version: link:../../../../integrations/preact
+ '@astrojs/react':
+ specifier: workspace:*
+ version: link:../../../../integrations/react
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/e2e/fixtures/server-islands:
dependencies:
'@astrojs/mdx':