Check out a free preview of the full Intermediate React, v5 course

The "Server Side Rendering" Lesson is part of the full, Intermediate React, v5 course featured in this preview video. Here's what you'd learn in this lesson:

Brian explains how server-side rendering runs React code on a Node.js server before the response is sent to the user. This allows the initial portion of the application to be rendered and sent back to the user while the server creates a readable stream of the remaining content.


Transcript from the "Server Side Rendering" Lesson

>> So we have a checkpoint right now, you can go into your app, or our project here rather,, we're down in code splitting. So we're down here at code splitting. So you can see all the code up until now here in code splitting. We are now going to do server side rendering.

We've done code splitting, now what we wanna do is whenever a user makes a request to our website, we don't wanna just send them bare HTML, which is what they receive upfront, right? They receive this, and then their app goes and refetches App.js and style.css. We wanna send them complete markup whenever the user makes the request, so that whenever they receive it, they get a complete vision of the site.

And then they can basically start reading your site, start deciding what they wanna do. And then hopefully by the time they make a decision about what they wanna do, your app is now ready to be interacted with, right? So that what server side rendering is going to do is it's going to reduce the time to first meaningful paint, right?

It's going to increase the time to interactivity, which is bad, right? But the idea is, you show the user something, they start understanding what you're showing them. And by the time they go to click on something, you'll have loaded all the JavaScript in the background, and they'll be able to do something about it, right?

So picture you're signing up on The reason I'm picking this one is cuz I used to literally work on exactly this problem, right? You show them, This, right? And by the time that the user reads the website, they're like, okay, I wanna sign in, right? By the time that they think about it and click on Sign In, you want the website to have been loaded by that point, right?

That's what we're gonna try and do right now. Now, what's really bad is if you show them something, and by the time that they go click on something, it doesn't do anything. You played yourself, that's a bad idea, right? Again, it's not a silver bullet, you gotta keep your eye on the ball.

You gotta make sure that you're measuring your performance metrics to make sure that you are making a better experience, not making a worse experience. Because nothing will frustrate a user more than them trying to do something and it's like, this is broken, and then they'll just bounce, right?

So let's go. Head to your application, we're gonna make a new file here that is called ClientApp.jsx. So we're gonna do server side rendering. You can't do any DOM stuff in Node, I think that makes sense, right? You can't call window.alert because one, there's no window, and two, there's no alert.

So you have to make sure that anything that interacts with the DOM is not loaded in Node, or else it breaks. So we're gonna put all of the browsery stuff here in ClientApp.jsx. So first thing we're gonna do is import hydrateRoot from react-dom/client. We're gonna import BrowserRouter, because it's in the name, you can't load BrowserRouter in Node, so that comes from react-router-dom.

And then we're going to import App from './App'. Okay, And then here, We're just gonna say hydrateRoot. We're gonna tell it where to hydrate it, so document.getElementsById('root'). We're gonna say BrowserRouter, App. That's it, my notes is incorrect, so that is it. So all we are doing is we're loading the app, we're loading BrowserRouter.

We're doing everything that has to be done in the browser here in ClientApp. Imagine if you had something like Google Analytics, you would do that here, right, because you don't wanna run analytics from the server, you wanna run analytics from the browser, right? So anything like that, you're gonna throw here in ClientApp.jsx.

Anything that's only gonna happen in the browser and not gonna happen on server side renders, that goes here, okay? Go back to index.html. This is gonna load ClientApp, not App.jsx, so that's important. Okay, and then go to App.jsx, get rid of BrowserRouter here. Don't load that, delete that from your app here as well.

Yeah, get rid of the React DOM import up here as well. Yes, so this one right here, the createRoot one, just get rid of that altogether. And then down here at the bottom, get rid of all this root.render stuff. I'm just gonna say export default App. So far so good.

So we're making app now general purpose that it can be run either in Node or in the browser. Now, we need a ServerApp.jsx. So make another new file called ServerApp.jsx. Okay, here we're gonna say import, RenderToPipeableStream, From react-dom/server. Import {StaticRouter} from react-routers-dom/server. And import App from './App'. Okay, StaticRouter is basically React router that can be run in Node, that's all it is.

So it's all the same machinations of how to resolve routes, what route they're on, which one to send them to, navigation, all that kinda stuff. But it doesn't have all the stuff that ties into history, which is only in the browser, right? That's why we need StaticRouter and not BrowserRouter.

renderToPipeableStream is something that can only be run in Node. This is actually going to run our application, and it's gonna run it into, what's called, a node stream, right? Which is basically this thing that it'll progressively render your app and it'll give your app progressive output. So that when a user makes a request, we can slowly serve them pieces of information as they're available.

The way that this used to work is we used to have to render the entire thing all at once and then send them complete HTML. Now, with React Router, sorry, not with React Router, but React 18 and suspense, we can send them piece by piece. I guess it was React 17 that did that, but it was keeping track.

Okay, we're gonna export default a function called render, whatever you wanna call it, url and options, okay? And then const stream = renderToPipeableStream, StaticRouter location = url. And we have App, And then we have opts, like that. Okay, and then down here, we just return stream. Now, what's cool about this and this approach, I didn't use to do it this way, I think this is much more elegant than the way I used to teach this is, is we can now just render, sorry, we can just have Vite build ServerApp.jsx.

And then we can import what is built by Vite into Node. So we don't have to build our entire node server, we just have to build server app, which then interfaces to our jsx, right? Because Node can't read jsx, that's the problem that we have here. Does that makes sense?

So now we can say, hey, Vite, build this for me, and then in our Node application, we'll say, import the built output. Your alternative is using something like a babel/node or something like that, it gets really janky pretty quickly. Okay, all that sounds good, go into your package.json.

We're gonna go in here to your scripts and we're gonna add a couple more. So we're gonna have two different builds. We're gonna have "build:client", which is gonna be vite build -- outDir ../dist/client. And then we're gonna have a build server, which is gonna be vite build --outDir ../dist/server -- ssr ServerApp.jsx.

Okay, and then when I have multiple build steps, I'll do something like npm run build client, and npm run build server. To be super fair, I don't think this works in Windows, so you'll have to do something else. It doesn't matter, we don't really have to build this.

What would be more elegant is having a make file that does all this stuff for you, but I'm definitely not teaching make today [LAUGH] or ever, for that matter, but definitely not today. Okay, and then server, we're gonna have just "node server.js", something like that. Or start, not server, just node start.

Npm run start, we'll do our "node server.js". One thing you do need to add your package.json, please up here, just put "type": "module", cuz we're gonna be doing modules in Node, it requires this type of module for it to be okay with that. Okay, getting closer, please npm install express@4.18.2.

You might ask me, why don't I just do this in Next.js? You probably should, I'm showing you how to basically build Next.js, right? I'm showing you what Next.js does under the hood for you. And then after this course, hopefully when you either take Scott's course or something like that, you'll really value what Next is doing for you, because this is not necessarily always that fun to do, right?

But what's cool, what I'm showing you here is that, you don't have to use a framework that's React aware, right? We can build this in any Node-based framework. So when I worked at Netflix, we were working with Fastify, right, Fastify? Restify, Restify is the one that Netflix uses.

This was the kind of stuff that we were doing there.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now