This is WAY better than NextJS (TanStack Server Components)

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

Transcript

00:00:00React server components. Love them or hate them. Seems mostly hate these days but that
00:00:04might be about to change as TanStack has entered the game. That's right we now have TanStack
00:00:08server components and they've taken quite a different approach from Next.js. Let's take a look.
00:00:13Now I'll start with a paragraph from their announcement post that I think will put a
00:00:21lot of people at ease. It says "Most people now think of React server components in a server-first
00:00:26way. The server owns the tree, useClient marks the interactive parts, and the framework conventions
00:00:31decide how the whole thing fits together. This turns React server components from a useful
00:00:35primitive into a thing your whole app has to orbit. We do not think you should have to buy
00:00:40into that whole model up front just to get the value out of React server components."
00:00:45Essentially what they're saying is they don't want to go down the Next.js route where it's
00:00:48server components by default and then you need the useClient directive where you want to have
00:00:52interactivity. Instead TanStack wants to think of it as what if you could use React server components
00:00:57as granularly as you could fetch JSON on the client. With that goal in mind then let's take a look at
00:01:01how they actually implemented server components because spoiler alert I really like the way they
00:01:06did it. What I have here is a normal TanStack star application so everything at the moment is going to
00:01:10be a client component and the only thing I've done is some small install steps that you need to get
00:01:15server components running which is essentially just installing some packages and modifying your
00:01:18vconfig. This is what the page currently looks like we have our greeting component here which
00:01:22should see currently a client component and in the code it literally is just a single React component
00:01:27then down here we have a normal TanStack route and we're using the greeting component here. Now let's
00:01:32say in our greeting component you wanted to do some logic on the server. In my case I want to get the
00:01:36operating system hostname then also some environment variables that are only available to the server
00:01:40just to show you that this is actually running there. At the moment if I try and use os.hostname
00:01:45it's not going to work since this is a node function and it's not available in the browser.
00:01:49What we need to do then is take our greeting component and render this on the server and the
00:01:53first step to doing that in TanStack star is with a simple server function. As you can see I've got
00:01:58one here called get greeting and all we're doing inside of here is using the new render server
00:02:01component function putting our component inside of it and returning the renderable server component
00:02:06that we get back. You can think of this as simple as we're creating a get request for our component.
00:02:10Next then all we need to do is simply fetch the component from the server function that we created
00:02:14and we can do that inside of a loader on a route here so we're just awaiting get greeting and then
00:02:18returning that as well and it's still a renderable server component. Then we can use that inside of
00:02:23our route here by using use loader data and we just use the component down here like this. With that we
00:02:27now have our first TanStack server component. You can see os.hostname is now working and it's also
00:02:32pulling in environment variables that are only available on the server. Now the thing that I
00:02:36absolutely love about this implementation is if you notice the only new thing in here is the render
00:02:41server component function. The rest of this was just normal TanStack start. You could replace this
00:02:46with just some simple JSON data being returned and you'd fetch it in the exact same way. I also think
00:02:51this implementation is just really explicit about where your code is actually running. You do it all
00:02:55inside of a server function so it's pretty clear that's going to be run on the server and also
00:02:59inside of the render server component. In fact I think I can improve my demo code here by simply
00:03:03taking the functions that we're running up here that I want to run on the server putting them
00:03:07inside of the server function and then simply pass that values into my greeting component as props so
00:03:12now my greeting component is essentially just a bare component that can actually be used on the
00:03:16client or the server. I'm running all of the logic that I want to run on the server inside of my
00:03:21server function here. Again it's pretty explicit that's going to be running on the server. This
00:03:25literally feels like the exact opposite of the logic used in Next.js and I absolutely love it.
00:03:30It means that I can think of React in a normal way it's all client first then adding server components
00:03:34on top when I want them. But what if I wanted to use a client component inside of my server
00:03:38component so nested within the tree. Say I wanted to add on a counter button here that each time we
00:03:43click it just adds to the counter. Well if I simply try and add this into my server component here with
00:03:47an on click and then also a use state call you can see it completely breaks. It says use state is not
00:03:52a function or its return value is not iterable and that's because greeting is being used as a server
00:03:56component. We can't use this client functionality. To fix this you have two options and the second
00:04:01option is definitely the best one to use but the first option is going to feel familiar to those
00:04:05of you that have used Next.js. We can simply move that logic into its own component and then use the
00:04:10use client directive. This works in tan stack start server components we can simply just use the
00:04:14component inside of the server component now and as you can see it is all working nicely. The downside
00:04:18of this approach though is that we now have a server component controlling the rendering of a
00:04:22client component and this can start to get a little bit messy aka if I wanted to find out where my
00:04:28counter component was in the tree I would go to my route here and see that we have a greeting server
00:04:32component then go back up to the greeting server component and inside of the server component see
00:04:37that we have a client component and this mess can really start to add up and just make that boundary
00:04:42of server and client become a little bit unclear. Tan stack wouldn't settle for that though. They
00:04:47asked themselves what if the server did not need to decide every client shaped part of the UI at all
00:04:51and this led them to create something entirely new - composite components. To use one the first
00:04:56thing I'll do is remove our client component from our server component and I'll simply replace it
00:05:00with any children of our greeting component. Next we also need to change what we're returning from
00:05:05our server function here. Instead of returning a renderable server component we need to return
00:05:09what's called a composite source. To do that we can use our second tan stack server component helper
00:05:14function - create composite component. In here we're essentially just building out a component
00:05:18where props here are considered the slots. I'm just using a very simple children slot so it's going to
00:05:22pass through anything that we put as a child of my composite component into this props.children which
00:05:27I'm passing through to the greeting component that we just had. With that again what we need to do is
00:05:31simply fetch our composite component from our server function and we do that in the exact same way in
00:05:36the loader here. You see I'm just renaming source to greeting then I'm loading it with the use loader
00:05:41data. The only difference here though is we can't use this as a component. You see it's throwing an
00:05:45error here. To actually render a composite component we need to use the composite component helper
00:05:49component that we get from tan stack server components and as the source prop we pass
00:05:53through the composite component that we fetch from our server function that we created earlier.
00:05:57With that my server component is now rendering in like we had before and I'm also passing through
00:06:01the counter as children of this composite component and that is getting passed to the greeting where we
00:06:05had it set up here so it's passing it through into the props.children so everything is now working
00:06:10nicely. I can also go into my counter component and remove the directive that we had here since that is
00:06:14no longer needed since it knows this is going to be a client component as it's inside of a composite
00:06:18component. This is being used as a slot. Now it might seem like we got the exact same result there
00:06:23but with more work than just using the use client directive but the power is really coming from the
00:06:27developer experience and it's a bit of a switch from the use client model. Instead of having our
00:06:32server decide where our client components render in like when we had the counter component inside
00:06:36of the server component itself instead what we're doing with composite components is saying hey
00:06:40there's going to be a slot here we're going to render in a client component but the server
00:06:44component itself has no idea what that's going to be. We add that in later in our client code
00:06:48so we handle all client-based components within client code itself. That's only the beginning too
00:06:53if we take a look at a more complex page like this post one where this post is being server rendered
00:06:58there are two issues that I want to solve. The first one is that I want to add in some actions like
00:07:03liking the post and following the author but I want to add them in above the title here and I want to
00:07:08use a client component. At the moment I'm simply using this children slot pattern that means if I
00:07:12added in my post actions down here it would just go where the comments are since this is how we have
00:07:17the component set up so I want some way to tell my server component where to put specific client
00:07:22components. Then we have a second issue of if I do have a follow author button. At the moment this
00:07:27post page actually has no idea who the post author is. We actually offloaded all of that logic into
00:07:32the server component itself. If I wanted to get the author in a client component down here I would
00:07:37actually have to fetch the JSON for the post and get the author that way and that's not really a great
00:07:42pattern we'd be double fetching the data. Lucky for us then tanstack actually has two other slot types
00:07:46that we can use on a composite component besides this children one and the first one is going to
00:07:50be render props. This is essentially just any prop that is a function that returns a react element so
00:07:56this can be called anything it doesn't have to be called render actions and here I'm just saying what
00:07:59data I want the server component to pass through and that is going to be the post id and author id.
00:08:04Now all we need to do in our composite component is simply use this function that we're passing
00:08:08through as a prop wherever you want the component that's eventually going to be rendered to be.
00:08:12In my case I want that to be underneath the card header so I can call it with props dot render
00:08:16actions we can use an optional so if it's not passed through it doesn't break it just won't
00:08:20render in then we can also pass through the information that we want from the server component
00:08:24to our client component. After this our composite component is going to accept the render actions
00:08:28prop that we just created and for the value we simply pass through a function which has a post
00:08:32id and author id as the argument which the server is going to fill in then we simply render in our
00:08:36post actions client component and we can pass that data through as the props. So now I have a button
00:08:41up here where I can like and copy the link to the post and also click on follow author here where
00:08:45it's aware of the author name despite the fact that I've actually never fetched it on this page.
00:08:49I only fetch it in the server component and the server component is passing that data into the
00:08:53client component for me. Now you might think this breaks the logic that we had before where we said
00:08:57we don't want any server components to be in charge of rendering client ones but it doesn't and that's
00:09:01because slots are actually opaque. The server component up here has no idea what's inside of
00:09:06this it just knows that something goes here and that needs to pass through these values which in
00:09:10this case is the post id and author id. This function doesn't run on the server instead the
00:09:15server simply sees that it needs to pass data through and then down in our client this is when
00:09:19the function is actually run and the component is rendered in. The exact same thing also applies to
00:09:23our third slot type which is component props this one is actually a little more simple than the
00:09:28render props all we're doing is instead of having a function which then returns our client component
00:09:33we're just passing through the client component as a prop itself then on our composite component
00:09:38definition up here we're saying that we want to accept a prop which is a react component that has
00:09:42the props of post id and author id then we can use this within the component itself. You can think of
00:09:47component props like a placeholder the server component knows there is going to be a component
00:09:51there that needs some data in our case the post id and author id but doesn't really care what that
00:09:56component is as long as it accepts those props so i changed my post actions component down here to
00:10:01another one i've made called fake post actions and then we save that you can see that this is
00:10:05still going to render in because it's the client that's responsible for rendering this component
00:10:10it's only the server that provides the data looking at the documentation it doesn't seem like there's
00:10:14any real difference in which approach you take whether you go with component props or render props
00:10:18it might just come down to preference the only difference that i can see is that maybe you want
00:10:22to modify the data that you get from the server so in this case we can do whatever we want with
00:10:26post id and author id since it's just a function and then we can pass that on to our component
00:10:31whereas if you're using component props you just pass through the component itself and the server
00:10:36handles passing through the props now that's the basics of tan stack server components but there's
00:10:40still so much more to like for example if you wanted most of your page to be server rendered
00:10:44maybe you have a header component content component and a footer one and you want them all server
00:10:49rendered you don't have to bundle them all into one render server component function you can actually
00:10:53use promise.all split them out into three different functions and then simply return them as an object
00:10:58from a single server function but what if one of those components takes long to load that would
00:11:03mean the entire server function would and therefore the entire page would well don't worry there either
00:11:07what we can actually do is instead of awaiting the render server component function we can actually
00:11:12return the promise that it creates and then on the client we can take advantage of the use hook and
00:11:16suspense boundaries to load in skeletons so server components will just load in when they're ready
00:11:21i just really like the approach that tan stack has taken here it doesn't feel intrusive i'm not forced
00:11:25to adopt it and i can adopt it without any weird workarounds plus when i do actually go to use it
00:11:31the server components themselves are actually only three new functions the rest of it is just simple
00:11:36tan stack start server functions something that i was already using and it's as simple as fetching
00:11:41data this also means that it integrates nicely with tools like tan stack query something i will
00:11:45definitely be doing and it also makes things like caching simpler if you wanted to you could literally
00:11:49just cache the response of the get request on your cdn i'm definitely going to be exploring these more
00:11:54so let me know in the comments below what you think of them and if you'd like to see more videos on them
00:11:59well yeah subscribe and as always see you in the next one

Key Takeaway

TanStack Server Components offer a non-intrusive alternative to the Next.js model by treating server-rendered components as granular, data-fetching functions rather than a mandatory, app-wide structural requirement.

Highlights

TanStack Server Components utilize a granular, function-based approach that does not force an entire application to orbit a server-first architecture.

The 'renderServerComponent' function enables executing Node-specific logic like os.hostname or accessing secure environment variables within TanStack Start components.

Composite components allow developers to define UI slots in server components, enabling client components to be injected later without the server needing to manage the client-side tree.

Render props and component props provide two distinct methods for passing server-derived data (e.g., post ID, author ID) into client components without needing redundant data fetching.

TanStack Server Components integrate with React's 'use' hook and Suspense boundaries to allow independent, asynchronous loading of specific page sections.

Timeline

TanStack Server Components Philosophy

  • React server components are implemented here as granular primitives instead of a mandatory, server-first architecture.
  • The framework avoids the Next.js model where the server owns the entire tree by default.

The approach prioritizes developer flexibility, treating server components as granular tools similar to fetching JSON on the client. It rejects the requirement that an entire application must orbit a server-first model up front.

Implementing Basic Server Components

  • Rendering logic on the server requires using the 'renderServerComponent' function inside a server function.
  • Server-side execution allows access to node-specific functions like os.hostname and private environment variables.
  • Component logic is made explicit by keeping it within standard TanStack Start server functions.

By moving logic into a server function and rendering the component there, code becomes explicitly server-side. This setup returns a renderable server component that is awaited in the route loader and passed to the client via 'useLoaderData'.

Client Interaction and Composite Components

  • Attempting to use client-side hooks like 'useState' directly in a server component causes runtime errors.
  • Composite components allow the server to define slots for client-side content without needing to know the implementation details of the client component.
  • The 'createCompositeComponent' function replaces the need for the 'useClient' directive in nested structures.

While traditional 'useClient' components work, they can create messy boundaries between server and client. Composite components solve this by treating the server component as a shell with opaque slots, where client components are injected and rendered entirely in the client runtime.

Advanced Data Passing with Slots

  • Render props allow the server to pass dynamic data like post IDs to client components without double-fetching.
  • Component props act as placeholders, allowing the server to define what data a component needs without dictating which specific component is rendered.
  • Slots are opaque, meaning the server passes values through without executing client-side logic.

To address complex UI requirements, developers use render props or component props to bridge the server-client gap. These patterns ensure the server provides necessary data while keeping the client responsible for rendering the interactive UI elements.

Performance and Integration

  • Multiple components can be rendered in parallel using 'Promise.all' within a single server function.
  • Independent components can leverage React Suspense boundaries to load asynchronously when using the 'use' hook.
  • The pattern integrates naturally with TanStack Query and standard caching strategies.

The system supports high performance by allowing components to load independently via Suspense. Because the implementation relies on standard TanStack Start server functions, it integrates seamlessly with existing data-fetching tools and caching infrastructure.

Community Posts

No posts yet. Be the first to write about this video!

Write about this video