Next.js 13 Server and Client Components Confusion
Asked Answered
N

4

13

I have a server component InitView;

const InitView = () => {
    useEffect(() => { });
    return (
        <>
            <Hero/>
            <span className="text-xl font-normal text-gray-100">Now, how do you want to play?</span>
            <GameModeMenu/>
        </>
    );
}

export default InitView;

Also I have one more server component View;

interface ViewProps {
    children?: React.ReactNode;
}

const View = ({children}:ViewProps) => {
    return (
        <main className="home w-screen h-screen flex flex-col gap-10 justify-start items-center bg-neutral-900 px-8 py-10">
            {children}
        </main>
    );
}

export default View;

And here is my page.tsx

export default function Page() {
  return (
    <View>
        <InitView/>
    </View>
  )
}

When I tried to import the InitView inside the View component with pass-child method it throws an error;

You're importing a component that needs useEffect. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

I'm completely okay with this error since I'm trying to use an effect inside a server component. However here is the thing , if I change my codes to this;

Page.tsx

export default function Page() {
  return (
    <View/>
  )
}

View.tsx

"use client";

const View = () => {
    return (
        <main className="home w-screen h-screen flex flex-col gap-10 justify-start items-center bg-neutral-900 px-8 py-10">
            <InitView/>
        </main>
    );
}

export default View;

The error is gone now. To clarify;

I can use an effect inside my InitView component without any "use client" markings since I directly imported it in View(marked as client) component.

I'm assuming every directly imported (server or client) components inside client components, will be client components, as the previous error says none of its parents are marked with "use client", so they're Server Components by default.

Have you guys any ideas? Am I wrong or correct?

P.S. The documentation says I can not import server components inside client components but as clearly can be seen, I can. I'm highly confused.

Neptunian answered 24/3, 2023 at 8:9 Comment(0)
O
9

As per my understanding, we can't import Server Components inside Client Components only when the Server Components contain any server only code (for instance, a database calling or using component level async/await ).

In your case InitView don't have any server-specific code, so importing it inside a Client Component, may infer it to Client Component.

You can find it on Next.js' beta docs - Link

Otalgia answered 24/3, 2023 at 8:51 Comment(1)
Thanks for your answer. I made the InitView async and fetched some JSON data in it. Situation went as you guessed. I could not imported it inside View component directly. I had to pass it as child of the View like <View><InitView/></View>. They should really emphasize this subtle confusion in their docs.Neptunian
P
17

This question already has enough explanations, but I will try to clarify some things a bit.

TL:DR: you need that 'use client' directive to use client-only features. Once the component became client-side, its nested components are client-side too. No way to change or intercept this behavior.

Some important details to clarify:

  1. You can use server-side stuff in server-sided components only. For example, direct DB queries.
  2. You can use client-side stuff in client-sided components only. For example, React hooks (useEffect, useRouter and so).
  3. Nextjs 13 use Server Components by default, which I personally think is wrong and causes additional troubles and confusion for most projects.
  4. Child components inherit the "side" of execution. For example, for your 3 components hierarchy page.jsx=>PageMain.jsx=>SomeModal.jsx all 3 are server-sided by default.
  5. If you need some client-side stuff inside the PageMain.jsx component (useEffect, useState, useRouter, page transitions, client-side libs...), you need to mark the component as client: 'use client'. Now PageMain.jsx will render on the client side. And its children will render on client side, too. And its grandchildren will. You got it)))
  6. Once a component became client-sided, it's no way to make any of its children server-only again.
  7. No way to break the rule #6. You may have encountered the following directives: 'server-only' and 'client-only'. These doesn't allow you make a component server-only again. These are just for the errors generation. see the docs here
  8. Technically client-sided components are pre-rendered on the server side too, you can try Nextjs 12 - that version is much more straightforward. "server-sided", "server-only" components are only usable when you need literally no client javascript reactivity. For example, static text with images. No JS animations, no React Context, no React hooks, no DOM manipulation, no window usage on the server!.
  9. You still can fetch any server-side component from the server (like Suspense or dynamic examples from docs). But don't trick yourself. This works more like get-request with 'component' response type. That component will be inserted inside your client-side tree. This is close to the situation "fetch some raw html, then insert it inside your components tree with with dangerouslySetInnerHTML". For sure, in this case your server data is rendered on the server first, and thus can use DB queries. It can use some other server components, but again, no client functionality on the server.

I have many projects, and none of them can be made at least 50% server-only. I personally wouldn't hope that "server-only" components can magically solve any optimization problems or speedup any normal site like Medium.com or Reddit significantly.

I hope that my deep explanations and various examples will help somebody.

Prehuman answered 10/9, 2023 at 19:15 Comment(6)
You actually can use server components in client components and they will act as server component. But they need to be passed as prop. nextjs.org/docs/app/building-your-application/rendering/…Seiler
@MartinHetfieldMali Read my #9. This is what you talk of.Prehuman
I think #6 should be clarified. "Once a component became client-sided, it's no way to make any of its children server-only again." I don't know about the Pages router, but in App router this is a very common pattern. See docs nextjs.org/docs/app/building-your-application/rendering/…Winnie
@Winnie You cannot make DB requests inside "server" component which is inside a client one. All you can do is fetch "result" of server component and hydrate it, that technically works nearly as #9.Prehuman
@VictorGorban I think one of us is misunderstanding something here, and it could be me. My understanding though is children components that are imported into a client component cannot be server components, but if they are passed as props then they can be. This is the composition pattern in the link I referenced earlier. On page load, children server components are fully rendered server-side, including DB requests, since they are true server components after all. Additional reference: nextjs.org/docs/app/building-your-application/rendering/…Winnie
@Winnie that concept is two-sided. Let us leave this conversation now, those who needed already has enough information about this.Prehuman
O
9

As per my understanding, we can't import Server Components inside Client Components only when the Server Components contain any server only code (for instance, a database calling or using component level async/await ).

In your case InitView don't have any server-specific code, so importing it inside a Client Component, may infer it to Client Component.

You can find it on Next.js' beta docs - Link

Otalgia answered 24/3, 2023 at 8:51 Comment(1)
Thanks for your answer. I made the InitView async and fetched some JSON data in it. Situation went as you guessed. I could not imported it inside View component directly. I had to pass it as child of the View like <View><InitView/></View>. They should really emphasize this subtle confusion in their docs.Neptunian
A
3

All the component's are Server Components by default, if you don't mention the

*'use client';*

marker on the top of the component. In order to use reacts client side hooks you need to mention the marker on the top for Next.js to know if it is client or server-side component.

Archaeo answered 27/5, 2023 at 14:41 Comment(0)
G
0

useEffect() is a client-side function. As in, it executes on the browser. So if you use that in your component, that component, or its parent component, has to have the "use client" directive on it. Now, combine that with the fact, that all components are by default server components.

In your case initview, view, and page are all server components. So, when you bring page->view->initview together, there is useEffect function in initview, but no component in the chain with "use client". Therefore, you get that error.

As soon as you put "use client" in view, one of the components in the chain has the "use client" directive, the error goes away. However, by marking the view components with "use client" directive, view becomes a client component and then we cannot import the initview component into it, because by default initview is a server components. Server components can be passed into the client component as children as you noted above or as server actions.

Grandpa answered 28/7, 2023 at 0:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.