Next.js

ReactNext.js
ℹ️

Next.js is a flexible full-stack React framework that gives you building blocks to create fast web applications. It solves common problems and makes building React apps easier! 🌐 → https://nextjs.org/

⚠️

Unlike pure React - Next doesn’t just run in the browser, we need a server for the server-side rendering parts! You can, however, use next export to export to static HTML if you don’t need any of the server features.

👉🏽

API Routes - allow you to build an API that runs on the server

Next.js handles the tooling and configuration needed for React, and provides additional structure, features, and optimizations for your application.

You can use React to build your UI, then incrementally adopt Next.js features to solve common application requirements such as routing, data fetching, integrations - all while improving the developer and end-user experience.

Key Features:

  • File-based Routing (which makes it a bit of an alternative to React Router)

  • Server-side rendering + Data Fetching

Installation

Set it up from a template 🏆

$ npx create-next-app my-next-app --template default

Or go the complicated way and start from scratch…

$ npm install react react-dom next

run via $ npm next dev or add "dev": "next dev" to the scripts section of package.json, then you can do $ npm run dev to the same effect

Pages / Routing

⚠️

This will continue to work, but they just introduced the app directory as an even better way to do this (see further below).

ℹ️

A page is a react component in the pages directory.

Each page is automatically associated with a route based on its file name.

Example: If you create pages/about.js that exports a React component like below, it will be accessible at /about.


function About() {
  return <div>About</div>
}

export default About

Supports dynamic routes! If you create a file called pages/posts/[id].js, then it will be accessible at posts/1, posts/2, etc.


// pages/post/[pid].js
import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post

The path parameter will be merged with the query parameters, so /post/abc?foo=bar will have the following query object:


{ "foo": "bar", "pid": "abc" }

Catch-all routes: pages/post/[...slug].js matches /post/a, but also /post/a/b, /post/a/b/c and so on.

index > root: The router will automatically route files named index to the root of the directory.

  • pages/index.js → /

  • pages/blog/index.js → /blog

Shallow routing → Docs

Link between pages using the <Link> component:


import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/">Home</Link>
      </li>
      <li>
        <Link href="/about">About Us</Link>
      </li>
      <li>
        <Link href="/blog/hello-world">Blog Post</Link>
      </li>
    </ul>
  )
}

export default Home

Link to dynamic paths:


import Link from 'next/link'

function Posts({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${encodeURIComponent(post.slug)}`}>
            {post.title}
          </Link>
        </li>
      ))}
    </ul>
  )
}

export default Posts

app Directory / NEW Router

Pre-rendering Pages

By default, Next.js pre-renders every page. Two ways to choose from:

  • Static Generation (recommended) - HTML is generated **at build-time. **To make a page use Static Generation, either export the page component, or export getStaticProps (and getStaticPaths if necessary)

    • without data - static page, just renders to one html file

    • with data:

      • Your page content depends on external data: Use getStaticProps.

      • Your page paths depend on external data: Use getStaticPaths (usually in addition to getStaticProps).

  • Server-side rendering - HTML is generated on each request

    • To use Server-side Rendering for a page, you need to export an async  function called getServerSideProps. This function will be called by the server on every request.
  • All this is great, but can cause trouble if you’re not careful! (Unfold for details)

Data Fetching

[SWR]

Built-in CSS Support

Next.js extends the concept of import beyond JavaScript.

Global Stylesheet: will be applied to everything, import inside **pages/_app.js**** **via import '../styles.css' (+ will be hot-reloaded during dev!)

Component-Level CSS: [name].module.css naming schema. CSS Modules locally scope CSS by automatically creating a unique class name. This allows you to use the same CSS class name in different files without worrying about collisions.

  • Example

Styled JSX

Styled JSX is an easy way to throw some CSS int he mix → https://github.com/vercel/styled-jsx#global-styles

For example: if you need to change the style of the body element, which is usually the same for every page, so you can’t get to it that easily per page… but just do this in any JSX and you’re good:


<style jsx global>{`
body {
    background: #e5e5e5;
}
`}</style>

SASS

SASS support: npm install --save-dev sass, add sassOptions to next.config.js → Docs

Layouts

You can do a single shared layout for the entire app by adding it to pages/_app.js:


// pages/_app.js

import Layout from '../components/layout'

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

pages/_app.js is special. <Component> is always the current page being rendered, so if you wrap your Layout around it, it will always be there.

Or we can do per page layouts like this**:**


// pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return {
    /** Your content* /
  }
}

Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>)
}



// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}


Image Optimization

Next.js will automatically determine the width and height of your image based on the imported file.

Local images:

  • Use the next/image <Image> component: import Image from 'next/image';

  • static-import your image: import profilePic from '../public/me.png';


import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} automatically provided
        // height={500} automatically provided
        // blurDataURL="data:..." automatically provided
        // placeholder="blur" // Optional blur-up while loading
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

Remote images:

Next.js does not have access to remote files during the build process, so you'll need to provide the width, height and optional blurDataURL props manually:


import Image from 'next/image'

export default function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src="/me.png"
        alt="Picture of the author"
        width={500}
        height={500}
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

You should add the priority property to the image that will be the Largest Contentful Paint (LCP) element for each page.

Font Optimization

Next inlines font CSS during next build

To add a web font to your Next.js application, add the font to a Custom Document.


// pages/_document.js

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head>
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
          rel="stylesheet"
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

Serve static files

Just put it into the public folder. Then. you can simply do this:


import Image from 'next/image'

function Avatar() {
  return <Image src="/me.png" alt="me" width="64" height="64" />
}

export default Avatar

This folder is also useful for robots.txt, favicon.ico, Google Site Verification, and any other static files (including .html)!

Environment Variables

Next.js has built-in support for loading environment variables from .env.local into process.env.

An example .env.local:


DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword

This loads process.env.DB_HOST, process.env.DB_USER, and process.env.DB_PASS into the Node.js environment automatically allowing you to use them in Next.js data fetching methods and API routes.

For example, using getStaticProps:


// pages/index.js
export async function getStaticProps() {
  const db = await myDB.connect({
    host: process.env.DB_HOST,
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
  })
  // ...
}

Note: Next.js will automatically expand variables ($VAR) inside of your .env* files. This allows you to reference other secrets, like so:


# .env
HOSTNAME=localhost
PORT=8080
HOST=http://$HOSTNAME:$PORT

API Routes

Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size.

For example, the following API route pages/api/user.js returns a json response with a status code of 200:


export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}

For an API route to work, you need to export a function as default (a.k.a request handler), which then receives the following parameters:

To handle different HTTP methods in an API route, you can use req.method in your request handler, like so:


export default function handler(req, res) {
  if (req.method === 'POST') {
    // Process a POST request
  } else {
    // Handle any other HTTP method
  }
}

Dynamic API Routes

Dynamic routes follow the same file naming rules as pages.

For example, the API route pages/api/post/[pid].js has the following code:


export default function handler(req, res) {
  const { pid } = req.query
  res.end(`Post: ${pid}`)
}

Now, a request to /api/post/abc will respond with the text: Post: abc.


Leave a comment

Your contact detail is only visible to me. Comments are approved by hand.