vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.93k stars 26.68k forks source link

Dot notation client component breaks consuming RSC. #51593

Open Thinkscape opened 1 year ago

Thinkscape commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: linux
      Arch: x64
      Version: #22 SMP Tue Jan 10 18:39:00 UTC 2023
    Binaries:
      Node: 16.17.0
      npm: 8.15.0
      Yarn: 1.22.19
      pnpm: 7.1.0
    Relevant packages:
      next: 13.4.7-canary.2
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 4.9.5

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue or a replay of the bug

https://codesandbox.io/p/sandbox/winter-bash-kvw9lz?file=%2Fapp%2FComponent.tsx

To Reproduce

Describe the Bug

Unhandled Runtime Error
Error: Unsupported Server Component type: undefined

Call Stack
attemptResolveElement
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1484:8)

or

node_modules/.pnpm/next@13.4.7-canary.2_@babel+core@7.22.5_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/webpack/loaders/next-flight-loader/module-proxy.js (51:14) 
@ Object.get
Error: Cannot access .Foo on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

Expected Behavior

Code compiles without fail.

Which browser are you using? (if relevant)

irrelevant

How are you deploying your application? (if relevant)

Vercel

chillardinho commented 1 year ago

have you tried export const A, export const B, then import * as C and using them as C.A, C.B?

Thinkscape commented 1 year ago

When I tried attaching the other components as props on a function (object), like so:


function Foo(){
  return <>foo</>;
}

function Component({ children }: { children: React.ReactNode }) {
  return children;
}

Component.Foo = Foo;

export { Component };

... I'm getting a new error:

node_modules/.pnpm/next@13.4.7-canary.2_@babel+core@7.22.5_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/webpack/loaders/next-flight-loader/module-proxy.js (51:14) @ Object.get
Error: Cannot access .Foo on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

So I'm assuming it's a known limitation 🤷

Thinkscape commented 1 year ago

have you tried export const A, export const B, then import * as C and using them as C.A, C.B?

This workaround would work on my own components, but wouldn't for 3rd party packages that use this style of composition...

PHILLIPS71 commented 1 year ago

I'm running into a similar issue with components using dot notation, it's a really convenient convention and disappointing that it's something that doesn't seem to be supported. I know other component libraries use a similar approach in constructing components such as https://github.com/react-bootstrap/react-bootstrap.

I'm also constructing components like this across my application, but am also running into the same issue that you're encountering.

'use client'

import NavigationBrand from '@/components/navigation/NavigationBrand'
import NavigationContent from '@/components/navigation/NavigationContent'
import NavigationItem from '@/components/navigation/NavigationItem'
import NavigationLink from '@/components/navigation/NavigationLink'
import NavigationPortal from '@/components/navigation/NavigationPortal'
import NavigationSegment from '@/components/navigation/NavigationSegment'

const Navigation = React.forwardRef<HTMLElement, NavigationProps>((props, ref) => {
  // omitted for breverity
})

Navigation.displayName = 'Navigation'

export default Object.assign(Navigation, {
  Brand: NavigationBrand,
  Content: NavigationContent,
  Item: NavigationItem,
  Link: NavigationLink,
  Portal: NavigationPortal,
  Segment: NavigationSegment,
})
const NavigationMobile: React.FC<NavigationProps> = (props) => (
  <Navigation orientation="horizontal" {...props}>
    <Navigation.Brand>
      <Image src="/images/logo.png" alt="ogo" height={40} width={128} priority />
    </Navigation.Brand>
  </Navigation>
)

It then produces the same issue below when used within the app directory

Error: Cannot access .Brand on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.
kiner-tang commented 1 year ago

I had the same problem with antd in app router mode and hope to get it resolved soon.

image image
li-jia-nan commented 1 year ago

Have the same problem.

emmgfx commented 1 year ago

I also have the same problem. I've think about updating some components removing the dot notation, but doesn't looks like a good solution.

Jared-Dahlke commented 1 year ago

i am having this issue as well

marceloverdijk commented 11 months ago

Is there any official feedback from the next.js team on this? The new App Router mechanism seem to have some open ends...

I'm now adding 'use client' to pages where I need dotted components (e.g. from react-bootstrap), but it doesn't feel good. Neither I'm sure what the impact is doing that for a SSG...

mryechkin commented 11 months ago

Echoing @marceloverdijk - I've had to do the same in my projects, and it would be great to hear an official stance on this pattern.

gaearon commented 11 months ago

I'm not on the Next.js team, but I'd say we don't generally consider this very idiomatic:

export default Object.assign(Navigation, {
  Brand: NavigationBrand,
  Content: NavigationContent,
  Item: NavigationItem,
  Link: NavigationLink,
  Portal: NavigationPortal,
  Segment: NavigationSegment,
})

I'd say the idiomatic way would be to express this with multiple exports. And then use import * as Navigation. But that indeed doesn't give you a pretty <Navigation> root item so I get why it's maybe a bit less pretty.

I think it would make sense for third party packages to move into that direction. That's better for tree shaking etc too. A temporary workaround would be a 'use client' file that re-exports these as named exports.

AhmedBaset commented 11 months ago

Thanks for the answer Dan @gaearon My cause is slightly different, this issue only occurs when I lazy load components with dynamic(). Is there a solution?

const WalletSummary = dynamic(() =>
  import("@/components/wallet-summary").then((mod) => mod.WalletSummary)
);

⨯ Internal error: Error: Cannot access WalletSummary.then on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

iankduffy commented 11 months ago

Thanks for the answer Dan @gaearon My cause is slightly different, this issue only occurs when I lazy load components with dynamic(). Is there a solution?

const WalletSummary = dynamic(() =>
  import("@/components/wallet-summary").then((mod) => mod.WalletSummary)
);

⨯ Internal error: Error: Cannot access WalletSummary.then on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

All have the same issue, before this, I had an object of components that would dynamically loaded and pick of it type.

import dynamic from "next/dynamic";

interface Props {
  [key: string]: unknown;
}

export const GenericPageComponents: Props = {
  test: () => <h1>test</h1>,
  hero: dynamic(() => import("./components").then((re) => re.Hero)),
  searchBar: dynamic(() => import("./components").then((res) => res.SearchBar)),
  contentRow: dynamic(() =>
    import("./components").then((res) => res.ContentRow)
  ),
};

I made a search bar component use client and now I get the same error:

Cannot access SearchBar.then on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.

ITenthusiasm commented 1 month ago

That's better for tree shaking etc too.

In situations where the expectation is that all of the "sub components" must be used, this point doesn't really hold up as valid. Just wanted to drop that in as a consideration.

Curious to know why the common export approach for dot notation components isn't idiomatic anymore as well. Previously, the React docs encouraged it. I get that now we're running into issues with RSCs, but that isn't sufficient justification for considering this approach to exports non-idiomatic. (Nor does it help anyone working with packages that can't/won't pivot in this way within a reasonable period of time.)