From d161bcfdbea27a16b156e31725437f52eef21d93 Mon Sep 17 00:00:00 2001
From: Manuel Schiller <manuel.schiller@caligano.de>
Date: Thu, 25 Dec 2025 18:39:16 +0100
Subject: [PATCH 1/3] fix: createIsomorphicFn compilation

fixes #6217
fixes #6206
---
 .../start-fn-stubs/src/createIsomorphicFn.ts  |  7 ++-
 .../tests/createIsomorphicFn.test-d.ts        |  3 -
 .../src/start-compiler-plugin/compiler.ts     | 63 ++++++++++++++-----
 .../handleCreateIsomorphicFn.ts               | 16 -----
 .../createIsomorphicFn.test.ts                | 34 +---------
 .../snapshots/client/call-at-module-level.ts  |  5 ++
 .../client/createIsomorphicFnDestructured.tsx |  3 +-
 .../createIsomorphicFnDestructuredRename.tsx  |  3 +-
 .../client/createIsomorphicFnFactory.tsx      |  4 +-
 .../client/createIsomorphicFnStarImport.tsx   |  3 +-
 .../snapshots/server/call-at-module-level.ts  |  5 ++
 .../server/createIsomorphicFnDestructured.tsx |  3 +-
 .../createIsomorphicFnDestructuredRename.tsx  |  3 +-
 .../server/createIsomorphicFnFactory.tsx      |  4 +-
 .../server/createIsomorphicFnStarImport.tsx   |  3 +-
 .../test-files/call-at-module-level.ts        | 13 ++++
 16 files changed, 93 insertions(+), 79 deletions(-)
 create mode 100644 packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
 create mode 100644 packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
 create mode 100644 packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts

diff --git a/packages/start-fn-stubs/src/createIsomorphicFn.ts b/packages/start-fn-stubs/src/createIsomorphicFn.ts
index 09a1be9ec1..df6590c6e0 100644
--- a/packages/start-fn-stubs/src/createIsomorphicFn.ts
+++ b/packages/start-fn-stubs/src/createIsomorphicFn.ts
@@ -21,7 +21,7 @@ export interface ClientOnlyFn<TArgs extends Array<any>, TClient>
   ) => IsomorphicFn<TArgs, TServer, TClient>
 }
 
-export interface IsomorphicFnBase extends IsomorphicFn {
+export interface IsomorphicFnBase {
   server: <TArgs extends Array<any>, TServer>(
     serverImpl: (...args: TArgs) => TServer,
   ) => ServerOnlyFn<TArgs, TServer>
@@ -34,8 +34,9 @@ export interface IsomorphicFnBase extends IsomorphicFn {
 // if we use `createIsomorphicFn` in this library itself, vite tries to execute it before the transformer runs
 // therefore we must return a dummy function that allows calling `server` and `client` method chains.
 export function createIsomorphicFn(): IsomorphicFnBase {
-  return {
+  const fn = () => undefined
+  return Object.assign(fn, {
     server: () => ({ client: () => () => {} }),
     client: () => ({ server: () => () => {} }),
-  } as any
+  }) as any
 }
diff --git a/packages/start-fn-stubs/tests/createIsomorphicFn.test-d.ts b/packages/start-fn-stubs/tests/createIsomorphicFn.test-d.ts
index 7c6c3b040a..bb75a6311b 100644
--- a/packages/start-fn-stubs/tests/createIsomorphicFn.test-d.ts
+++ b/packages/start-fn-stubs/tests/createIsomorphicFn.test-d.ts
@@ -4,9 +4,6 @@ import { createIsomorphicFn } from '../src/createIsomorphicFn'
 test('createIsomorphicFn with no implementations', () => {
   const fn = createIsomorphicFn()
 
-  expectTypeOf(fn).toBeCallableWith()
-  expectTypeOf(fn).returns.toBeUndefined()
-
   expectTypeOf(fn).toHaveProperty('server')
   expectTypeOf(fn).toHaveProperty('client')
 })
diff --git a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
index 70ae1be911..65d10f8339 100644
--- a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
+++ b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
@@ -52,10 +52,6 @@ export type LookupKind =
 type MethodChainSetup = {
   type: 'methodChain'
   candidateCallIdentifier: Set<string>
-  // If true, a call to the root function (e.g., createIsomorphicFn()) is also a candidate
-  // even without chained method calls. This is used for IsomorphicFn which can be
-  // called without .client() or .server() (resulting in a no-op function).
-  allowRootAsCandidate?: boolean
 }
 type DirectCallSetup = { type: 'directCall' }
 type JSXSetup = { type: 'jsx'; componentName: string }
@@ -75,7 +71,6 @@ const LookupSetup: Record<
   IsomorphicFn: {
     type: 'methodChain',
     candidateCallIdentifier: new Set(['server', 'client']),
-    allowRootAsCandidate: true, // createIsomorphicFn() alone is valid (returns no-op)
   },
   ServerOnlyFn: { type: 'directCall' },
   ClientOnlyFn: { type: 'directCall' },
@@ -179,14 +174,13 @@ for (const [kind, setup] of Object.entries(LookupSetup) as Array<
   }
 }
 
-// Known factory function names for direct call and root-as-candidate patterns
+// Known factory function names for direct call patterns
 // These are the names that, when called directly, create a new function.
 // Used to filter nested candidates - we only want to include actual factory calls,
 // not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)
 const DirectCallFactoryNames = new Set([
   'createServerOnlyFn',
   'createClientOnlyFn',
-  'createIsomorphicFn',
 ])
 
 export type LookupConfig = {
@@ -205,16 +199,12 @@ interface ModuleInfo {
 
 /**
  * Computes whether any file kinds need direct-call candidate detection.
- * This includes both directCall types (ServerOnlyFn, ClientOnlyFn) and
- * allowRootAsCandidate types (IsomorphicFn).
+ * This applies to directCall types (ServerOnlyFn, ClientOnlyFn).
  */
 function needsDirectCallDetection(kinds: Set<LookupKind>): boolean {
   for (const kind of kinds) {
     const setup = LookupSetup[kind]
-    if (
-      setup.type === 'directCall' ||
-      (setup.type === 'methodChain' && setup.allowRootAsCandidate)
-    ) {
+    if (setup.type === 'directCall') {
       return true
     }
   }
@@ -1165,6 +1155,28 @@ export class StartCompiler {
     return resolvedKind
   }
 
+  /**
+   * Checks if an identifier is a direct import from a known factory library.
+   * Returns true for imports like `import { createServerOnlyFn } from '@tanstack/react-start'`
+   * or renamed imports like `import { createServerOnlyFn as myFn } from '...'`.
+   * Returns false for local variables that hold the result of calling a factory.
+   */
+  private async isKnownFactoryImport(
+    identName: string,
+    fileId: string,
+  ): Promise<boolean> {
+    const info = await this.getModuleInfo(fileId)
+    const binding = info.bindings.get(identName)
+
+    if (!binding || binding.type !== 'import') {
+      return false
+    }
+
+    // Check if it's imported from a known library
+    const knownExports = this.knownRootImports.get(binding.source)
+    return knownExports !== undefined && knownExports.has(binding.importedName)
+  }
+
   private async resolveExprKind(
     expr: t.Expression | null,
     fileId: string,
@@ -1197,9 +1209,28 @@ export class StartCompiler {
       if (calleeKind === 'Root' || calleeKind === 'Builder') {
         return 'Builder'
       }
-      // Use direct Set.has() instead of iterating
-      if (this.validLookupKinds.has(calleeKind as LookupKind)) {
-        return calleeKind
+      // For method chain patterns (callee is MemberExpression like .server() or .client()),
+      // return the resolved kind if valid
+      if (t.isMemberExpression(expr.callee)) {
+        if (this.validLookupKinds.has(calleeKind as LookupKind)) {
+          return calleeKind
+        }
+      }
+      // For direct calls (callee is Identifier), only return the kind if the
+      // callee is a direct import from a known library (e.g., createServerOnlyFn).
+      // Calling a local variable that holds an already-built function (e.g., myServerOnlyFn())
+      // should NOT be treated as a transformation candidate.
+      if (t.isIdentifier(expr.callee)) {
+        const isFactoryImport = await this.isKnownFactoryImport(
+          expr.callee.name,
+          fileId,
+        )
+        if (
+          isFactoryImport &&
+          this.validLookupKinds.has(calleeKind as LookupKind)
+        ) {
+          return calleeKind
+        }
       }
     } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {
       result = await this.resolveCalleeKind(expr.object, fileId, visited)
diff --git a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateIsomorphicFn.ts b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateIsomorphicFn.ts
index 83b7a69dde..8331a00849 100644
--- a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateIsomorphicFn.ts
+++ b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateIsomorphicFn.ts
@@ -18,22 +18,6 @@ export function handleCreateIsomorphicFn(
     const envCallInfo =
       context.env === 'client' ? methodChain.client : methodChain.server
 
-    // Check if we have any implementation at all
-    if (!methodChain.client && !methodChain.server) {
-      // No implementations provided - warn and replace with no-op
-      const variableId = path.parentPath.isVariableDeclarator()
-        ? path.parentPath.node.id
-        : null
-      console.warn(
-        'createIsomorphicFn called without a client or server implementation!',
-        'This will result in a no-op function.',
-        'Variable name:',
-        t.isIdentifier(variableId) ? variableId.name : 'unknown',
-      )
-      path.replaceWith(t.arrowFunctionExpression([], t.blockStatement([])))
-      continue
-    }
-
     if (!envCallInfo) {
       // No implementation for this environment - replace with no-op
       path.replaceWith(t.arrowFunctionExpression([], t.blockStatement([])))
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/createIsomorphicFn.test.ts b/packages/start-plugin-core/tests/createIsomorphicFn/createIsomorphicFn.test.ts
index c161b80586..ca29207145 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/createIsomorphicFn.test.ts
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/createIsomorphicFn.test.ts
@@ -1,6 +1,6 @@
 import { readFile, readdir } from 'node:fs/promises'
 import path from 'node:path'
-import { afterAll, describe, expect, test, vi } from 'vitest'
+import { describe, expect, test } from 'vitest'
 
 import { StartCompiler } from '../../src/start-compiler-plugin/compiler'
 
@@ -50,22 +50,6 @@ async function getFilenames() {
 }
 
 describe('createIsomorphicFn compiles correctly', async () => {
-  const noImplWarning =
-    'createIsomorphicFn called without a client or server implementation!'
-
-  const originalConsoleWarn = console.warn
-  const consoleSpy = vi.spyOn(console, 'warn').mockImplementation((...args) => {
-    // we want to avoid sending this warning to the console, we know about it
-    if (args[0] === noImplWarning) {
-      return
-    }
-    originalConsoleWarn(...args)
-  })
-
-  afterAll(() => {
-    consoleSpy.mockRestore()
-  })
-
   const filenames = await getFilenames()
 
   describe.each(filenames)('should handle "%s"', async (filename) => {
@@ -111,20 +95,4 @@ describe('createIsomorphicFn compiles correctly', async () => {
       }),
     ).rejects.toThrowError()
   })
-
-  test('should warn to console if no implementations provided', async () => {
-    await compile({
-      env: 'client',
-      code: `
-      import { createIsomorphicFn } from '@tanstack/react-start'
-      const noImpl = createIsomorphicFn()`,
-      id: 'no-fn.ts',
-    })
-    expect(consoleSpy).toHaveBeenCalledWith(
-      noImplWarning,
-      'This will result in a no-op function.',
-      'Variable name:',
-      'noImpl',
-    )
-  })
 })
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
new file mode 100644
index 0000000000..5591995c32
--- /dev/null
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
@@ -0,0 +1,5 @@
+const getEnvironment = () => {
+  console.log("[CLIENT] getEnvironment called");
+  return "client";
+};
+const moduleLevel = getEnvironment();
\ No newline at end of file
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx
index 87eb2bba0e..956fe05f98 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructured.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import { createIsomorphicFn } from '@tanstack/react-start';
+const noImpl = createIsomorphicFn();
 const serverOnlyFn = () => {};
 const clientOnlyFn = () => 'client';
 const serverThenClientFn = () => 'client';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructuredRename.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructuredRename.tsx
index 87eb2bba0e..5e167daeb6 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructuredRename.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnDestructuredRename.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import { createIsomorphicFn as isomorphicFn } from '@tanstack/react-start';
+const noImpl = isomorphicFn();
 const serverOnlyFn = () => {};
 const clientOnlyFn = () => 'client';
 const serverThenClientFn = () => 'client';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnFactory.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnFactory.tsx
index 803bb6d801..b54ad664b1 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnFactory.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnFactory.tsx
@@ -1,3 +1,5 @@
+import { createIsomorphicFn } from '@tanstack/react-start';
+
 // Isomorphic function factory - returns a createIsomorphicFn with .client() and .server() calls
 export function createPlatformFn(platform: string) {
   return () => `client-${platform}`;
@@ -18,7 +20,7 @@ export const createClientImplFn = (name: string) => {
 
 // Factory returning no-implementation isomorphic fn
 export function createNoImplFn() {
-  return () => {};
+  return createIsomorphicFn();
 }
 
 // Top-level isomorphic fn for comparison
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnStarImport.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnStarImport.tsx
index 87eb2bba0e..070f59d901 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnStarImport.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/createIsomorphicFnStarImport.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import * as TanStackStart from '@tanstack/react-start';
+const noImpl = TanStackStart.createIsomorphicFn();
 const serverOnlyFn = () => {};
 const clientOnlyFn = () => 'client';
 const serverThenClientFn = () => 'client';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
new file mode 100644
index 0000000000..9747f2db02
--- /dev/null
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
@@ -0,0 +1,5 @@
+const getEnvironment = () => {
+  console.log("[SERVER] getEnvironment called");
+  return "server";
+};
+const moduleLevel = getEnvironment();
\ No newline at end of file
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
index f0b7d12af4..195cee12a6 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructured.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import { createIsomorphicFn } from '@tanstack/react-start';
+const noImpl = createIsomorphicFn();
 const serverOnlyFn = () => 'server';
 const clientOnlyFn = () => {};
 const serverThenClientFn = () => 'server';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructuredRename.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructuredRename.tsx
index f0b7d12af4..c50c274dd6 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructuredRename.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnDestructuredRename.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import { createIsomorphicFn as isomorphicFn } from '@tanstack/react-start';
+const noImpl = isomorphicFn();
 const serverOnlyFn = () => 'server';
 const clientOnlyFn = () => {};
 const serverThenClientFn = () => 'server';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnFactory.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnFactory.tsx
index ee949e0c38..cce206bc8b 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnFactory.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnFactory.tsx
@@ -1,3 +1,5 @@
+import { createIsomorphicFn } from '@tanstack/react-start';
+
 // Isomorphic function factory - returns a createIsomorphicFn with .client() and .server() calls
 export function createPlatformFn(platform: string) {
   return () => `server-${platform}`;
@@ -18,7 +20,7 @@ export const createClientImplFn = (name: string) => {
 
 // Factory returning no-implementation isomorphic fn
 export function createNoImplFn() {
-  return () => {};
+  return createIsomorphicFn();
 }
 
 // Top-level isomorphic fn for comparison
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnStarImport.tsx b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnStarImport.tsx
index f0b7d12af4..65e4fc18b1 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnStarImport.tsx
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/createIsomorphicFnStarImport.tsx
@@ -1,4 +1,5 @@
-const noImpl = () => {};
+import * as TanStackStart from '@tanstack/react-start';
+const noImpl = TanStackStart.createIsomorphicFn();
 const serverOnlyFn = () => 'server';
 const clientOnlyFn = () => {};
 const serverThenClientFn = () => 'server';
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts
new file mode 100644
index 0000000000..dc0835eff8
--- /dev/null
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts
@@ -0,0 +1,13 @@
+import { createIsomorphicFn } from '@tanstack/react-start'
+
+const getEnvironment = createIsomorphicFn()
+  .server(() => {
+    console.log("[SERVER] getEnvironment called");
+    return "server";
+  })
+  .client(() => {
+    console.log("[CLIENT] getEnvironment called");
+    return "client";
+  });
+
+const moduleLevel = getEnvironment();
\ No newline at end of file

From d405d7c012fbd41ffcb195312e8bdbea73e358e5 Mon Sep 17 00:00:00 2001
From: Manuel Schiller <manuel.schiller@caligano.de>
Date: Thu, 25 Dec 2025 18:58:57 +0100
Subject: [PATCH 2/3] simplify compiler

---
 .../src/start-compiler-plugin/compiler.ts     | 175 +++++++-----------
 .../server/clientOnlyNotFromTanstack.tsx      |  10 +-
 2 files changed, 73 insertions(+), 112 deletions(-)

diff --git a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
index 65d10f8339..f51bff2e8c 100644
--- a/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
+++ b/packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
@@ -33,11 +33,6 @@ type Binding =
       resolvedKind?: Kind
     }
 
-type ExportEntry =
-  | { tag: 'Normal'; name: string }
-  | { tag: 'Default'; name: string }
-  | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`
-
 type Kind = 'None' | `Root` | `Builder` | LookupKind
 
 export type LookupKind =
@@ -53,7 +48,11 @@ type MethodChainSetup = {
   type: 'methodChain'
   candidateCallIdentifier: Set<string>
 }
-type DirectCallSetup = { type: 'directCall' }
+type DirectCallSetup = {
+  type: 'directCall'
+  // The factory function name used to create this kind (e.g., 'createServerOnlyFn')
+  factoryName: string
+}
 type JSXSetup = { type: 'jsx'; componentName: string }
 
 const LookupSetup: Record<
@@ -72,8 +71,8 @@ const LookupSetup: Record<
     type: 'methodChain',
     candidateCallIdentifier: new Set(['server', 'client']),
   },
-  ServerOnlyFn: { type: 'directCall' },
-  ClientOnlyFn: { type: 'directCall' },
+  ServerOnlyFn: { type: 'directCall', factoryName: 'createServerOnlyFn' },
+  ClientOnlyFn: { type: 'directCall', factoryName: 'createClientOnlyFn' },
   ClientOnlyJSX: { type: 'jsx', componentName: 'ClientOnly' },
 }
 
@@ -134,6 +133,9 @@ const KindHandlers: Record<
   // ClientOnlyJSX is handled separately via JSX traversal, not here
 }
 
+// All lookup kinds as an array for iteration with proper typing
+const AllLookupKinds = Object.keys(LookupSetup) as Array<LookupKind>
+
 /**
  * Detects which LookupKinds are present in the code using string matching.
  * This is a fast pre-scan before AST parsing to limit the work done during compilation.
@@ -145,10 +147,8 @@ export function detectKindsInCode(
   const detected = new Set<LookupKind>()
   const validForEnv = LookupKindsPerEnv[env]
 
-  for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<
-    [LookupKind, RegExp]
-  >) {
-    if (validForEnv.has(kind) && pattern.test(code)) {
+  for (const kind of AllLookupKinds) {
+    if (validForEnv.has(kind) && KindDetectionPatterns[kind].test(code)) {
       detected.add(kind)
     }
   }
@@ -159,9 +159,8 @@ export function detectKindsInCode(
 // Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)
 // Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)
 const IdentifierToKinds = new Map<string, Set<LookupKind>>()
-for (const [kind, setup] of Object.entries(LookupSetup) as Array<
-  [LookupKind, MethodChainSetup | DirectCallSetup]
->) {
+for (const kind of AllLookupKinds) {
+  const setup = LookupSetup[kind]
   if (setup.type === 'methodChain') {
     for (const id of setup.candidateCallIdentifier) {
       let kinds = IdentifierToKinds.get(id)
@@ -174,14 +173,16 @@ for (const [kind, setup] of Object.entries(LookupSetup) as Array<
   }
 }
 
-// Known factory function names for direct call patterns
-// These are the names that, when called directly, create a new function.
+// Factory function names for direct call patterns.
 // Used to filter nested candidates - we only want to include actual factory calls,
 // not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)
-const DirectCallFactoryNames = new Set([
-  'createServerOnlyFn',
-  'createClientOnlyFn',
-])
+const DirectCallFactoryNames = new Set<string>()
+for (const kind of AllLookupKinds) {
+  const setup = LookupSetup[kind]
+  if (setup.type === 'directCall') {
+    DirectCallFactoryNames.add(setup.factoryName)
+  }
+}
 
 export type LookupConfig = {
   libName: string
@@ -192,7 +193,8 @@ export type LookupConfig = {
 interface ModuleInfo {
   id: string
   bindings: Map<string, Binding>
-  exports: Map<string, ExportEntry>
+  // Maps exported name → local binding name
+  exports: Map<string, string>
   // Track `export * from './module'` declarations for re-export resolution
   reExportAllSources: Array<string>
 }
@@ -203,8 +205,7 @@ interface ModuleInfo {
  */
 function needsDirectCallDetection(kinds: Set<LookupKind>): boolean {
   for (const kind of kinds) {
-    const setup = LookupSetup[kind]
-    if (setup.type === 'directCall') {
+    if (LookupSetup[kind].type === 'directCall') {
       return true
     }
   }
@@ -226,28 +227,13 @@ function areAllKindsTopLevelOnly(kinds: Set<LookupKind>): boolean {
  */
 function needsJSXDetection(kinds: Set<LookupKind>): boolean {
   for (const kind of kinds) {
-    const setup = LookupSetup[kind]
-    if (setup.type === 'jsx') {
+    if (LookupSetup[kind].type === 'jsx') {
       return true
     }
   }
   return false
 }
 
-/**
- * Gets the set of JSX component names to detect.
- */
-function getJSXComponentNames(kinds: Set<LookupKind>): Set<string> {
-  const names = new Set<string>()
-  for (const kind of kinds) {
-    const setup = LookupSetup[kind]
-    if (setup.type === 'jsx') {
-      names.add(setup.componentName)
-    }
-  }
-  return names
-}
-
 /**
  * Checks if a CallExpression is a direct-call candidate for NESTED detection.
  * Returns true if the callee is a known factory function name.
@@ -271,7 +257,7 @@ function isNestedDirectCallCandidate(node: t.CallExpression): boolean {
  * Checks if a CallExpression path is a top-level direct-call candidate.
  * Top-level means the call is the init of a VariableDeclarator at program level.
  * We accept any simple identifier call or namespace call at top level
- * (e.g., `isomorphicFn()`, `TanStackStart.createServerOnlyFn()`) and let
+ * (e.g., `createServerOnlyFn()`, `TanStackStart.createServerOnlyFn()`) and let
  * resolution verify it. This handles renamed imports.
  */
 function isTopLevelDirectCallCandidate(
@@ -320,6 +306,9 @@ export class StartCompiler {
   private entryIdToFunctionId = new Map<string, string>()
   private functionIds = new Set<string>()
 
+  // Cached root path with trailing slash for dev mode function ID generation
+  private _rootWithTrailingSlash: string | undefined
+
   constructor(
     private options: {
       env: 'client' | 'server'
@@ -376,12 +365,9 @@ export class StartCompiler {
   }): string {
     if (this.mode === 'dev') {
       // In dev, encode the file path and export name for direct lookup
-      const rootWithTrailingSlash = this.options.root.endsWith('/')
-        ? this.options.root
-        : `${this.options.root}/`
       let file = opts.extractedFilename
-      if (opts.extractedFilename.startsWith(rootWithTrailingSlash)) {
-        file = opts.extractedFilename.slice(rootWithTrailingSlash.length)
+      if (opts.extractedFilename.startsWith(this.rootWithTrailingSlash)) {
+        file = opts.extractedFilename.slice(this.rootWithTrailingSlash.length)
       }
       file = `/@id/${file}`
 
@@ -424,6 +410,15 @@ export class StartCompiler {
     return this.options.mode ?? 'dev'
   }
 
+  private get rootWithTrailingSlash(): string {
+    if (this._rootWithTrailingSlash === undefined) {
+      this._rootWithTrailingSlash = this.options.root.endsWith('/')
+        ? this.options.root
+        : `${this.options.root}/`
+    }
+    return this._rootWithTrailingSlash
+  }
+
   private async resolveIdCached(id: string, importer?: string) {
     if (this.mode === 'dev') {
       return this.options.resolveId(id, importer)
@@ -498,15 +493,8 @@ export class StartCompiler {
           this.moduleCache.set(libId, rootModule)
         }
 
-        rootModule.exports.set(config.rootExport, {
-          tag: 'Normal',
-          name: config.rootExport,
-        })
-        rootModule.exports.set('*', {
-          tag: 'Namespace',
-          name: config.rootExport,
-          targetId: libId,
-        })
+        rootModule.exports.set(config.rootExport, config.rootExport)
+        rootModule.exports.set('*', config.rootExport)
         rootModule.bindings.set(config.rootExport, {
           type: 'var',
           init: null, // Not needed since resolvedKind is set
@@ -528,7 +516,7 @@ export class StartCompiler {
     id: string,
   ): ModuleInfo {
     const bindings = new Map<string, Binding>()
-    const exports = new Map<string, ExportEntry>()
+    const exports = new Map<string, string>()
     const reExportAllSources: Array<string> = []
 
     // we are only interested in top-level bindings, hence we don't traverse the AST
@@ -571,7 +559,7 @@ export class StartCompiler {
           if (t.isVariableDeclaration(node.declaration)) {
             for (const d of node.declaration.declarations) {
               if (t.isIdentifier(d.id)) {
-                exports.set(d.id.name, { tag: 'Normal', name: d.id.name })
+                exports.set(d.id.name, d.id.name)
                 bindings.set(d.id.name, { type: 'var', init: d.init ?? null })
               }
             }
@@ -579,11 +567,7 @@ export class StartCompiler {
         }
         for (const sp of node.specifiers) {
           if (t.isExportNamespaceSpecifier(sp)) {
-            exports.set(sp.exported.name, {
-              tag: 'Namespace',
-              name: sp.exported.name,
-              targetId: node.source?.value || '',
-            })
+            exports.set(sp.exported.name, sp.exported.name)
           }
           // export { local as exported }
           else if (t.isExportSpecifier(sp)) {
@@ -591,7 +575,7 @@ export class StartCompiler {
             const exported = t.isIdentifier(sp.exported)
               ? sp.exported.name
               : sp.exported.value
-            exports.set(exported, { tag: 'Normal', name: local })
+            exports.set(exported, local)
 
             // When re-exporting from another module (export { foo } from './module'),
             // create an import binding so the server function can be resolved
@@ -607,11 +591,11 @@ export class StartCompiler {
       } else if (t.isExportDefaultDeclaration(node)) {
         const d = node.declaration
         if (t.isIdentifier(d)) {
-          exports.set('default', { tag: 'Default', name: d.name })
+          exports.set('default', d.name)
         } else {
           const synth = '__default_export__'
           bindings.set(synth, { type: 'var', init: d as t.Expression })
-          exports.set('default', { tag: 'Default', name: synth })
+          exports.set('default', synth)
         }
       } else if (t.isExportAllDeclaration(node)) {
         // Handle `export * from './module'` syntax
@@ -691,10 +675,6 @@ export class StartCompiler {
     // JSX candidates (e.g., <ClientOnly>)
     const jsxCandidatePaths: Array<babel.NodePath<t.JSXElement>> = []
     const checkJSX = needsJSXDetection(fileKinds)
-    // Get target component names from JSX setup (e.g., 'ClientOnly')
-    const jsxTargetComponentNames = checkJSX
-      ? getJSXComponentNames(fileKinds)
-      : null
     // Get module info that was just cached by ingestModule
     const moduleInfo = this.moduleCache.get(id)!
 
@@ -801,10 +781,10 @@ export class StartCompiler {
           }
         },
         // Pattern 3: JSX element pattern (e.g., <ClientOnly>)
-        // Collect JSX elements where the component name matches a known import
-        // that resolves to a target component (e.g., ClientOnly from @tanstack/react-router)
+        // Collect JSX elements where the component is imported from a known package
+        // and resolves to a JSX kind (e.g., ClientOnly from @tanstack/react-router)
         JSXElement: (path) => {
-          if (!checkJSX || !jsxTargetComponentNames) return
+          if (!checkJSX) return
 
           const openingElement = path.node.openingElement
           const nameNode = openingElement.name
@@ -815,13 +795,18 @@ export class StartCompiler {
           const componentName = nameNode.name
           const binding = moduleInfo.bindings.get(componentName)
 
-          // Must be an import binding
+          // Must be an import binding from a known package
           if (!binding || binding.type !== 'import') return
 
-          // Check if the original import name matches a target component
-          if (jsxTargetComponentNames.has(binding.importedName)) {
-            jsxCandidatePaths.push(path)
-          }
+          // Verify the import source is a known TanStack router package
+          const knownExports = this.knownRootImports.get(binding.source)
+          if (!knownExports) return
+
+          // Verify the imported name resolves to a JSX kind (e.g., ClientOnlyJSX)
+          const kind = knownExports.get(binding.importedName)
+          if (kind !== 'ClientOnlyJSX') return
+
+          jsxCandidatePaths.push(path)
         },
       })
     }
@@ -951,25 +936,8 @@ export class StartCompiler {
     }
 
     // Handle JSX candidates (e.g., <ClientOnly>)
-    // Note: We only reach here on the server (ClientOnlyJSX is only in LookupKindsPerEnv.server)
-    // Verify import source using knownRootImports (same as function call resolution)
+    // Validation was already done during traversal - just call the handler
     for (const jsxPath of jsxCandidatePaths) {
-      const openingElement = jsxPath.node.openingElement
-      const nameNode = openingElement.name
-      if (!t.isJSXIdentifier(nameNode)) continue
-
-      const componentName = nameNode.name
-      const binding = moduleInfo.bindings.get(componentName)
-      if (!binding || binding.type !== 'import') continue
-
-      // Verify the import source is a known TanStack router package
-      const knownExports = this.knownRootImports.get(binding.source)
-      if (!knownExports) continue
-
-      // Verify the imported name resolves to ClientOnlyJSX kind
-      const kind = knownExports.get(binding.importedName)
-      if (kind !== 'ClientOnlyJSX') continue
-
       handleClientOnlyJSX(jsxPath, { env: 'server' })
     }
 
@@ -1039,9 +1007,9 @@ export class StartCompiler {
     visitedModules.add(moduleInfo.id)
 
     // First check direct exports
-    const directExport = moduleInfo.exports.get(exportName)
-    if (directExport) {
-      const binding = moduleInfo.bindings.get(directExport.name)
+    const localBindingName = moduleInfo.exports.get(exportName)
+    if (localBindingName) {
+      const binding = moduleInfo.bindings.get(localBindingName)
       if (binding) {
         const result = { moduleInfo, binding }
         // Cache the result (build mode only)
@@ -1305,11 +1273,12 @@ export class StartCompiler {
           )
           if (targetModuleId) {
             const targetModule = await this.getModuleInfo(targetModuleId)
-            const exportEntry = targetModule.exports.get(callee.property.name)
-            if (exportEntry) {
-              const exportedBinding = targetModule.bindings.get(
-                exportEntry.name,
-              )
+            const localBindingName = targetModule.exports.get(
+              callee.property.name,
+            )
+            if (localBindingName) {
+              const exportedBinding =
+                targetModule.bindings.get(localBindingName)
               if (exportedBinding) {
                 return await this.resolveBindingKind(
                   exportedBinding,
diff --git a/packages/start-plugin-core/tests/clientOnlyJSX/snapshots/server/clientOnlyNotFromTanstack.tsx b/packages/start-plugin-core/tests/clientOnlyJSX/snapshots/server/clientOnlyNotFromTanstack.tsx
index f91fe2854c..b35c36ab1b 100644
--- a/packages/start-plugin-core/tests/clientOnlyJSX/snapshots/server/clientOnlyNotFromTanstack.tsx
+++ b/packages/start-plugin-core/tests/clientOnlyJSX/snapshots/server/clientOnlyNotFromTanstack.tsx
@@ -1,9 +1 @@
-// This should NOT be transformed because ClientOnly is not from @tanstack/*-router
-import { ClientOnly } from 'some-other-package';
-export function MyComponent() {
-  return <div>
-      <ClientOnly fallback={<div>Loading...</div>}>
-        <div>This should remain as-is</div>
-      </ClientOnly>
-    </div>;
-}
\ No newline at end of file
+no-transform
\ No newline at end of file

From c7a8a75698c3ecfa5d1a0db5b4ebd1dee066ab41 Mon Sep 17 00:00:00 2001
From: Manuel Schiller <manuel.schiller@caligano.de>
Date: Thu, 25 Dec 2025 19:05:05 +0100
Subject: [PATCH 3/3] formatting

---
 .../snapshots/client/call-at-module-level.ts         |  4 ++--
 .../snapshots/server/call-at-module-level.ts         |  4 ++--
 .../test-files/call-at-module-level.ts               | 12 ++++++------
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
index 5591995c32..efc23cf31b 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/client/call-at-module-level.ts
@@ -1,5 +1,5 @@
 const getEnvironment = () => {
-  console.log("[CLIENT] getEnvironment called");
-  return "client";
+  console.log('[CLIENT] getEnvironment called');
+  return 'client';
 };
 const moduleLevel = getEnvironment();
\ No newline at end of file
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
index 9747f2db02..2fa99b5686 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/snapshots/server/call-at-module-level.ts
@@ -1,5 +1,5 @@
 const getEnvironment = () => {
-  console.log("[SERVER] getEnvironment called");
-  return "server";
+  console.log('[SERVER] getEnvironment called');
+  return 'server';
 };
 const moduleLevel = getEnvironment();
\ No newline at end of file
diff --git a/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts b/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts
index dc0835eff8..ee017d7f59 100644
--- a/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts
+++ b/packages/start-plugin-core/tests/createIsomorphicFn/test-files/call-at-module-level.ts
@@ -2,12 +2,12 @@ import { createIsomorphicFn } from '@tanstack/react-start'
 
 const getEnvironment = createIsomorphicFn()
   .server(() => {
-    console.log("[SERVER] getEnvironment called");
-    return "server";
+    console.log('[SERVER] getEnvironment called')
+    return 'server'
   })
   .client(() => {
-    console.log("[CLIENT] getEnvironment called");
-    return "client";
-  });
+    console.log('[CLIENT] getEnvironment called')
+    return 'client'
+  })
 
-const moduleLevel = getEnvironment();
\ No newline at end of file
+const moduleLevel = getEnvironment()
