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.