Next.js

Fix Unsupported Server Component type: undefined

Keep the page on the server while repairing Container.Window: direct named imports work, and changing to a default export alone does not fix this case.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

For the compound-component case behind Unsupported Server Component type: undefined, stop reading Container.Window from the Server Component. Import Window directly and render it inside Container, or move that compound JSX into a small client wrapper. Keep the page on the server. Changing a correctly matched named import to a default import is not a general fix.

The original Stack Overflow report has a precise shape: a server page imports a client Container, and that module assigns Window to a property on the container function. The reporter says a separate import works. That distinction matters more than the word “undefined”: the imported component exists, but the server cannot use its attached subcomponent as though it held the ordinary client-side JavaScript function.

In a native Windows reproduction on Node.js 26.7.0, Next.js 15.5.23 and React 19.2.4, the same shape failed with a different diagnostic:

text
 ⨯ [Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.] {

The tests below used an isolated development server and HTTP requests. They did not test browser hydration or a production build. The old error is the search term from the incident; the longer message is the output observed in this reproduction.

Why the attached subcomponent disappears at the boundary#

A compound component is an API convenience: the parent function has another component assigned to one of its properties. Ordinary client code can read that property. But a Server Component importing a client module does not receive the same function object with all of its runtime assignments.

The version-tagged Next.js webpack loader shows the relevant transformation. For an ES module client boundary, it creates registered client references for the module's exports. It handles both named and default exports. An assignment to Container.Window is not a separately exported component, so that assignment does not give the server a Window export to render.

This explains a useful diagnostic split. Rendering the imported Container is allowed. Looking up a custom property on that reference is the operation that changes the example. The component implementations can be perfectly valid, and the import spelling can be correct, while the compound expression still fails.

The general boundary is documented in React's use-client reference: a client entry point marks a module and its dependencies for client evaluation. This is about the module graph. It is not a rule that all JSX nested under a client component must be written in a client file.

Fix the compound expression without moving the page#

1. Identify the property access in the server file#

These are the small component modules used in the failing reproduction. They deliberately do no data fetching, authentication or browser work, so the property access is isolated from those other concerns.

jsx
// components/Window.jsx
'use client'
 
export function Window({ children }) {
  return <section>{children}</section>
}
jsx
// components/Container.jsx
'use client'
 
import { Window } from './Window'
 
export function Container({ children }) {
  return <div>{children}</div>
}
 
Container.Window = Window

The failing page uses the attached property. In an existing application, look for the equivalent expression on a dialog, menu, accordion or other compound API; the component names themselves are not significant.

jsx
// app/bad/page.jsx
import { Container } from '../../components/Container'
 
export default function Page() {
  return (
    <Container>
      <Container.Window>
        <h1>COMPOUND_OK</h1>
      </Container.Window>
    </Container>
  )
}

Do not start by adding a client directive to every parent. First locate the exact component expression being evaluated in the server file. The outer container and the inner window are separate element types even though the JSX makes them look like one grouped API.

2. Import the child as an exported component#

Keep both component modules unchanged and replace the server page with a direct import of Window. This is the single change between the failing and passing composition: the server now renders the exported component reference instead of looking up an attached property.

jsx
// app/fixed/page.jsx
import { Container } from '../../components/Container'
import { Window } from '../../components/Window'
 
export default function Page() {
  return (
    <Container>
      <Window>
        <h1>DIRECT_OK</h1>
      </Window>
    </Container>
  )
}

This page has no client directive, and both imports remain named imports. The request succeeded in the reproduction. There is no need to delete the property assignment for other callers that genuinely use the compound API inside client code.

When using a library, check its documented individual exports before copying this pattern. Do not invent a package subpath merely because its component is called Window. If the package only exposes the compound API, use the client wrapper below instead of assuming an internal file is a supported import.

3. Keep the compound API inside a small client wrapper#

This alternative moves the property access across the boundary. It is useful when the library's intended API is the compound form, or when the group also needs client state. Leave the original component modules in place and add:

jsx
// components/Composed.jsx
'use client'
 
import { Container } from './Container'
 
export function Composed({ children }) {
  return (
    <Container>
      <Container.Window>{children}</Container.Window>
    </Container>
  )
}

The server page imports the wrapper itself, without reading its properties:

jsx
// app/wrapper/page.jsx
import { Composed } from '../../components/Composed'
 
export default function Page() {
  return (
    <Composed>
      <h1>WRAPPER_OK</h1>
    </Composed>
  )
}

That route also succeeded. Passing the content as children keeps the composition explicit: the wrapper imports the interactive shell, while the page supplies the content. The Next.js composition documentation describes passing server-rendered UI through children. Avoid importing server-only data access into the wrapper just to preserve a convenient JSX shape.

Verify the fix against the failed case#

The native reproduction kept the component modules identical for the failing and direct-import routes. It then compared a default-export variant and the client-wrapper alternative:

Page compositionHTTP resultMeaning
Server page reads the named container's attached window500Original failure reproduced with the newer diagnostic
Server page imports the named window directly200Direct export reference resolves
Server page reads the default container's attached window500Default-export conversion alone does not repair the boundary
Server page renders a client wrapper containing the compound JSX200Property access stays inside the client module graph

In your application, repeat the request that failed and inspect the server log as well as the rendered content. A page source containing the heading text is insufficient: the failed reproduction response also contained that text in its streamed payload. The request still failed. Check that the intended component actually renders without the server exception, then test its interactive behaviour in a browser.

If the error changes to functions cannot be passed directly to Client Components, inspect the props you moved across the boundary. A render callback or event handler created in the server page is a different problem from an attached component property. Keep those callbacks with the client wrapper where appropriate; do not label a rendering callback as server work just to silence the error.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Named exports, missing boundaries and restarts#

The source discussion includes suggestions to switch export styles or restart the development environment. These are not evidence that named Client Component exports are unsupported. The direct named import passed here, and the default-container negative control failed while retaining the attached property access.

An actual import mismatch is still worth checking. React's import/export guide distinguishes default imports from named imports and requires the syntax to match the module's export. If the compiler names an absent export, follow the TypeScript exported-member diagnostic rather than treating it as a compound-component boundary failure. Inspect the module being imported, including a re-export file if one sits between the page and the component.

Removing the client directive is appropriate only when the module can genuinely remain on the server. The minimal fixtures above have no interactive behaviour, but a real dialog may use state or effects. Removing its boundary can simply replace this failure with the component needs useState error. Preserve the interactive entry point and change the composition instead.

A restart can help you confirm what the development server is currently compiling after edits, but it does not change the import boundary in the code shown here. Keep the failed and repaired compositions small enough to compare. The useful correction is either a directly imported subcomponent or a client-owned compound expression; both let the surrounding page retain its server responsibilities.

Further reading: React Hook Form: Fix Confirm Password Validation. Also see Fix "changing an uncontrolled input to be controlled".

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote