You don't want to Server-side Render your Next.js App


Next.js is a fantastic framework; it provides many unique features, helping you build any web application effortlessly. But when it comes to choosing the right rendering strategy for your pages, you may face a problem; how should I render them? Should I statically generate them at build time? Should I server-side render them at run-time? Well, the answer is surprising. server-side rendering is rarely the best option, and let's explore why (and how to solve this problem!)



Hello everyone and welcome to my talk, You Don't Want to Serve a Set Renderer, Your Next JS App. Before we start, let me please introduce myself really briefly. I am Michele, I work as a Senior Software Architect at NearForm, I'm a Google Developer Expert and a Microsoft MVP. I am also the author of Real World Next JS, which is a book that, as you may guess, talks about Next JS. So if after the talk you'd like to hang out and talk about Next JS, please feel free to reach out. I'd be glad to talk with you. A couple of words about NearForm. We're a professional services company and we specialize in Node.js, React, Next JS, TypeScript, and whatever. We are maintaining a lot of open source packages that get downloaded 1.2 billion times per month cumulatively. So if you're looking for a remote first job and you're really committed to open source, please feel free to reach out. I'd be glad to introduce you to the company. But that's not why we are here, so let's talk about Next JS. First of all, what is Next JS? Why do we want to use it and why is it so beautiful? So when React started to be a thing, we only had client-side rendering, basically. So that was the norm. We basically generate a big bundle and serve it over the net. This bundle will contain the whole application and as soon as it gets transferred to the browser, the browser will read the file, the JavaScript file, execute it, and we'll have the DOM ready to be browsed. So basically the problem with this approach, but we will see many problems later on, is that this is what we see when we first download the bundle. So while it's downloading, while it's executing, we see a spinner. After several seconds, we will see the complete page ready to get browsed. So this approach has some cons and has some pros, so let's see why it might be a good or a bad choice. So first of all, it makes you feel your app like it's a native application. And that's because page transitions are really easy to handle and you don't have to refresh the page every single time. Every time you click on a button, for example, and you go to another page, you don't need a page refresh. You already have all the components you need that get lazy loaded directly into the browser. So for example, you're in the homepage, you click to read an article, the whole DOM refreshes, but the page doesn't. So the DOM React will swap the content with the new one and will execute lazy components that haven't been executed yet during the first load. One other great thing about client-side rendering is that you don't need a server. And if you need, because you already have one and you want to serve your client-side application from a server, it doesn't require much power and there's low server workload, which is pretty good because it's really easy to scale. But if you don't have a server, that's still good. You can just put your files, static assets, on a CDN, on S3, on AWS, for example, or using CloudFlare pages or whatever, and you can host an entire application directly from something that doesn't need a server at all. While this is good for many things, it also has some problems, for example, with search engine optimization. We all know that React is not particularly good for search engine optimization, which is true, but it really depends on the market you care about. For example, if you care for the European and American market, we know that Google is the number one search engine, and Google today is capable of indexing React content. But there might be other search engines that are more popular in other continents, such as Asia, for example, or Africa, that does not read React-generated pages. So if you truly care about those continents and markets, you should definitely look into something different for building your application. Another con is that React is really bad for the initial page load time, as we saw. So the first time you download a bundle, you just have to wait several seconds to make it in order to be able to browse it. So this is why we begin to see server-side rendering as a different way for approaching React rendering specifically. So with server-side rendering, this is what you get as soon as you request a page. It might take a couple of seconds, because the server still needs to render the application on the server-side, but eventually it will send you the complete page ready for browsing. So this approach also has some pros and cons. Let's see them. One pro, the application might be a bit more secure, and that's because you might have server-side cookies, for example, for authentication, and you don't have to share those cookies with the client, which makes your application more secure. You can hide some requests from server to server without exposing them on the client, so that limits the possibility for a man-in-the-middle attack, for example. Also, you end up having more compatible websites. So if you don't have JavaScript enabled, you will still see the first render for your application. Search engine optimization is enhanced thanks to server-side rendering, because you basically end up having a product which is the same that you might have with Ruby on Rails, JavaSpring Boot, Laravel, or whatever. The content can be highly dynamic, and you can have that kind of dynamism directly on the server. So depending on the user that is currently logged in, for example, you might decide to render different things directly on the server. Of course, it also has some problems. For example, a server is required. Every single request gets rendered on the server and transmitted to the browsers once rendered. So there will be a higher server workload, higher maintenance costs, because you will have to maintain a server, and this is costly. So you have to scale the server horizontally, for example, if you want to add more servers to serve your application because you need to scale, or you need to add more power to the server you already have if you want to scale vertically. That depends on, of course, how you want to scale your applications. So there is a third option that we are going to explore, which is called static site generation. So just like server-side rendering, this is what you get when you request a static site-generated page. So basically, as soon as you make a request, you don't need to server-side render it, because you already rendered it during build time. So you will serve your application as static files directly to the browser. This approach has pros and cons, of course. One pro is that it's super easy to scale. That's because, again, you don't need a server. You already have static assets. You put them in a CDN, on S3, on wherever you want, on a bucket, and you just serve them as static assets, because you don't need to render the page every single time you get a request. So that leads to outstanding performances, because the page is ready. You don't need to render it either on the client or server side. So the page is done. You just have to transfer it. It is really secure, because you don't have a server. So there's no way to attack the server directly. So that's another thing to keep in consideration. Of course, again, it has some cons. Let's see them. So no dynamic content is allowed on the server side, because you don't have a server. So you can't render specific things for a specific user on the server, because you don't have a server at all. It relies on client-side rendering for all the dynamics part of your application. So for example, if you are on Instagram and you're scrolling the feed, you could generate the feed page with static side generation. But eventually, you will need to wait for the client to understand who is the user that it's logged in, and then render the content which is specific to them. And one other thing, which is really annoying, I would say, is that if you need to change something, for example, a typo on the home page, you will need to reload and rebuild and redeploy the whole application. That can be fixed with incremental static regeneration, but we will see that in detail later on. So one nice thing about React is that you don't have to compromise. You don't have to choose one rendering strategy for the whole website. But you can do that granularly at the page when you basically render the single page. This is called hybrid rendering. So basically, you might say, OK, the home page for this website will be a statically generated page. If you are in a blog, the post page can be dynamically generated with a server-side rendering, for example. That's cool. And some parts of the application can be rendered on the client-side only. So you can choose how long, basically, the user has to wait before assessing the content. Let's see in detail what I mean by waiting. So for example, this is client-side rendering, right? We make a request, and as soon as we make the request, the server or the CDN will send us an empty page. As soon as the JavaScript bundle containing the React application gets executed, we will start seeing the application getting mounted on the browser. So as you can see, the server will be really fast in sending a response, but we will have to wait. So as time passes by, we have to wait for the page to be loaded. With server-side rendering, it is a bit different. We ask for the server for a specific page. We wait milliseconds, hopefully, or seconds, depending on how good we are at writing React or how slow the APIs behind our applications are. And then, when the page is correctly rendered on the server, we transmit it to the client. Static site generation looks like the perfect match. As soon as we make a request, we get a complete response. So this is why I like to talk about incremental static regeneration, because it basically solves a big problem we are having with static site generation. So we don't want to rebuild the whole website every single time we make a change, because this is good. What we are seeing right now on the slide, it's good. But for example, if I change my mind and I start loving Irish setters, for example, I will have to rebuild the whole website. But thankfully, with incremental static regeneration, that's not the case. So let's see how it works. So basically, this is our page right now. This is a homepage for my website, because I love English setters. So let's say we have a value in our getStaticProps that is called revalidate set to five minutes. So every five minutes, we have to revalidate that page, meaning we have to rebuild it. So I publish the page. After one minute, one user comes to the website, and they will see that page. After three minutes, another user comes to the website and will see the exact same page. The same, of course, for the third user that looks at our website after five minutes. Remember, this is lazy. So if no users will see the page again from now on, Next.js won't reload the application itself, won't re-render, I would say, the homepage in that case. But we say that every five minutes, when a new request comes, we have to regenerate the page. So that's what's happening, basically. We regenerate the page, and the next user, after five minutes, will see a totally new page with a new background color, with an Irish setter instead of an English one. And that's what will happen after four minutes for another user, and after five minutes for another user again. From now on, after five minutes, if another user comes to the website, we will need to revalidate the page. So basically, if nothing changes, we will see server-side render the page, but we will put some caching headers so that it will be able for our cache to serve a cached version of the page. If nothing changes, we will still see the same page. Okay, but that said, why would I want to avoid server-side rendering? So it's my opinion that React server-side rendering is not really efficient yet. That's not Next.js' fault, this is no one's fault, but JSX. JSX is not really efficient as a markup language, or I don't know how to call it, but it's not really efficient when it comes to server-side rendering. So every single request requires a lot of effort on the server-side, which slows down the server, so you need to scale it, so you need to add more computing power, more servers, to keep up with the increasing demand for your application pages. So it might be easy for an architect at a certain point to say, yeah, go serverless. You don't have that problem if you go serverless. You basically deploy your Next.js application on AWS Lambda, Google Cloud Functions, Azure Functions, whatever, and you don't need to scale. That's correct. But at what cost? It's not cheap. So let's see a little example on Google Cloud Platform. Let's say this is the price of Google Cloud Functions. So let's say for the first two millions of invocations per month, we don't pay anything. Beyond two millions, we pay 40 cents every million of invocations. Sounds cheap, right? We will see later on how much it will cost. As you can see, you also pay computer time every 100 milliseconds. I have no idea how to pronounce those small numbers, so forgive me. Networking. You also pay for networking. So first gigabyte of transfer traffic are free, then you pay 12 cents per gigabyte. Sounds cheap, right? So let's say we have an application that handles 100 concurrent server-side renders requests per second, which leads to 259 million and 200,000 requests per month. It's incredibly hard for me as an Italian to say English numbers. I'm super sorry. But it's a huge number of requests, right? The average execution time, let's say, is 300 milliseconds. We can use the cheapest RAM option, which is 128 megabytes. And we will require around 100 kilobytes of bandwidth per execution. And OK, we will need more. But let's say, for keeping it simple, that this is how much we will be transferring. How much would it cost? Yes, using Google Cloud Functions, for example, it will cost $3,000 per month. But it will scale. Of course it will scale. But if you get more requests, the price will get higher and higher. So it's not really sustainable for large-scale applications. If you go back, for example, on Compute Engine with two virtual CPUs, eight gigabytes of RAM, you will pay like $52 per month, which is way cheaper. But still you'll have the problem of server-side rendering. Because you will need to scale it. So eventually you want to go back to serverless, but it will cost a lot. You want to go back again to Compute Engine, for example, but it won't scale, or it will become really costly. So how do we get out of that bad situation? So my suggestion would be play smart, depending on the scenario. So let's start with an example scenario. Let's say we want to build a social network app, just like Instagram or whatever. So I want to play a little quiz game with you. I will give you a couple of seconds for thinking of an answer. So this is the first question. We already discussed this. Let's see if you were paying attention. So let's say we have to build a feed page feature for our application. For example, on Instagram, when scrolling the feed page, the home page of Instagram. So the content is really dynamic, and it's custom for every single user. It doesn't need SEO, and that's because the user is logged in. So the feed is really specific for users. It serves a lot of static assets, and it's basically one big common page for every single user. What would you choose for that? Would you choose server-side rendering? Would you choose server-side rendering with a caching layer? Would you choose static site generation and incremental static regeneration? Or would you choose static site generation and client-side rendering for displaying dynamic content? Let's think about it. Say my opinion, the correct answer is static site generation with client-side rendering. That's because you don't need server-side rendering. We know that it will be different for every single user, but the page is the same. The header will be the same, the footer will be the same. It only changes the content. So the content can be lazy loaded on the client. Also, if you think of a scale of Instagram, okay, this is too big for many of us, but you don't really want to server-side rendering that much requests. It's becoming really costly. So let's go with a single post page. So we see a nice photo, we click on the title, and we see the full post with the description and the comments and whatever. So there are a large amount of pages. It will need a good search engine optimization, because this is how we get into Google with the individual posts, not with the homepage. It serves big static assets, photos, videos, and there is one single page for every single post. What would you use? So answers are the same as the previous answer, but in my opinion, this is a good case for server-side rendering to cache. Let me explain to you why. We will be server-side rendering this for the first time, and we'll be serving a caching response every single time a new user asks for the same post. After let's say one hour, 30 minutes, one day, we will purge the cache and re-render it, mimic how incremental static regeneration works. We will see later on with another example what might be a good alternative to that, but for the moment, let's keep our thoughts on SSR and cache. Okay, last one. Account setting page needs high security. Doesn't need search engine optimization, and it's protected under authentication. We know for sure that this is not the most visited page of every account, right? So that might be a good case, in my opinion, for server-side rendering without any cache. We don't want cache, otherwise the risk is that I do log in, and when another user logs in they will see my account. This is not what we want, you know? But also we have server-side cookies, which are more secure than client-side cookies. So that's a good case for server-side rendering, and we can afford it because there are not many requests to that page specifically. So let's go with another example. Let's say we want to build an e-commerce. So the homepage, it's really important for every single e-commerce, right? We need high performances, awesome search engine optimization, and the content changes from time to time. Not really frequently, but let's say a couple of times per week, okay? So what would you choose? I'll give you a couple of seconds. My opinion is to go for static site generation and incremental static regeneration without a long revalidate time. So that you basically statically render the page, the performances will be awesome, and you will revalidate the page after, I don't know, one day, two days, one week. It really depends. You can choose. So let's go with a single product page. Let's say that in our e-commerce, we have 250 products. We need awesome SEO for every single product. Need awesome SEO, great performances, some dynamic data, for example, stock quantity. I don't know, for example, delivery time, depending on where you come from or where to ship the merch, et cetera. What would you choose? I would choose static site generation and client-side rendering. And that's because the product page doesn't really change that much. Otherwise, we could have chosen incremental static regeneration, but it doesn't really change that much. The stock quantity can be controlled on the client side via API calls, for example. And the same can be done with external APIs for delivery. One thing to mention, and I wanted to mention this previously when talking about the social network, but it's worth mentioning now, is that if we have a very large e-commerce or a blogging platform, for example, where we have, for example, one billion pages to be rendered, of course, we can't render one billion pages at build time. It will take forever. So we could choose to go with incremental static regeneration with a fallback. There is a fallback value in the incremental static regeneration setting so that we can say, I will build at build time the hundred more popular posts or articles in my e-commerce, whatever, and I will defer the rendering phase for all the other posts or articles at request time with incremental static regeneration. So I will basically generate 100 posts that are the ones that get searched the most, and the other ones will be rendered at request time, and we will have some nice caching headers thanks to incremental static regeneration and revalidation, of course. Okay, so last one, account setting page. High security, doesn't need search engine optimization, and it's protected under authentication. What would you choose? Okay, I wanted to know if you were paying attention. This is the same of the social network one. We already discussed this, so let me go ahead because I'm about to finish my time. So lesson learned number one, content is dynamic and depends on the user. In that case, we can generate a static page at build time and render the dynamic data on the client side. And that's because the dynamic parts of our application are not meaningful for search engine optimization, so we don't need them. So it is useless for us to use server-side rendering in that case. Lesson learned number two, content is static and SEO is important. We can generate the page at build time and adopt incremental static regeneration when needed. Lesson number three, content is static but the page gets created dynamically. Okay, if the content is static but the page gets dynamically created, for example, we are in a blog, I publish a new article, I don't want to rebuild the entire website. In that case, we could use server-side rendering and put the web app behind a cache, or we could use, again, incremental static regeneration with a fallback, so that we generate at build time all the articles in a blogging platform, for example, that we have in the database, and as soon as I create a new article, I don't need to rebuild the entire website. As soon as the requests come to the website, Next.js will say, okay, I have fallback set to true, so I will ask the server if the article now exists. If it exists but I haven't generated it at build time, I will generate it now and put it behind a caching layer using caching headers. In any case, you want to avoid server-side rendering at all costs, and that's because it's really costly. It's really expensive to manage and also really costly on talking about money, because you have to manage an infrastructure, you have to manage maintenance for your servers, and whatever. So it's time to use Next.js for how it evolved rather than why it was born. And Next.js, it's an amazing framework and lets us decide whether we have to use static side generation, incremental static regeneration, server-side rendering, client-side rendering, and whatever. So that's why I still bet on Next.js, even though I do avoid server-side rendering at all costs. So again, this talk has been adapted from a chapter of VR World Next.js. In case you are interested, feel free to reach out. Thank you so much for being there and following the talk. I will leave you a couple of contacts in case you want to reach out. I live on Twitter, so if you want to reach out, that's the best place where you can find me. Thank you again. I hope to see you all in person anytime soon. Thank you. Bye-bye.
28 min
21 Jun, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic