Let’s build an Events website with Next.js: Intro to Next.js
Tags: nextjsreact
Published: 2021-08-03 13:54:59

---
title: Let’s build an Events website with Next.js: Intro to Next.js
published: true
date: 2020-12-08 18:25:18 UTC
tags: nextjsreact
canonical_url: https://www.thisdot.co/blog/lets-build-an-events-website-with-next-js-intro-to-next-js
---

# Let’s build an Events website with Next.js: Intro to Next.js

This is the first article of a series of posts where I will write about [Next.js](https://nextjs.org), the most complete framework for writing React.js applications. Through this guide, you will learn the basics of Next.js, as well as how to integrate it with [GraphCMS](https://graphcms.com/), the GraphQL native headless CMS.

## What is Next.js?

Next.js is a web framework for React.js, built and maintained by the [Vercel](https://vercel.com) team.

With Next.js, you can create both **server-rendered** and **statically-generated** websites, with out-of-the-box support for **internationalization** , **image optimization** , **routing** , **TypeScript support** , **CSS and Sass support** , **code splitting** , **Serverless API Routes** , and more. All of the above is offered out-of-the-box, with no further config needed.

## Core concepts

### File-system Routing

Unlike other React applications, where you may need to import a routing library, and write a routes config file associating each component with a route path, in Next.js it is a simple as adding files to the `pages` directory and defaultly exporting a React component. There, the route path will be the filename. (i.e. `pages/index.js`, `pages/about.js` `pages/blog/index.js`)

Matching dynamic parameterized routes is also possible by wrapping the filename with a bracket syntax (i.e. `pages/blog/[slug].js`) and _catch-all_ routes, which allows it to have as many subpaths as possible (i.e. `pages/parametrized/[...paths].js`).

The following file structure:

```
.
|- pages/
|- |- about.js
|- |- blog/index.js
|- |- blog/[slug].js
|- |- index.js
|- |- parametrized/[...paths].js
```

Will match the following paths:

- `pages/about.js` => `https://my-website.com/about`
- `pages/blog/index.js` => `https://my-website.com/blog`
- `pages/blog/[slug].js` => `https://my-website.com/blog/hello-world`, `https://my-website.com/blog/my-first-post`, `https://my-website.com/blog/hello-world`
- `pages/index.js` => `https://my-website.com`
- `pages/parametrized/[...paths].js` => `https://my-website.com/parametrized/a`, `https://my-website.com/parametrized/a/b`, `https://my-website.com/parametrized/a/b/c` ... and so on.

### Data fetching

There is two ways to fetch data from a Next.js application. You can either **generate static pages** at build time, or fetch data **from the server** at run time.

#### 1. Static pages generation

It is the recommended way to fetch **public** data coming from any data source. This can be a CMS, a database, or even a local or remote JSON file. One of the benefits of generating pages at build time is that you can put a CDN in front of it, and serve the already generated assets to the user.

To set up a page to be statically generated, export an asynchronous `getStaticProps` function from it, returning an object with a nested `props` object, which will be passed to the route component.

**`pages/blog/index.js`**

```
import cms from 'lib/cms'

export async function getStaticProps() {
  const posts = await cms.getPosts()
  /**
  [
    {
      slug: 'hello-world',
      title: 'Hello World',
      body: 'This is the hello world blog post'
    },
    {
      slug: 'my-first-post',
      title: 'My first post',
      body: 'This is my first post'
    },
    {
      slug: 'my-second-post',
      title: 'My second post',
      body: 'This is my second post'
    }
  ]
  */

  return {
    props: {
      posts
    }
  }
}

export default function BlogHomePage(props) {
  return (
    <ul>
      {props.posts.map(post => (
        <li key={post.slug}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </li>
      ))}
    </ul>
  )
}
```

When working with dynamic routes, an extra asynchronous function `getStaticPaths` needs to be exported, which needs to return an object with a `fallback` property (this will be explained later), and a nested `paths` array. Each item of the array needs to be an object with a nested `params` object, which will be received by `getStaticProps` to know which object to fetch.

**`pages/blog/[slug].js`**

```
import cms from 'lib/cms'

export async function getStaticPaths() {
  const posts = await cms.getPosts()
  /**
  [
    {
      slug: 'hello-world',
      title: 'Hello World',
      body: 'This is the hello world blog post'
    },
    {
      slug: 'my-first-post',
      title: 'My first post',
      body: 'This is my first post'
    },
    {
      slug: 'my-second-post',
      title: 'My second post',
      body: 'This is my second post'
    }
  ]
  */

  const paths = posts.map(post => ({ 
    params: { slug: post.slug }
  }))
  /**
  [
    { slug: 'hello-world' },
    { slug: 'my-first-post' },
    { slug: 'my-second-post' }
  ]
  */

  return {
    paths,
    fallback: false
  }
}

export async function getStaticProps(context) {
  const post = await cms.getPostBySlug(context.params.slug)
  /**
  {
    slug: 'hello-world',
    title: 'Hello World',
    body: 'This is the hello world blog post'
  }

  (the above can change depending on the requested slug)
  */

  return {
    props: {
      post
    }
  }
}

export default function BlogPostPage(props) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  )
}
```

**Note:** the above will generate all blog posts **at build time**. If you don't want to generate _all_ blog post static pages at build time, you can opt-in to **Incremental Static Regeneration** , which will be explained later on this article.

#### 2. Server-side rendering

The other technique to hydrate a page with dynamic data is to render it from the server. This can be useful when working with **private** pages that can't be put on a CDN, and the data will change according to the request context. For that, an asynchronous function called `getServerSideProps` will need to be exported from the route component, returning an object with a nested `props` object.

**`pages/profile.js`**

```
import db from 'lib/db'

export async function getServerSideProps(context) {
  const currentUserId = getCurrentUserIdFromCookies(context)

  const user = await db.getUserById(currentUserId)
  /* 
  {
    name: 'John Doe',
    somePrivateData: 'foo'
  }
  */

  return {
    props: {
      user
    }
  }
}

export default function ProfilePage(props) {
  return (
    <div>
      <h1>{props.user.name}</h1>
      <h2>Private data:</h2>
      <p>{props.user.somePrivateData}</p>
    </div>
  )
}
```

**Note:** one of the caveats of server-side rendering is a page using it can't be cached by a CDN without additional configuration, and your page relies on a long-life server to be running. A workaround for this is to just export a component from the page route file, and do the fetching client-side using `fetch` either on a `useEffect` hook, or using a data fetching library such as [`SWR`](https://swr.vercel.app).

### API Routes

If you want to create serverless API endpoints for external consumers, or for client-side fetching, you can add API routes files to an `api` directory within the `pages` directory exporting a handler that receives a [`req`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and a [`res`](https://nodejs.org/api/http.html#http_class_http_serverresponse) argument. It can also receive dynamic and catch-all parameters, just like a regular app route.

**`pages/api/profile.js`**

```
import db from 'lib/db'

export default async function profileApiRoute(req, res) {
  const currentUserId = getCurrentUserIdFromCookies(req)

  const user = await db.getUserById(currentUserId)
  /* 
  {
    name: 'John Doe',
    somePrivateData: 'foo'
  }
  */

  res.json({
    user
  })
}
```

The above will create an API endpoint at https:my-website.com/api/profile that can be consumed from anywhere.

```
import { useState, useEffect } from 'react'

export default function ProfilePage() {
  const [user, setUser] = useState(undefined)

  useEffect(() => {
    const fetchUser = async () => {
      const res = await fetch('/api/profile')
      const json = await res.json()
      setUser(json.user)
    }
  }, [])

  if (user === undefined) {
    return <p>Fetching user...</p>
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <h2>Private data:</h2>
      <p>{user.somePrivateData}</p>
    </div>
  )
}
```

## Moving forward

The above core concepts are just a small taste of what Next.js can offer. In the upcoming articles in this series, you will continue learning how to use more techniques like image optimization, Incremental Static Regeneration, and Internationalization. Stay tuned!

_This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com)._

_This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co)._