Max Next – Section 14 – Optional: Next JS Summary


Next JS

Updated Jul 22nd, 2022

What is NextJS?

Max personally calls Next JS Full Stack React. React is a framework itself already right – Why use Next JS? Next JS is a framework. It has more features than a library. Clear rules and guidance on how to build apps. makes building large scale apps easier. Adds a lot of features to your React app out of the box. Solves common problems. Great explanation in this video.

Key Feature: Server-side (Pre-) Rendering of Pages

The most important built-in feature. Page content in the source code. Good for search engine crawlers. Also good to not have a flickering initial loading state shown to users. After an initial load we still get a SPA. Blending server-side and client-side.

Key Feature: File-based Routing

Don’t have to import and configure React Router. Not bad, it’s a great package but it is extra code you to write, (Switch, Route, etc.). Less Code, less work, highly understandable. Back to old school way.

Key Feature: Build Full-stack Apps With Ease

Easy to add standalone back-end code and API that uses Node code. Stay in one project.

Creating a Next JS Project & IDE Setup

npx create-next-app nextjs-course

Analyzing the Created Project

The pages, public, and styles folders.

Adding First Pages To The Project

Strip down to create basic news-like site with homepage, news page, and news item. Add “pages/index.js” file.

Don’t need import react code.

// pages/index.js

function HomePage() {
  return <h1>The Home Page</h1>
}

export default HomePage

Page content in the source code which is an important difference from basic React app.

Adding Nested Pages / Paths

Alternative to have “news” subfolder with “index.js” file. Folders act as path segments.

Creating Dynamic Pages

// our-domain.com/news/something-important

[newsId].js

Don’t want to hardcode the identifier in the filename. Want a dynamic page identifier.

Extracting Dynamic Route Data

Special hook provided by Next JS

// our-domain.com/news/something-important

import { useRouter } from "next/router"

function DetailPage() {
  const router = useRouter()

  console.log(router.query.newsId)

  return <h1>The Detail Page</h1>
}

export default DetailPage

Note: The log will show undefined on first execution and then something-else.

Helpful to get newsId and then send a request to a backend API to fetch the news item with newsId.

Linking Between Pages

Basic traditional linking still works but we reset the SPA and lose all state.

import Link from "next/link"

// in the component

<li>
<Link href="/news/something-unique">Holla</Link>
</li>

The anchor element is automatically rendered and you don’t fetch a new html page so the SPA is still rolling.

Onwards To A Bigger Project!

Starter project with basic React components. Basic “Meetup” app with database.

Preparing Our Project Pages

/pages/_app.js
/pages/index.js
/pages/new-meetup/index.js
/pages/[meetupId]/index.js

Rendering A List Of (Dummy) Meetups

The “/pages/index.js” file imports a “MeetupList” component from “components” folder. Uses some hard-coded meetup data in “pages/index.js” file that is an array of meetup objects.

Adding A Form For Adding Meetups

To the “pages/new-meetup/index.js” file add the “/components/meetups/NewMeetupForm.js” component.

The “_app.js” File & Wrapper Components

Don’t want to add a general layout around all of your pages. The “_app.js” file saves the day here, and acts as the root component Next JS will render. Receives props and uses object de-structuring to pull out “Component” prop and a “pageProps” prop.

“Component” holds actual page content and “pageProps” are page-specific props our page might be getting.

// _app.js

function MyApp({Component, pageProps}) {
  return <Component {...pageProps} />
}

export default MyApp

Import and use any Layout component here by wrapping the line above.

Programmatic Navigation

import {useRouter} from "next/router"

  // inside of main component fucntion
  const router = useRouter()

  function showDetailsHandler() {
    router.push("/" + props.id)
  }

// also router.replace()

Note: can only use React hooks at the root level of a component function.

Note: router.push is the equivalent of using a Link component.

Adding Custom Components & Styling With CSS Modules

Supported out of the box and will scope to the component

// MeetupDetail.modules.css

Note: use whatever word for import “blank” from “whatEvs.modules.css” file you choose

How NextJS Page Pre-Rendering Actually Works

Simulate if we actually had a backend. Would typically send a request with “useEffect” and manage state with the “useState” hook. Downside is we need to show a loading state and no meetups data seen in the source code which is bad for SEO.

Introducing Data Fetching For Page Generation (getStaticProps)

Built-in pre-rendering may not be enough. If we want to pre-render with data we need to find tune and we have two form of pre-rendering, (Static generation on build or server-side rendering on the fly).

GSProps only works in page-component files.

Load data before component is rendered

export async function getStaticProps() {
  // fetch data from an API
  return {
    props: {
      meetups: DUMMY_MEETUPS
    }
  }
}

Note: you don’t have to use “async” keyword for the data-fetching functions but this is how I always see it used.

More Static Site Generation (SSG) With getStaticProps

npm run build

See the key in the console

GSProps is good for personal blogs, build after each update.

Need updates even more frequently can still use getStaticProps with revalidation key set to update regularly after deployment

Exploring getServerSideProps

export async function getServerSideProps(context) {
  const req = context.req
  const res = context.res

  return {
    props: {
      meetups: DUMMY_MEETUPS   
    }
  }
}

Why we don’t want to use GSSP for every page? We have to wait for the page to be generated.

Note: I’m pretty sure this is still lightning fast though. I would guess it is similar to an express app built with with EJS templates.

Good for data that changes multiple times every second or you need access to the request object (for user-authentication for example) for example.

Working With Dynamic Path Params In getStaticProps

Using “getStaticProps” to get meetup data. Get id encoded in the URL but cannot use the router in the “gsProps” function but we also get access to a context object.

const meetupID = context.params.meetupId

Dynamic Pages & getStaticProps & getStaticPaths

Need “gsPaths” to tell NextJS which id values it needs to pre-generate the page during the build:

export async function getStaticPaths() {
  return {
    paths: [
      {
        params: {
          meetupId: "m1"
        }
      },
      {
        params: {
          meetupId: "m2"
        }
      }
    ]
  }
}

But of course we wouldn’t hard code this but instead get the paths programmatically. We must have a “paths” keys and a “fallback” key. The “fallback” key tells Next JS if the paths key describes all possible paths or just some of them. Fallback can be set to false, true, or blocking.

Introducing API Routes

Dummy data is obviously not realistic so we add a simple firebase backend.

Add an “api” folder that must be named “api” and contain files that act as Api routes.

function handler(req, res) {
  if (req.method === "POST") {
    const data = req.body

    const {title, image, address, description} = data
  }
}

export default handler

Connecting & Querying a MongoDB Database

Can get started with Atlas for free.

function handler(req, res) {
  if (req.method === "POST") {
    const data = req.body

    const client = await MongoClient.connect('connectionStringHere')
    const db = client.db()
    const meetupsCollection = await db.collection('meetups')
    const result = await meetupsCollection.insertOne(data)

    client.close()
    res.status(201).json({message: "insert successful"})
  }
}

export default handler

Skipping error handling in the code above.

Sending HTTP Requests To API Routes

Works the same as any react app.

function NewMeetupPage() {
  async function addMeetupHandler(enteredMeetupData) {
    const response = await fetch('/api/new-meetup', {
      method: "POST",
      body: JSON.stringify(enteredMeetupData),
      headers: {
        'Content-Type': 'application/json'
      }
    })

    const data = await response.json()
    console.log(data)

    // can use the useRouter hook here to navigate away
  }
}

Getting Data From The Database (For Page Pre-Rendering)

We could create use fetch inside “getStaticProps”, server-side code, in Next projects but for an internal API this is a redundant request.

We can import MongoClient and it will not make it into client-side bundle. Do all the db work directly inside the GSProps function. Could alternatively outsource to reusable function in a separate file and import in.

Note: Will get error related to auto-generated mongo id, (“Error serializing: Cannot be serialized as JSON. Please only return JSON serializable data types”). This is a more complex object that cannot be returned as data. We run a map on the array and run the “toString” method on the “meetup._id.”

Getting Meetup Detail Data & Paths

Inside of “getStaticPaths” run the database code to fetch the ids from each meetup document, getting just the ids back.

// note how we just get id back with the number one
const meetups = await meetupsCOllection.find({}, {_id: 1}).toArray()

Also fetch the selected meetup with the “findOne” method.

Now in the the main “MeetupDetails” page componentn function we can receive and drill into props.

Note: the same way we need to decode the auto-generated mongoID, when we search for a specific id pulled from the URL and used in database query we need to encode it from the string back to the Mongo “ObjectId” thing by importing “ObjectId” from the “mongodb” package and wrap your string with that.

const selectedMeetup = await meetupsCollection.findOne({
  _id: ObjectId(meetupId)
})

Adding “head” Metadata To Pages

We need to manually add some metadata including description and page title. Very simple with Next JS.

import Head from "next/head"

return (
  <Head>
    <title>React Meetups</title>
    <meta
      name="description"
      content="Browse a huge list of highly active React meetups"
    />
  </Head>
)

Can use a dynamic page title for each unique meetup using curly brackets as usual.

Instead of doing this for each page we can wrap in “_app.js” file

Deploying NextJS Projects

Vercel is optimized for Next JS. Connect Vercel with GitHub repo. May need to generate a personal access token.

Don’t need to run “npm run build” to get the “.next” folder when deploying to Vercel but if hosting on another provider or on your own server, run the build command, move the “.next” folder, and run “npm run start.”

Working With Fallback Pages & Re-Deploying

We get an error after deployment due to fallback being sent to “false.” When fallback is set to “true” or “blocking” it will generate the page on demand and then cache it. Setting fallback to “true” requires code to handle a loading state and “blocking” waits until the page is fully loaded and then goes there so you do not need a loading state.