Introduction to Server Side Rendering with Next.js
Ever since the enormous spike in popularity of highly dynamic front-end frameworks such as React, web developers have been trying to find a balance between the interactive user experience offered by client-side rendering, and the various speed and optimization related benefits provided by server-side data rendering.
One easy way of combining these techniques to get the best of both worlds is by using Next.js, a simple and flexible framework built on top of React, which allows users to utilize a wide range of features like server-side rendering, static site generation, easy file-based routing and much more!
In this article, we will take a look at some of its key features, along with actual code implementation, and highlight the reasons why you should use Next.js for your next web application!
CSR vs SSR, What’s the difference?
Front end frameworks like React and Angular use Client-Side Rendering (CSR), where the server sends an almost empty HTML file to the page, followed by all the JavaScript files in one big bundle, which must then be processed in the browser to render the DOM.
In client-side rendered pages, the initial load speed is slow and the user is left staring at a blank screen until all the JavaScript is executed, and API requests have been fulfilled. However, subsequent load speeds are rapid as further changes will only need to update the relevant sections of the DOM.
The initial load speed issue in CSR can be solved by using Server-Side Rendering (SSR), wherein the server fetches information from the database and sends a prepared HTML file to the page.
Now there is meaningful content on the page that can be viewed by the user. The page becomes interactive once the JavaScript file has been executed. Here, the server bears the load of fetching and processing data, which means initial load speed is faster. However, subsequent load speeds are unaffected as each page needs to be rendered and retrieved again from the server.
The other major benefit of SSR over CSR is Search Engine Optimization (SEO). Since server-rendered pages are already prepared with relevant metadata, it is easy for crawler bots used by search engines like Google to classify these pages, resulting in a higher search ranking for the website. However, pages using CSR rely on JavaScript execution to fill out the metadata, which is not easily filtered by the bots. Often, if the execution takes more than 300–400 ms, the bot will end up processing a blank page. For a detailed explanation about search engine optimization, check out this article.
An ideal solution is to build a hybrid application which takes advantage of both of these techniques in order to optimize the accessibility and user experience, and Next.js offers an excellent starting point for this. So enough talking, let’s get our hands dirty with some code!
Setting up Next.js
There are a couple of ways to install Next.js, but the easiest method is to use npx create-next-app
. For those familiar with npx create-react-app
, this works the exact same way, except it creates a Next app.
npx create-next-app
This creates a new folder with a package.json
file and installs the required packages (next, react and react-dom). You can customize the Babel and Webpack settings, but we won’t discuss that here.
That’s all it takes, you can immediately start the development server, loaded with the sample application, by running npm run dev
. On inspecting the source code, we can see that the SSR is working as it should.
Awesome! Let’s begin testing out the features of Next.js by making a simple web application that displays the weather of various cities (using OpenWeatherMap API) with a few different routes. The complete source code for this project can be found in this repository.
File-Based Routing
Next.js has a file-system based router built on the concept of pages. Any file inside the pages
directory will be available at the same path as the file name. Therefore the file about.js
, which is created in the pages
folder, can be accessed with the route localhost:3000/about. This format is also valid for nested files.
Custom API Routes
Any JavaScript files created inside /pages/api
folder (already present on installation) will be mapped to localhost:3000/api/* . These routes will automatically be treated as API endpoints and processed only on the server-side. This simplifies the addition of customized endpoints to a large extent.
Here we have the default GET route that returns city names in a JSON format. Other types of request like POST, DELETE etc. can be handled by testing the value of the req.method
parameter.
Data Fetching
This is where the applications of server-side rendering come into full effect. Data can be retrieved from the server-side using the following 2 functions:
- getStaticProps ()— This method is used for Static Site Generation (SSG), i.e., pre-rendering those pages at build time whose contents are readily available during the initial build, and static throughout the session.
Here we are using isomorphic-unfetch
, a package that allows us to utilize the fetch API exactly as we would in vanilla JavaScript. getStaticProps() only pre-renders the page at build time, after which the data remains static. However, there are cases where even static data can change over time, for example, updating a database in the backend. In such cases, another field called revalidate can be added to the return body, with a value equal to the number of seconds after which the page re-generation should occur. This is called Incremental Static Regeneration.
Note that in Next.js, the Link
component works by wrapping around a regular HTML <a>
tag, where the ‘as’ prop is used to specify the dynamic route in case of ambiguity. The resultant index
page is shown below.
- getServerSideProps() — This method pre-renders the page on each request to the server. This is useful in case of dynamic routing, where the data needs to be updated each time based on request parameters.
Dynamic Routing in Next.js is done using files named in the manner of [city].js
, where city
is the varying request parameter. The weather conditions can vary erratically, thus it makes sense to render this information using getServerSideProps(). The output page after clicking on New York on the index
page is shown below.
Note: This is simply an example to demonstrate the process by which the page is rendered when using the getServerSideProps() function. The loading speed of this function is slower, and the result cannot be cached by CDNs without extra configurations. If the data doesn’t need to be pre-rendered, consider fetching data on the client-side for improved performance.
Components and Styling
Layout components can be individually created and imported to wrap around the data, as per standard React procedure. Customized <Meta>
tags can be used to inject page-specific information directly into the document’s <head>
, thus improving SEO.
Apart from the traditional method of importing globally scoped CSS files, Next.js also offers the ability to directly include inline styling, or apply component-level CSS using the <style jsx>
tag (shown above), which makes the code easy to comprehend and helps improve load speeds.
And that’s not all
Next.js comes bundled with a plethora of other features like:-
- Integrated TypeScript — By creating a
ts.config
file and runningnpm run dev
, you can directly begin implementing TypeScript into your web applications.
- Static Optimization — When a page doesn’t utilize functions such as getServerSideProps(), Next.js performs automatic static optimization and pre-renders the page to static HTML (This is indicated by a lightning logo on the bottom-right of the Next.js application). This enables extremely fast loading speeds for pages that don’t require server-side computation.
- Automatic code splitting — By default, Next.js supports dynamic imports even at the component level, which means users only download the code essential for the part of the site they’re viewing, enabling faster load speeds.
- Using custom servers — Large scale web applications often require their servers to be manually configured to support custom routes and server-side logic, and Next.js offers full flexibility to reconfigure its entire ExpressJS server. First, replace
next start
withnode server.js
in thepackage.json
file, and then create aserver.js
file in the parent directory. This basic server from the Next.js Documentation can be used as a starting point to begin implementing your own routes and functions.
Conclusion
Hopefully, this article has given you a clear idea about the of the essential features of Next.js, and the bigger picture of server-side rendering. There are many more facets to this framework that I haven’t covered which make it really cool to work with. I encourage you to explore these yourself!
Happy Learning!
This article is published as a part of ‘JavaScripted’ under Spider Research and Development Club, NIT Trichy on a Web Wednesday!