Next.js Tutorial Notes
Contents
1 Initializing a Next.js App
2 Navigate Between Pages
2.1 Pages in Next.js
2.2 Link Component
2.3 Client-Side Navigation
3 Assets, Metadata, and CSS
3.1 Assets
3.1.1 Image Component
3.2 Metadata
3.3 CSS Styling
3.4 Layout Component
3.5 Global Styles
4 Pre-rendering and Data Fetching
4.1 Pre-rendering
4.2 Two Forms of Pre-rendering
4.3 Static Generation with and without Data
4.4 Fetching Data at Request Time
4.4.1 Client-side Rendering
4.4.2 SWR
5 Dynamic Routes
5.1 Page Path Depends on External Data
5.2 Dynamic Routes Details
6 Conclusion
This blog post contains some notes I took following the Next.js tutorial at nextjs.org/learn/basics/create-nextjs-app, in which you walk through building a simple blog app.
Initializing a Next.js App
Initialize a Next.js app similarly to how you would with Create React App:
npx create-next-app nextjs-blog \
--use-npm --example \
"https://github.com/vercel/next-learn-starter/tree/master/navigate-between-pages-starter"
Navigate Between Pages
Pages in Next.js
Creating pages in Next.js involves writing JSX and using React components, instead of writing HTML.
To create a page in your app you need to define a JS file in the pages
directory in the root of your project.
The path to that file becomes the URL path.
Suppose I’ve got a blog built with Next.js at myblog.com
and my path
directory just contains an index.js
, then pages/index.js
will correspond to the /
route.
If I want to add a blog post I can simply create posts/first-post.js
in pages
with content similar to:
export default function FirstPost() {
return <h1>First Post</h1>
}
Now navigating to myblog.com/posts/first-post
will display that page.
Note: the component can have any name, but you must export it as a default
export.
Link Component
To link between pages you use the <Link>
component.
Instead of using <a href"...">
you use <Link href="...">
and put an <a>
tag inside.
So to link back to the homepage from the first-post
page edit pages/posts/first-post.js
to be:
import Link from 'next/link'
export default function FirstPost() {
return (
<>
<h1>First Post</h1>
<h2>
<Link href="/">
<a>Back to home</a>
</Link>
</h2>
</>
)
}
Client-Side Navigation
This part of the tutorial is when I started getting excited about Next.js because this section introduces some of the cool stuff Next.js does to optimize application performance.
Next.js uses client-side navigation which as the tutorial states means that “page transition happens using JavaScript, which is faster than the default navigation done by the browser [(which always reloads the entire page)]”.
Next.js performs code splitting automatically so that each page only loads what is necessary. So when you visit a new page only the code for that page is served, the code for other pages is not.
As a side benefit only loading the code of the page you requested isolates that page so that if it throws an error the rest of the application still works.
In production Next.js also performs prefetching. Whenever a <Link>
component appears in the browser’s viewport, Next.js prefetches the code for the linked page in the background. This way when you click on the link the code for the page will already be loaded making the page transition extra fast.
Assets, Metadata, and CSS
Assets
Next.js serves static assets under the top-level public
directory.
Image Component
Next.js provides an Image
component to handle things the regular HTML img
tag doesn’t.
With the Image
component, Next.js:
- Supports Image Optimization by default, which allows resizing and optimizing images in modern formats
- On-demand image optimization. Images are optimized when users request them so the number of images the app serves doesn’t impact build time.
- Lazy loading of images by default. Images are only loaded when they scroll into the viewport.
- Images are loaded in a way to avoid Cumulative Layout Shift.
Here’s an example using the Image
component:
import Image from 'next/image'
const YourComponent = () => (
<Image
src="/images/profile.jpg" // Route of the image file
height={144} // Desired size with correct aspect ratio
width={144} // Desired size with correct aspect ratio
alt="Your Name"
/>
)
Metadata
Next.js has a built in React component named <Head>
(uppercase H) which you can use like the regular <head>
HTML tag:
import Head from 'next/head'
export default function FirstPost() {
return (
<>
<Head>
<title>First Post</title>
</Head>
<h1>First Post</h1>
<h2>
<Link href="/">
<a>Back to home</a>
</Link>
</h2>
</>
)
}
CSS Styling
Next.js supports multiple CSS-in-JS libraries, you can also import .css
and .scss
files.
Layout Component
One way of applying style to elements in a React Component is to use the Layout
component provided by Next.js, along with CSS Modules.
CSS Modules:
- Allow you to import CSS in a React Component.
- Are CSS files that must end in
.module.css
. - Prevent class name collisions by automatically generating unique class names, scoping style at the component level.
- Are subject to Next.js’s code splitting feature, ensuring that only the CSS that is needed is loaded.
An example for how to use CSS Modules:
- Create a
components
directory at the root of your project - Create a
layout.module.css
file in thecomponents
directory with the style you want to apply, such as:
.container {
max-width: 36rem;
padding: 0 1rem;
margin: 3rem auto 6rem;
}
- Create a
layout.js
file incomponents
with content:
import styles from './layout.module.css'
export default function Layout({ children }) {
return <div className={styles.container}>{children}</div>
}
- Wrap the elements you want to style in your React component with the
Layout
component:
import Head from 'next/head'
import Link from 'next/link'
import Layout from '../../components/layout'
export default function FirstPost() {
return (
<Layout>
<Head>
<title>First Post</title>
</Head>
<h1>First Post</h1>
<h2>
<Link href="/">
<a>Back to home</a>
</Link>
</h2>
</Layout>
)
}
CSS Modules are useful for component-level styles, however Next.js supports another method to handle global CSS.
Global Styles
Next.js has an App
component it uses to initialize pages. This component can be overridden to control page initialization.
To load global CSS files, override the App
component by defining a file called pages/_app.js
:
import '../styles/global.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
Notes:
- In Next.js,
pages/_app.js
is the only place you can import global CSS. - The global CSS file can be placed anywhere (here it’s in a top-level directory called
styles
). - You need to restart the development server when adding
pages/_app.js
.
Pre-rendering and Data Fetching
Pre-rendering
Pre-rendering is one of the most important concepts in Next.js.
Pre-rendering results in better performance and SEO.
Next.js pre-renders every page by default.
Next.js generates HTML for each page in advance instead of having it done by client-side JavaScript.
If you disable JavaScript you will still be able to see the UI of a Next.js app because Next.js pre-renders the app into static HTML.
The above doesn’t imply that disabling JavaScript means you can still interact with a Next.js app because:
“Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. (This process is called hydration.)”
Two Forms of Pre-rendering
Next.js has two forms of pre-rendering: Static Generation and Server-side Rendering.
The difference between the two forms of pre-rendering is in when Next.js generates the HTML:
- Static Generation: the HTML is generated at build time (and reused on each request).
- Server-side Rendering: the HTML is generated on each request.
Next.js lets you choose which pre-rendering form to use for each page.
Note: in development, every page is rendered on each request (including those that use Static Generation).
Next.js recommends using Static Generation whenever possible.
Static Generation with and without Data
Static Generation can be done with and without data.
For pages without data, there’s nothing much to it, Next.js just generates the HTML for the pages at build time.
Obviously the above is not always the case, sometimes you will need to fetch external data before a page can be statically generated. Next.js supports Static Generation with data out of the box.
How is this done? When exporting a page component you can export an async
function called getStaticProps
which:
- Runs at build time in production.
- Allows you to fetch external data and send it as props to 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: ...
}
}
Note: in development, getStaticProps
runs on each request.
Fetching Data at Request Time
As mentioned above, if you need to fetch data at request time use Server-side Rendering.
getStaticProps
is only run at build time in production and so only runs server-side and never on the client.
You must export getServerSideProps
in order to use Server-side Rendering.
export async function getServerSideProps(context) {
return {
props: {
// props for your component
}
}
}
The context
parameter contains request specific parameters.
Client-side Rendering
Client-side Rendering can be used when you do not need to pre-render page data. This approach involves:
- Statically generating parts of the page that do not require external data.
- Once the page loads, fetch the data from the client using JavaScript and populate the remaining parts.
Example uses of this approach are for user dashboard pages because:
- The page is private so SEO is not relevant and the page doesn’t need to be pre-rendered.
- The data is updated frequently, which means the page must be updated on every request.
SWR
There’s a React hook for data fetching called SWR which Next.js highly recommends for fetching data on the client side because it handles lots of things such as caching, revalidation, focus tracking…
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>
}
Dynamic Routes
Page Path Depends on External Data
Previously we saw that with getStaticProps
we could fetch data required to render a page. Now we’ll see how we can do something similar for the page path using another async
function called getStaticPaths
.
If you want to statically generate a page at a path called /posts/<id>
where <id>
can be dynamic, then create a page at /pages/posts/[id].js
containing:
- A React component to render the page.
getStaticPaths
which returns an array of possible values forid
.getStaticProps
which fetches necessary data for a blog post with a givenid
.
import Layout from '../../components/layout'
export default function Post() {
return <Layout>...</Layout>
}
export async function getStaticPaths() {
// Return a list of possible value for id
}
export async function getStaticProps({ params }) {
// Fetch necessary data for the blog post using params.id
}
Dynamic Routes Details
In development getStaticPaths
runs on every request, in production it runs at build time.
Dynamic routes can be extended to catch all paths by adding three dots inside the brackets:pages/posts/[...id].js
matches /posts/a
as well as /posts/a/b
and /posts/a/b/c
and so on.
Conclusion
After my first encounter with Next.js I was left feeling excited to learn more and use it in future projects.
Next.js does so many cool things out of the box:
- code splitting
- prefetching
- client-side navigation
- pre-rendering
- dynamic routes
- …
Next.js also provides React components like the Image
component that do even more cool things for you like image optimization.
I forget why I chose to write my own static site generator to make this website… I’m thinking I might have to refactor the codebase to use Next.js now!