OrangeDurito.blog

An amalgamation of engineering and artistry

Learning NextJS In Lockdown

Introduction

While React is awesome as such, there are obvious shortcomings when it comes to speed, routing, scaling, etc. NextJS was the answer. This is the latest framework that is used by world's leading companies, thus as a natural extension to learning basic working of React, I gave NextJS a try. While going through their docs, I kept making this note for the reference. You can visit the blog I built, here - Sample Blog

Let's start with the benefits that NextJS framework provides over simple ReactJS application.

Built-in Features

  • An intuitive page-based routing system (with support for dynamic routes)
  • Pre-rendering, both static generation (SSG) and server-side rendering (SSR) are supported on a per-page basis
  • Automatic code splitting for faster page loads
  • Client-side routing with optimizes prefetching
  • Built-in CSS and Sass support, and support for any CSS-in-JS library
  • Development environment which supports Hot Module Replacement
  • API routes to build API endpoints with Serverless Functions
  • Fully extendable

Starter

  • npx create-next-app
  • Template could be downloaded using --example flag npm init next-app nextjs-blog --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"

Routing - Navigate Between Pages

  • Create a new page using the file system routing feature
  • Learn how to use the Link component to enable client-side navigation between pages
  • Learn about built-in support for code splitting and prefetching
  • In Next.js, we use the <Link> React Component that wraps the <a> tag. <Link> allows you to do client-side navigation to a different page in the application.
  • Client-side navigation means that the page transition happens using JavaScript, which is faster than the default navigation done by the browser.

If we change any CSS property like background-color of <html> and click on the links to navigate between pages, we see that the change persists between page transitions. This shows that the browser does NOT load the full page and client-side navigation is working.

If we had use <a href="..."> instead of <Link href="..."> and did this, the change (background-color here) will be cleared on link clicks because the browser does the full refresh.

Code splitting and prefetching

  • Next.js does 'code-splitting' automatically, so each page only loads what's necessary for that page. That means when the homepage is rendered, the code for other pages is not served initially. This ensures that the homepage loads quickly even if you add hundreds of pages.

  • Only loading the code for the page you request also means that pages becomes isolated. If a certain page throws an error, the rest of the application would still work.

  • In a production build of Next.js, whenever Link components appear in the browser's viewport, Next.js automatically prefetches the code for the linked page in the background. By the time you click the link, the code for the destination page will already be loaded in the background, and the page transition will be near-instant.

  • If you need to link to an external page outside the Next.js app, just use an <a> tag without Link.

  • If you need to add attributes like, for example, className, add it to the a tag, not the Link tag.

import Link from 'next/link'

export default function LinkClassnameExample(){
    return(
        <Link href="/">
            <a className="foo" target="_blank" rel="noopener noreferrer">
                Hello World
            </a>
        </Link>
    )
} 

Assets, Metadata, and CSS

  • Next.js can serve static assets, like images, under the top-level public directory. Files inside public can be referenced from the root of the application similar to pages.
<img src="/vercel.svg" alt="Vercel Logo" className="logo" />
  • The public directory is also useful for robots.txt, Google Site Verification, and any other static assets.

CSS Styling

  • styled-jsx library (a "CSS-in-JS" library that lets you write CSS within a React component, and the CSS styles will be scoped, meaning other components won't be affected.)
<style jsx>{'
    ...
'}</style>
  • CSS Modules lets us import CSS files in a React component.
  • To use CSS Modules, the CSS file name must end with .module.css

unique class name

CSS Module automatically generates unique class names.

  • CSS Modules are extracted from the JavaScript bundles at the build time and generate .css files that are loaded automatically by Next.js

Global Styles

  • To load global CSS files, create a file called _app.js under pages with following code -
export default function App({Component, pageProps}){
    return <Component {...pageProps} />
}
  • App component is the top-level component which will be common across all the different pages. You can use this App component to keep state when navigating between pages.

  • Development server needs to be restarted after adding _app.js

npm run dev
  • In Next.js, we can add global CSS files by importing them from _app.js. We cannot import global CSS anywhere else.

Summary

To use CSS Modules, import a CSS file named *.module.css from any component

To use global CSS, import a CSS file in pages/_app.js

Pre-rendering

  • By default, Next.js pre-renders every page. It generates HTML for each page in advance, instead of having it all done by client-side JavaScript.

  • Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded is loaded by the browser, its JavaScript code runs and makes the page full interactive. This process is called hydration.

Pre-rendering can result in better performance and SEO.

  • Pre-rendering Vs. No Pre-rendering Comparison -

Pre-rendering

No Pre-rendering

Two Forms of Pre-rendering

  • Next.js has two forms of pre-rendering: Static Generation and Server-side Rendering. The difference is in when it generates the HTML for a page.

  • Static Generation is the pre-rendering method that generates the HTML at build time. The pre-rendered HTML is then reused on each request.

  • Server-side Rendering is the pre-rendering method that generates the HTML on each request.

Static Generation

Server-side Rendering

In development mode (when we run npm run dev or yarn dev), every page is pre-rendered on each request - even for pages that use Static Generation.

  • Per-page Basis - Next.js lets you choose which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

  • Use Static Generation (with and without data) whenever possible because the page can be built once and served by CDN, making it faster than having a server render a page on every request.

  • If we cannot pre-render a page ahead of a user's request, then only we should go for Server-side rendering. It maybe because our page shows frequently updated data, and the page content changes on every request.

  • Albeit slow, pre-rendered page will always be up-to-date. Or we can skip pre-rendering and use client-side JavaScript to populate data.

Static Generation with and without Data

  • For pages which do not require fetching external data, Next.js automatically generates them statically when the app is built for production.

Static Generation without Data

  • In case, some pages need to access the file system, fetch external API, or query your database at build time, even then Next.js might be able to render the HTML without first fetching some external data. Next.js supports Static Generation with Data out of the box.

Static Generation with Data

Static Generation with Data using getStaticProps

  • When we export a page component, we can also export an async function called getStaticProps.

getStaticProps runs at build time in production and inside the function, you can fetch external data and pass that as the props of the page.

export default function Home(props){ ... }

export async function getStaticProps(){
    //Get external data from the file system/API/DB, etc.

    const data = ...

    //The value of the `props` key will be passed to the `Home` component

    return {
        props: ...
    }
}
  • Essentially, getStaticProps allows you to tell Next.js: \"Hey, this page has some data dependencies - so when you pre-render this page at build time, make sure to resolve them first!"

Blog Data

pre-rendering.md file created in top-level 'posts' directory.

---
title: 'Two Forms of Pre-rendering'
date: '11-07-2020'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

Notice that each markdown file has a metadata section at the top containing title and date. This is called YAML Front Matter, which can be parsed using a library called gray-matter.

Parsing Blog Data on getStaticProps

  • To parse each markdown file and get title, date, file name (which will be used as id for the post URL), and list the data on the index page (sorted by date), we need to implement getStaticProps by updating our index page (pages/index.js) using the above data.

Parsing Blog Data using getStaticProps

Pre-rendering and Data Fetching

  • After installing gray-matter we parse the markdown posts by creating lib/posts.js file in the top domain. Then we import the function in pages/index.js as
import { getSortedPostsData } from '../lib/posts'

Then we call this function in getStaticProps and return the result inside the props key as

export async function getStaticProps(){
    const allPostsData = getSortedPostsData()
    return {
        props: {
            allPostsData
        }
    }
}

And then allPostsData prop will be passed to the Home component.

export default function Home ({ allPostsData }) { ... }

To display the data -

{allPostsData.map(({id, date, title}) => (
    <li className={utilStyles.listItem} key={id}>
        {title}
        <br />
        {id}
        <br />
        {date}
    </li>
))}

We successfully fetch external data (from the file system)and pre-render the index page with this data -

Data Fetching and Pre-rendering

Fetch External API or Query Database

import fetch from 'node-fetch'

export async function getSortedPostsData(){
    //Instead of the file system, fetch post data from an external API endpoint
    const res = await fetch('..')
    return res.json()
}

Querying Database Directly

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData(){
    //Instead of the file system, fetch post data from a database
    return databaseClient.query('SELECT posts...')
}
  • This is possible because getStaticProps runs only on the server-side. It will never be run on the client-side. It won't even be included in the JS bundle for the browser. That means you can write code such as direct database queries without them being sent to browsers.

Development Vs. Production

  • In development (npm run dev or yarn dev), getStaticProps runs on every request while in production, getStaticProps runs at build time.

Because it's meant to be run at build time, we won't be able to use data that's only available during request time, such as query parameters or HTTP headers.

  • Also getStaticProps can only be exported from a page. We can't export it from non-page files. One of the reasons for this restriction is that React needs to have all the required data before the page is rendered.

Fetching Data at Request Time

  • If we need to fetch data at request time instead of build time, we can try Server-side Rendering. Here, we need to export getServerSideProps instead of getStaticProps from your page.
export async function getServerSideProps(context){
    return{
        props: {
            // props for your component
        }
    }
}

Server-side Rendering with Data

We should use getServerSideProps only if we need to pre-render a page whose data must be fetched at request time. TTFB (Time to First Byte) will be slower than getStaticProps because the server must compute the result on every request, and the result cannot be cached by a CDN without extra configuration.

Client-Side Rendering

  • As alternative strategy to pre-rendering, we can Statically generate (pre-render) only parts of the page that do not require external data and then fetch external data from the client using JavaScript when the page loads and populate the remaining parts.

Selective Pre-Rendering and Client-Side Rendering

e.g - In user dashboard like pages, we can use this approach because it is a private, user-specific page, SEO is not relevant, and the page doesn't need to be pre-rendered. Data is frequently updated, which required request-time data fetching.

SWR - React Hook for Data Fetching

  • Highly recommended when fetching data on the client side. It handles caching, revalidation, focus tracking, refetching on interval, and more.
import useSWR from 'swr'

function Profile(){
    const {data, error} = useSWR('/api/user',fetch)

    if (error) return <div> failed to load </div>
    if (!data) return <div> loading...</div>
    return <div> hello {data.name}!</div>
}

Comments