A Next.js cheat sheet repository
| Project | Description |
|---|---|
| next-js-example-app | A bare-bone example app with local data |
- Create a new Next.js app
- ESLint
- Manual Installation
- Folder Structure
- Routing
- Meta tags
- The
_app.jsfile - The
Layoutcomponent - Sass
- Tailwind CSS
- Styled JSX
- The
_document.jsfile - The
Imagecomponent - The
Scriptcomponent - Fetch data
- Example of using
getStaticPathsandgetStaticPropstogether - Fetch Data on the client
- SWR
- When to use Static Generation v.s. Server-side Rendering
- Dynamic routes
- Custom 404 pages
- Export Static Site
- API Routes
- Check for
developmentmode orproductionmode - Custom Meta Component
- useRouter Hook
- useRouter Redirect
- Redirects
npx create-next-appnpx create-next-app --typeScript --eslint --use-npmAdd the following to the .eslintrc.json file
{
// "extends": ["next/core-web-vitals"]
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"plugin:@next/next/recommended",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"env": {
"es6": true
}
}- Add Next.js to your project
npm install next react react-dom- Add the following scripts to your package.json
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}Pages folder - is the only required folder in a Next.js app. All the React components inside pages folder will automatically become routes
Note: The name of the file will be the route name, use lowercase for the file name and PascalCase for the component name
Public folder - contains static assets such as images, files, etc. The files inside public folder can be accessed directly from the root of the application
Styles folder - contains stylesheets, here you can add global styles, CSS modules, etc
Usually
globals.cssis imported in the_app.jsfile
Components folder - contains React components
The @ alias is used to import files from the root of the project
import Header from '@/components/Header'To use the @ alias, add the following to the jsconfig.json file at the root of the project
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["*"]
}
}
}- Link - is used for client-side routing. It is similar to the HTML
<a>tag
import Link from 'next/link'
export default function Home() {
return (
<div>
<Link href='/about'>About</Link>
</div>
)
}- Head - is used to add meta tags to the page
import Head from 'next/head'
export default function Home() {
return (
<div>
<Head>
<title>My page title</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
</div>
)
}The
Headcomponent should be placed inside theLayoutcomponent or inside the_app.jsfile
- Import the
Headcomponent and put thetitletag inside it
Wrap around each page and here is where you would import global styles and put header and footer components
Note: You could also put the header and footer components inside the
Layoutcomponent
- Create a
Layoutcomponent and wrap around each page with children prop
import Header from '@/components/Header'
import Footer from '@/components/Footer'
export default function Layout({ children }) {
return (
<div>
<Header />
{children}
<Footer />
</div>
)
}- Import the
Layoutcomponent in the_app.jsfile
import Layout from '@/components/Layout'
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
export default MyAppNext.js has built-in support for Sass
- Install
sass
npm i -D sass- Install
tailwindcss
npm install -D tailwindcss autoprefixer postcss- Create a
tailwind.config.jsfile at the root of the project
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
}Note: If you are using the
srcfolder, change the path to./src/pages/**/*.{js,ts,jsx,tsx}and./src/components/**/*.{js,ts,jsx,tsx}
- Create a
postcss.config.jsfile at the root of the project
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}- Add the following to
globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;- Import
globals.cssin the_app.jsfile
import '@/styles/globals.css'Styled JSX is a CSS-in-JS library that allows you to write CSS inside a React component
It has two modes: global and scoped
- Global - styles are applied globally to the entire application
export default function Home() {
return (
<>
Your JSX here
<style jsx global>{`
p {
color: red;
}
`}</style>
</>
)
}- Scoped - styles are applied only to the component
export default function Home() {
return (
<>
Your JSX here
<style jsx>{`
p {
color: red;
}
`}</style>
</>
)
}Note: If in vs-code the syntax highlighting for the
styletag is not working, you can install thevscode-styled-componentsextension to fix thisBe sure that the curly braces are on the same line as the
styletag:<style jsx>{No need to use styled jsx if you use other methods like CSS modules or styled components
Here you can customize the html and body tags
For instance you can add a
langattribute to thehtmltag
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang='en'>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}Note: This file will be created if you create a new Next.js app with
npx create-next-app
You can use the Image component to add images
The images will be optimized automatically
import Image from 'next/image'
export default function Home() {
return (
<div>
<Image src='/images/profile.jpg' width={144} height={144} />
</div>
)
}Note: src, width and height are required, alt is recommended
- if you use a remote image, you need to add the domain to the
next.config.jsfile
images: {
domains: ['images.pexels.com'],
},- or in Next.js 12.4.0:
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},You can use the Script component to add scripts
import Script from 'next/script'
export default function Home() {
return (
<div>
<Script src='https://code.jquery.com/jquery-3.6.0.min.js' />
</div>
)
}Note: you can add cdn scripts as well as local scripts in the
publicfolder
Next.js let's you choose how to fetch data for each page. It is advised to use getStaticProps for most of the pages and getServerSideProps for pages with frequently updated data
- getStaticProps - is used to fetch data at build time
Note: During development with
npm run dev,getStaticPropsruns on every request
getStaticPropscan only be exported from a page. You can't export it from non-page files
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
}
}
postswill be passed to the component as a prop:
export default function Home({ posts }) {
return (
<div>
{posts.map((post) => (
<h3>{post.title}</h3>
))}
</div>
)
}- getStaticPaths - is used to specify dynamic routes to pre-render pages based on data
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: false }
}Note: When
fallbackisfalse, any paths not returned bygetStaticPathswill result in a 404 pageIf
fallbackistrue, then when a user visit a page that is not pre-rendered, Next.js will generate the page on the fly and return it to the user (useful for sites with frequently updated data like a social network)
- getServerSideProps - is used to fetch data on the server on each request
export async function getServerSideProps(context) {
return {
props: {
// props for your component
},
}
}
getStaticPropsandgetServerSidePropshave acontextparameter that contains the urlparamsobjectYou can use this to fetch data for a specific post (e.g.
context.params.id)
Use getStaticPaths to fetch an array of IDs and use getStaticProps to fetch data for each product based on the ID
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return {
props: {
post,
},
}
}Sometimes it can be beneficial to fetch data on the client instead of on the server.
For example, you could fetch all the static data on the server and then fetch the dynamic data on the client such as a user-specific data that changes frequently and is not needed for SEO.
- useEffect - is used to fetch data on the client
import { useEffect, useState } from 'react'
export default function Home() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('https://.../posts')
.then((res) => res.json())
.then((data) => setPosts(data))
}, [])
return (
<div>
{posts.map((post) => (
<h3 key={post.id}>{post.title}</h3>
))}
</div>
)
}SWR is a React Hooks library for remote data fetching on the client
You should use it instead of
useEffect
import useSWR from 'swr'
export default function Home() {
const { data, error } = useSWR('api/user', fetch)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return (
<>
{data.map((post) => (
<h3 key={post.id}>{post.title}</h3>
))}
</>
)
}Use Static Generation whenever possible because it's much faster than Server-side Rendering and the page can be served by CDN.
You should ask yourself:
- Can I pre-render this page ahead of a user's request?
If the answer is yes, then you should choose Static Generation.
- Does the page need to update frequently?
If the answer is yes, then you should choose Server-side Rendering.
You can use Static Generation for many types of pages, including:
- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation
You could also skip Server-side Rendering and use client-side JavaScript to fetch data with useEffect
-
Create a folder inside the
pagesfolder with the name of the dynamic route in square brackets (e.g.[id]) -
Create an
index.jsfile inside the dynamic route folder
- Create a link with that points to the dynamic route and pass the dynamic value as a prop
import Link from 'next/link'
export default function Post({ post }) {
return (
<div>
<Link href='/posts/[id]' as={`/posts/${post.id}`}>
<a>{post.title}</a>
</Link>
</div>
)
}Note: this is usually done inside a
mapfunction
Dynamic routes can be extended to catch all paths by adding three dots (...) inside the brackets. For example:
pages/posts/[...id].jsmatches/posts/a, but also/posts/a/b,/posts/a/b/cand so on.
If you do this, in getStaticPaths, you must return an array as the value of the id key like so:
return [
{
params: {
// Statically Generates /posts/a/b/c
id: ['a', 'b', 'c'],
},
},
//...
]And params.id will be an array in getStaticProps:
export async function getStaticProps({ params }) {
// params.id will be like ['a', 'b', 'c']
}- Create a
404.jsfile inside thepagesfolder
export default function Custom404() {
return <h1>404 - Page Not Found</h1>
}Note: You can also create a
500.jsfile for the server error page
Export a static site with next export
Add an npm script to the
package.jsonfile:
"scripts": {
"export": "next build && next export"
}Run the script:
npm run exportThe static site will be exported to the
outfolderYou can deploy this folder to any static site host such as GitHub Pages
- Install
serve
npm i -g serve- Run the server
serve -s out -p 8000You can work with any database in the pages/api/ folder
Note: Any API route that is placed inside this folder will be accessible like any other page in Next.js
- Create a folder inside the
pagesfolder with the name of the API route (e.g.api/posts)
- Create a
data.jsfile at the root of the project
const posts = [
{
id: 1,
title: 'Post 1',
},
{
id: 2,
title: 'Post 2',
},
{
id: 3,
title: 'Post 3',
},
]- Import the data in the API route
import { posts } from '@/data'- Get the data
export default function handler(req, res) {
res.status(200).json(posts)
}You can now fetch the data as you would with any other API
Note: Next.js needs absolute paths when fetching data
Since Next.js needs absolute paths when fetching data, you can check if you are in development mode or production mode
- Create a
config.jsfolder at the root of the project with anindex.jsfile inside
const dev = process.env.NODE_ENV !== 'production'
export const server = dev ? 'http://localhost:3000' : 'https://yourwebsite.com'- Now you can use
serveras a variable in your code as an absolute path when fetching data
import { server } from '@/config'
export default function handler(req, res) {
fetch(`${server}/api/posts`)
.then((res) => res.json())
.then((data) => res.status(200).json(data))
}Note: There is no need to create a custom meta component since we can use the
Headcomponent from Next.js
A meta component is used to add meta tags to the head of the document
- Create a
Meta.jsfile inside thecomponentsfolder
import Head from 'next/head'
export default function Meta({ title, keywords, description }) {
return (
<Head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
<meta name='keywords' content={keywords} />
<meta name='description' content={description} />
<title>{title}</title>
</Head>
)
}Tip: You can also use packages such as
next-seofor this
- Add
defaultPropsto theMetacomponent so that you don't need to add props to it every time you use it
Meta.defaultProps = {
title: 'WebDev News',
keywords: 'web development, programming',
description: 'Get the latest news in web dev',
}- Now you can use the
Metacomponent in any page (it is common to use it in theLayoutcomponent)
import Meta from '@/components/Meta'
export default function Layout({ children }) {
return (
<div>
<Meta />
{children}
</div>
)
}- Import the
Metacomponent in the specific page and pass the title as a prop
import Meta from '@/components/Meta'
export default function About() {
return (
<div>
<Meta title='About' />
<h1>Welcome to the About Page</h1>
</div>
)
}useRouter is a hook that gives you access to the router object
- Import the
useRouterhook
import { useRouter } from 'next/router'- Use the
useRouterhook
const router = useRouter()- Get the query
const router = useRouter()
const { query } = router- Get the query with destructuring
const {
query: { id },
} = useRouter()useRouter main properties:
pathname- Current route. That is the path of the page inpagesroute- Current route with the query stringquery- Query string section of URL parsed as an objectasPath- String of the actual path (including the query) shown in the browser
- Import the
useRouterhook
import { useRouter } from 'next/router'- Use the
useRouterhook to redirect the user to home page
const router = useRouter()
router.push('/')Note: You can for instance use this hook in a
404page to redirect the user to the home page after 3 seconds
To redirect a user to another page, you can use redirects on next.config.js
module.exports = {
async redirects() {
return [
{
source: '/about',
destination: '/',
permanent: false,
},
]
},
}Note:
permanent: truewill tell the browser to cache the redirect forever. That means that if the user goes to the/aboutpage, the browser will redirect the user to the/page without making a request to the serverTIP: Do not use
permanent: truefor redirects that are not permanent
308- Permanent Redirect307- Temporary Redirect
Note:
308replaces301,307replaces302