bastawhiz 5 days ago

And you just never have any handlers on the server components? The problem is that if a component is populated with data from the server, it's sending the data down as JSX. Which means that component can't react to interactivity of client components within it. Unless, of course, you draw the line further up and make more stuff client components.

Consider making a list of posts from some sort of feed. If each item in the list is a server component, you can't have the component representing the item be a server component if you need to handle any events in that item. So now you're limited to just making the list component itself a server component. Well what good is that?

The whole point of this is to move stuff off of the client. But it's not even clear that you're saving any bytes at all in this scenario, because if there's any props duplicated across items in the list, you've got to duplicate the data in the JSON: the shallower the returned JSX, the more raw data you send instead of JSX data. Which completely defats the point of going through all this trouble in the first place.

1
SebastianKra 5 days ago

You can...

...have a client component inside the post. For example, for each post, have a server component, that contains a <ClientDeleteButton postId={...} />.

...have a wrapper client component that takes a server components as a child. Eg. if you want to show a hover-card for each post:

    <ClientHoverCard preview={<Preview />}>
        <ServerPost />
    </ClientHoverCard>
https://nextjs.org/docs/app/building-your-application/render...

> props duplicated across items in the list, you've got to duplicate the data in the JSON

I'm pretty sure gzip would just compress that.

bastawhiz 4 days ago

> I'm pretty sure gzip would just compress that.

Bytes on the wire aren't nearly as important in this case. That value still has to be decompressed into a string and that string needs to be parsed into objects and that's all before you pump it into the renderer.

> have a wrapper client component that takes a server components as a child.

That doesn't work for the model defined in this post. Because now each post is a request to the server instead of one single request that returns a rendered list of posts. That's literally the point of doing this whole roundabout thing: to offload as much work as possible to the server.

> For example, for each post, have a server component, that contains a <ClientDeleteButton postId={...} />.

And now only the delete button reacts to being pressed. You can't remove the post from the page. You can't make the post semi transparent. You can't disable the other buttons on the post.

Without making a mess with contexts, state and interactivity can only happen in the client component islands.

And you know what? If you're building a page that's mostly static on a site that sees almost no code changes or deployments, this probably works great for certain cases. But it's far from an ideal practice for anything that's even mildly interactive.

Even just rendering the root of your render tree is problematic, because you probably want to show loading indicators and update the page title or whatever, and that means loading client code to load server code that runs more client code. At least with good old fashioned SSR, by the time code in the browser starts running, everything is already ready to be fully interactive.

SebastianKra 4 days ago

> That doesn't work for the model defined in this post. Because now each post is a request to the server instead of one single request that returns a rendered list of posts.

Thats where you’re wrong. The JSX snippet that I posted above gets turned into:

    { 
        type: "src/ClientHoverCard.js#ClientHoverCard", 
        props: { 
            preview: // this is already rendered on the server
            children: // this is already rendered on the server
        }
    }
If you wanted to fade the entire post when pressing the delete button without contexts, you’d create a client component like this:

    "use client"
    function DeletablePost({ children }: { children: ReactNode }) {
        const [isDeleted, setDeleted] = useState(false)
        return <div style={{ opacity: isDeleted ? 0.5 : 1 }}>
            {children}
            <DeleteButton onChange={setDeleted} />
        </div>
    }
And pass it a server component like this:

    <DeletablePost>
        <ServerPost />
    </DeletablePoast>