Skip to content

emanuelefavero/next-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 

Repository files navigation

Next JS

A Next.js cheat sheet repository

Example Projects

Project Description
next-js-example-app A bare-bone example app with local data

Table of Contents

Create a new Next.js app

npx create-next-app

Use TypeScript, ESLint and npm

npx create-next-app --typeScript --eslint --use-npm

ESLint

Add 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
  }
}

Manual Installation

  • 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"
}

Folder Structure

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.css is imported in the _app.js file

Components folder - contains React components

The @ alias

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": {
      "@/*": ["*"]
    }
  }
}

Routing

  • 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>
  )
}

Meta tags

  • 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 Head component should be placed inside the Layout component or inside the _app.js file

Give a different title to each page

  • Import the Head component and put the title tag inside it

The _app.js file

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 Layout component

The Layout component

  • Create a Layout component 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 Layout component in the _app.js file
import Layout from '@/components/Layout'

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

export default MyApp

Sass

Next.js has built-in support for Sass

  • Install sass
npm i -D sass

Tailwind CSS

  • Install tailwindcss
npm install -D tailwindcss autoprefixer postcss
  • Create a tailwind.config.js file 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 src folder, change the path to ./src/pages/**/*.{js,ts,jsx,tsx} and ./src/components/**/*.{js,ts,jsx,tsx}

  • Create a postcss.config.js file 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.css in the _app.js file
import '@/styles/globals.css'

Styled JSX

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 style tag is not working, you can install the vscode-styled-components extension to fix this

Be sure that the curly braces are on the same line as the style tag: <style jsx>{

No need to use styled jsx if you use other methods like CSS modules or styled components

The _document.js file

Here you can customize the html and body tags

For instance you can add a lang attribute to the html tag

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

The Image component

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.js file
images: {
    domains: ['images.pexels.com'],
  },
  • or in Next.js 12.4.0:
images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/account123/**',
      },
    ],
  },

The Script component

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 public folder

Fetch Data

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, getStaticProps runs on every request

getStaticProps can 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,
    },
  }
}

posts will 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 fallback is false, any paths not returned by getStaticPaths will result in a 404 page

If fallback is true, 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
    },
  }
}

getStaticProps and getServerSideProps have a context parameter that contains the url params object

You can use this to fetch data for a specific post (e.g. context.params.id)

Example of using getStaticPaths and getStaticProps together

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,
    },
  }
}

Fetch Data on the client

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

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>
      ))}
    </>
  )
}

When to use Static Generation v.s. Server-side Rendering

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

Dynamic Routes

  • Create a folder inside the pages folder with the name of the dynamic route in square brackets (e.g. [id])

  • Create an index.js file inside the dynamic route folder

Dynamic Links

  • 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 map function

Catch All Routes

Dynamic routes can be extended to catch all paths by adding three dots (...) inside the brackets. For example:

  • pages/posts/[...id].js matches /posts/a, but also /posts/a/b, /posts/a/b/c and 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']
}

Custom 404 pages

  • Create a 404.js file inside the pages folder
export default function Custom404() {
  return <h1>404 - Page Not Found</h1>
}

Note: You can also create a 500.js file for the server error page

Export Static Site

Export a static site with next export

Add an npm script to the package.json file:

"scripts": {
  "export": "next build && next export"
}

Run the script:

npm run export

The static site will be exported to the out folder

You can deploy this folder to any static site host such as GitHub Pages

Build a local server to test the static site

  • Install serve
npm i -g serve
  • Run the server
serve -s out -p 8000

API Routes

You 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 pages folder with the name of the API route (e.g. api/posts)

Work with local data

  • Create a data.js file 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

Check for development mode or production mode

Since Next.js needs absolute paths when fetching data, you can check if you are in development mode or production mode

  • Create a config.js folder at the root of the project with an index.js file inside
const dev = process.env.NODE_ENV !== 'production'

export const server = dev ? 'http://localhost:3000' : 'https://yourwebsite.com'
  • Now you can use server as 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))
}

Custom Meta Component

Note: There is no need to create a custom meta component since we can use the Head component from Next.js

A meta component is used to add meta tags to the head of the document

  • Create a Meta.js file inside the components folder
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-seo for this

  • Add defaultProps to the Meta component 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 Meta component in any page (it is common to use it in the Layout component)
import Meta from '@/components/Meta'

export default function Layout({ children }) {
  return (
    <div>
      <Meta />
      {children}
    </div>
  )
}

Add a specific title to a page

  • Import the Meta component 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 Hook

useRouter is a hook that gives you access to the router object

  • Import the useRouter hook
import { useRouter } from 'next/router'
  • Use the useRouter hook
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 in pages
  • route - Current route with the query string
  • query - Query string section of URL parsed as an object
  • asPath - String of the actual path (including the query) shown in the browser

useRouter Redirect

  • Import the useRouter hook
import { useRouter } from 'next/router'
  • Use the useRouter hook to redirect the user to home page
const router = useRouter()
router.push('/')

Note: You can for instance use this hook in a 404 page to redirect the user to the home page after 3 seconds

Redirects

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: true will tell the browser to cache the redirect forever. That means that if the user goes to the /about page, the browser will redirect the user to the / page without making a request to the server

TIP: Do not use permanent: true for redirects that are not permanent

Redirects HTTP status codes

  • 308 - Permanent Redirect
  • 307 - Temporary Redirect

Note: 308 replaces 301, 307 replaces 302

 


 

Go To Top   ⬆️

Releases

No releases published

Packages

No packages published