Shipping Reusable Full-Stack Components

Fran Zekan
Fran Zekan

In this talk, Fran Zekan dives into how React Server Components (RSCs) and server actions unlock the ability to ship full-stack, composable components—components that encapsulate not just UI, but also data fetching and server logic. Drawing from patterns in Rails Engines and WordPress plugins, Fran explores how React’s evolving architecture makes it possible to publish and reuse self-contained components that truly work out of the box.

Share this talk

Transcript

So, most of you were probably not expecting me. You were expecting the little bro from Miami to show up and talk about open auth. Thank you. This works? Yeah. Okay. So we're probably expecting him. And most of you saw my talk yesterday, so we're gonna do something else. It's gonna be open auth stuff.

I tried to recreate recreate his slides. Not sure how I used that, but okay, that's the description. So open auth. Right? It's an auth provider. You it's universal, self hosted, blah blah blah. Doesn't matter. It's an auth provider, but it's open. Do you want your auth data open? I prefer mine closed, so you should probably use this.

They do have great shirts. Shirts are amazing, but not the greatest security team. So you might wanna do something else. In my projects, I usually use something better, better shout, so probably use that. That's it with odd puns.

Going back to my talk, so as some of you already heard yesterday, I'm gonna be talking about how our RSCs are gonna allow us to ship full stack components, not just the view part. Just quickly about me, even though it was already said, I'm an engineer from Croatia.

And as they already said, I'm a triathlete, which means I legally need to add that into every conversation to tell you that I do Ironmans. That's me doing an Ironman. And I work at Unity, which is an OZ company, so that's kinda meta in this case, but doesn't matter. It's not the auth in the dev sense.

It's an SSO for sports clubs. So unless you're that, you're probably not my target audience, so it doesn't matter. Moving on. I said shippable full stack components. What does that mean? Let's start with shippable and components.

I think we all remember doing this, where we were just bundling things, kind of putting them everywhere, then it didn't work because we fucked up the order of imports or something. And then, like, everything broke. JQuery plug in broke something. JQuery 3.1 or 1.4 or whatever.

But then Facebook introduced something that kinda changed how we do front end, a component. They used to look like this, then we realized that, like, I don't like the syntax with h's. Let's actually use JSX. Let's not do classes or whatever. So it kind of morphed into this, but it's still kind of the same thing.

We've been doing this for a while. It's been amazing, but that is just the client part. Right? How do we do server fetching? How do we add other interactivity that requires a server here? Yeah. So, full stack. What do I mean by that?

I mean that you can add server fetching and server actions, like updating a post, liking a post, or whatever, right in the component. Let's first see what the rest of the ecosystem is doing. I don't know if anyone here is a Rails or a Django dev, but if you are, you probably know Django apps or Rails engines.

The idea of that is that if this is my Rails app, it should not be dark. I have two images. It's loading the wrong one. Doesn't matter. This is my Rails app. I can just mount engines into it. They're just small Rails apps. They have the controller that runs all the server code.

They have templates, JavaScript, CSS, whatever I want. I can just package it, put it there. Right? Amazing. You probably heard of this. It powers more than 45% of the web. It's WordPress. They have a really cool thing.

You just search for a plug in, click install, and with one click, you just got server parts, templates, CSS, JavaScript, whatever. Cool. So the ecosystem kinda has these, like mini apps, full stack components, whatever. But can we do this in React? I don't know.

A wise man said, you can just do things, so we're just gonna do it. This is gonna be my starting point. So this is a simple remix example. It's no longer remix. It's React router. I don't know. Hey. I wrote this.

It has a server loader, so we call something on the server to fetch some data, then we load it into the component, render it. They can also have interactive parts, like this a form that will send some data onto the server, and we can define the action that will handle this. This is amazing.

Like this pattern of writing everything in one file, everything here just works great. The problem is, how do we share this? What if you have some functionality that you want to share between different apps? Or maybe publish it to NPM so other people can use it? Probably like this.

Like, you just publish a thing that re exports loaders and actions. And if you saw Zach's talk, he actually showed that. So we can do this. This is actually an okay solution. The problem starts when you need to override things. So if you're just exporting everything, great.

What if you want to have your own custom UI on top of that? You can kind of do that and then re render the component inside, but what if it already had a layout or whatever, and it kind of breaks? But it fully breaks when you want to override the loader. How do you merge them?

Do you like run them in async, or do you just run for the first one, then wait for the other one? How do you do all of this? It's like, not really ideal. What if you wanna add your own action on top of this? How do you override it? How do you add auth to this? Whatever.

It's just not the most composable pattern. It's great if it's on one page, but if you wanna compose it and share it with others, canna starts breaking down. I think it's just an abstraction problem that is solved with React components and React, React server components and React server actions.

So this is the exact same functionality just written inside of the Next. Js app router. You just import a component, pass the I. D. It loads the thing. The component just looks like this. It's the same thing, just all in one component. You have loading, then you have rendering of that.

You have the form to send some data to the server, and you can just define an action. You can add your own custom loading states. You can add more content around it. You can even add your own loading or actions without overriding the global load or action, whatever your Remix app or Next JS app already had. Cool.

You can even do that in remix, kinda, in their alpha, kinda, but it's not out yet. So when it's out, it's gonna look something like this. You just render the thing on the server in the loader and pass it to the client. Just that simple. Cool. But is this already available? Like, Next.

Js had RSCs for a while. Like, can I already use this somewhere? Actually, yeah. There's a great package called Notion RSC. You just import it, call it with your API key, and and then just call the component. Just render it in React like you would any other component.

What this does is on the server, use your API key, fetches the thing from the Notion's API, then renders the thing. So it has the view layer. It has the CSS. You can even add interactivity inside of it like custom classes or whatever.

And since this is an RFC, it can actually return it without hydrating, which is a really cool thing if you're specifically rendering notion posts. If you have a huge post, it's going to be way faster because then your client side doesn't have to hydrate, and it works better on lower end devices. Cool. Cool. Cool.

But these were just like a to do example in React. And, like, can we get further? Like, is there a real world example? Glad you asked. Unless you're using the Skater Boys upload app, you're probably uploading to s three.

So let's take a look at how that would work with, like, React server components and actions and packaging, all of that. So you've probably written code like this.

Like, you have an input, then on change, you just generate the pre signed URL with your private key, and I hope no one is generating and passing the private key to the client. And if you are, please stop. Don't do that, and just run that on the server. So you probably just do something like this.

You call an API endpoint that just generates the URL, and then you use it further. And the API endpoint is just simple. It's just some AWS code that generates it and returns the URL to you. This is great, but how they publish this to MPM? I can just publish that to MPM, and it would, in theory, work.

There's just one problem. I hard coded this path, and if you look at that, that means that I need to have my users of the component also set up this endpoint. What if they have a Next. Js middleware that is acting up and, like, avoiding that route or blocking it?

Or what if, like, you want to have more components? Or what if another component is using that? Or what if they already have an endpoint that does that? So, yeah, we need a way to somehow package that server part in a shippable way inside of our component. And that's where server actions come from or server functions.

At some point, React theme last year renamed them from server actions to server functions to allow for data fetching as well and not just actions that a user did via post. The problem is no one shipped this yet, so it's not really there. But the spec is there, but, like, no one is kind of using it.

It's and I hate naming. So when I say action, I mean function. When I say function, I mean action. It's the same thing. So the example would look the same. You have an upload component you just imported, and the upload component looks like this. You have an input, and then you just call a function.

But how do we generate an API endpoint from this? Just a bunch a bunch of bundle magic.

So whenever you have a file that says, use server at the top, what Webpack in Next JS or RS pack or whatever packaging thing you're using sees this, it says, oh, all of the functions that are exported from here will be turned into API endpoints, and I'm automatically gonna add all of the URLs and everything for you.

I don't have to touch anything. I just mount the upload component, and it just works. Any other examples could be, for example, you could have a Stripe checkout start button, because for that, you need your private API key, which I hope nobody's embedding in their React apps, and just keeping that on the server.

So you can also call that. Or maybe you work at Shopify and wanna have a button that, like, loads, loads the current stock. And if there are things in stock, it, like, shows you, like, add to cart button. But also you want to add a bunch of CSS to animate that button up and down.

You can just package all of that into one server component. Your user just mounts it, and it just works. Or you know those cool MPM weekly download or, like, GitHub star badges that you see everywhere? They're great because these two things have public APIs.

But what if you're targeting, like, a stocks API that has a private API key or a weather API, and you just want to have a component that's like generic and you can put anywhere? You can just use our receipts for that.

You just put it there, runs on the server, API key is never exposed unless you do something wrong, and it just works. I already mentioned at the start, like embedded apps. It's a really, really common pattern in Rails and also in Django, where you can just have an engine that does something.

So for example, at work, we have a thing called Blazor. It just mounts a small dashboard that can directly run queries against your database. It's super useful because then sales teams and CSM teams can just open slash Blazor. If they're logged in, they get access to it.

They can just run a query to see how many new users in the last two days or something. No need to, like, sync to BigQuery or Looker Studio or write things outside. Just works directly with your database. Or maybe maintenance task. It's a great gem from Shopify that does the same thing.

You just write a bunch of functions on your server. It exposes them in a simple dashboard, then you just go. If you're logged in as admin on production, you can just click run this action. You ever wrote, like, an import script that, like, takes a CSV and import something?

Or, like, you you mess up your data, and you need, like, a migration script that, like, you copy the database URL and run it locally even though you know you shouldn't. And, like, I've seen people doing endpoints to do that.

Like, they would, for example, make an endpoint, call it slash API slash please don't call this, then deploy that to production and just load that in the browser, and it would trigger a function in the production environment. I hope no one is doing that. And if you are, please stop. It's not safe.

Or if you are, just, like, please use it with an API key or an authentication or something like that. Also, embedding full pages is a really cool example.

Like, there is a thing in Ruby called Sidekick or an equivalent in Node, the bull and queue, which are just background processing jobs, which have really good monitoring panels that you can see how many jobs are currently in the queue, you can clear the queue, pause the jobs, or whatever.

For that, both of those provide really good dashboards, which you need to host somewhere. If they were just a React, like, React server component, found them on a page, that's it. No overriding with the middleware or whatever. It just works. Cool. So I showed you all of the things you could do, but why would you do this?

The greatest thing I think comes from this is the DX that React originally provided. The whole point of React were components that are composable, and you can tie them in however you want to and do whatever you want with them.

If you just re export loaders and actually you can't really compose that easily, you can, but it's just hacky. And this gives you the DX that React originally provided.

Another thing is that since it's RSC based, you get all of the new cool things with, like, less hydration, smaller bundles, streaming SSR, and all of the cool optimization things that you can't really do without it. The other problem that we have here is that currently it's Next. Js only.

So if you like your React remixed or tanned, or God forbid, don't tell React team just an SPA with Vite, you can't use this right now, because there are no implementations outside of the Next. Js app router.

So So if you want to publish a package like this, and all you do is use Next in your company, perfect. Otherwise, there are problems with this. The other thing I mentioned are server functions. They're a great idea. Just create an endpoint that you can call perfect.

The problem is that not only are they not exposed, so you can't have, like, for example, your Shopify sorry, your Stripe component can't expose a webhook that's static and that then you can hit. TensorFlow's router can kinda do that with functions, but it's not there in Next, and that's the only implementation.

Plus, the get implementation in Next also isn't there. Also currently, since Next is the only thing implementing this, there are no lazy RSCs, which we saw with Remix is really cool because you can just put an RSC anywhere you want. With Next, it needs to originate from a top level RSC, blah blah blah.

So a bunch of people actually said, oh, I really don't like RSCs. Then I asked them why, and they're like, oh, it needs to work this, this, this, and I'm like, unfortunately, that's just the way Next does things.

In six months when, I hope, Remix and Fanstack release RSCs, we're all gonna see that, like, oh, they're actually great because you can just intertwine them and do the composability that React is known for, that you unfortunately right now can't with next. The other thing, but this is kind of tying back into there are no other implementations.

If you want to build and package RSC, and you want to do redirects or reading cookies or headers or whatever, right now you would use the next function, which is fine, but then you can't mount that in remix. So this is chicken and egg problem. We can't have generic things until other frameworks implement them.

But when other frameworks implement them, we will need to build this before we have true generic components that we can run anywhere. So I would just say that, like, this is a great idea, great tech. We can bundle a bunch of things. It's gonna make React more composable than ever, but it's not there yet.

Definitely not there yet. It's gonna be there in, like, six months to a year when other frameworks start shipping this. But till then, unfortunately, we're out of luck. That's it. Thank you.

Related Talks