Server-side rendering (SSR)
Even though server-side rendering (SSR) sounds like a new term in the developer's vocabulary, it is actually the most common way for serving web pages. If you think of languages such as PHP, Ruby, or Python, they all render the HTML on the server before sending it to the browser, which will make the markup dynamic once all the JavaScript contents have been loaded.
Well, Next.js does the same thing by dynamically rendering an HTML page on the server for each request, then sending it to the web browser. The framework will also inject its own scripts to make the server-side rendered pages dynamic in a process called hydration.
Imagine you're building a blog and you want to display all the articles written by a specific author on a single page. This can be a great use case for SSR: a user wants to access this page, so the server renders it and sends the resulting HTML to the client. At this point, the browser will download all the scripts requested by the page and hydrate the DOM, making it interactive without any kind of page refresh or glitch (you can read more about React hydration at https://reactjs.org/docs/react-dom.html#hydrate). From this point, thanks to React hydration, the web app can also become a single-page application (SPA), taking all the advantages of both client-side rendering (CSR) (as we'll see in the next section) and SSR.
Talking about the advantages of adopting a specific rendering strategy, SSR provides multiple benefits over the standard React CSR:
- More secure web apps: Rendering a page on the server side means that activities such as managing cookies, calling private APIs, and data validation happen on the server, so we will never expose private data to the client.
- More compatible websites: The website will be available even if the user has disabled JavaScript or uses an older browser.
- Enhanced search engine optimization: Since the client will receive the HTML content as soon as the server renders and sends it, the search engine spiders (bots that crawl the web pages) will not need to wait for the page to be rendered on the client side. This will improve your web app's SEO score.
Despite those great advantages, there are times where SSR might not be the best solution for your website. In fact, with SSR, you will need to deploy your web application to a server that will re-render a page as soon as it's required. As we'll see later, with both CSR and static site generation (SSG), you can deploy static HTML files to any cloud provider, such as Vercel or Netlify, for free (or at a meager cost); if you're already deploying your web app using a custom server, you have to remember that an SSR app will always lead to a more significant server workload and maintenance costs.
Another thing to keep in mind when you want to server-side render your pages is that you're adding some latency to each request; your pages might need to call some external API or data source, and they'll call it for every page render. Navigating between server-side rendered pages will always be a bit slower than navigating between client-side rendered or statically served pages.
Of course, Next.js provides some great features for improving navigation performances, as we'll see in Chapter 3, Next.js Basics and Built-In Components.
Another thing to consider is that by default, a Next.js page is statically generated at build time. If we want to make it more dynamic by calling an external API, a database, or other data sources, we will need to export a particular function from our page:
function IndexPage() { return <div>This is the index page.</div>; } export default IndexPage;
As you can see, the page only prints the This is the index page. text inside a div
. It doesn't need to call external APIs or any other data source to work, and its content will always be the same for each request. But now, let's pretend that we want to greet the user on every request; we will need to call a REST API on the server to get some specific user information and pass the result to the client using the Next.js flow. We will do that by using the reserved getServerSideProps
function:
export async function getServerSideProps() { const userRequest = await fetch('https://example.com/api/user'); const userData = await userRequest.json(); return { props: { user: userData } }; } function IndexPage(props) { return <div>Welcome, {props.user.name}!</div>; } export default IndexPage;
In the preceding example, we used the Next.js reserved getServerSideProps
function for making a REST API call on the server side for each request. Let's break it down into small steps so that we can better understand what we're doing:
- We start by exporting an async function called
getServerSideProps
. During the build phase, Next.js will look for every page exporting this function and make them dynamically server-side rendered for each request. All the code written within this function scope will always be executed on the server side. - Inside the
getServerSideProps
function, we return an object containing a property calledprops
. This is required because Next.js will inject those props inside ourpage
component, making them available both on the client and server side. In case you're wondering, we don't need to polyfill the fetch API when we use it on the server side, as Next.js already does that for us. - We then refactor the
IndexPage
function, which now accepts aprops
parameter containing all the props passed from thegetServerSideProps
function.
And that's all! After we ship this code, Next.js will always dynamically render our IndexPage
on the server, calling an external API and showing different results as soon as we make changes in our data source.
As seen at the beginning of this section, SSR provides some significant advantages but has some caveats. If you want to use any component that relies on browser-specific APIs, you will need to render it on the browser explicitly because, by default, Next.js renders the entire page content on the server, which does not expose certain APIs, such as window
or document
. So here comes the concept of CSR.