An Introduction To NextJS

What is NextJS?

NextJS is a “meta framework” for React. It provides tools and conventions that makes it easier for you as a developer. Looking at their website they write:

The React Framework for Production

Lots of concepts goes in to this statement. It’s one thing to develop your application on your computer where the conditions are optimal. When taking your application live to a production environment there are many things to consider. That NextJS will help you with.

When using a classic create-react-app you get all the things needed from React to build a full scale application. But when putting this to production you will encounter other issues that you need to handle. Rendering is one of them. A regular React application is always rendered on the client, referred to as Client Side Rendering (CSR).

NextJS provides extra rendering options such as Server Side Rendering (SSR), Static Site Generation (SSG) and Incremental Site Regeneration (ISR). I will not go into detail about each in this article but those are the main reasons to use NextJS, I would say. Makes it easier to manage pages and components based on the rendering strategy. And you can choose which one to use depending on your specific project needs.

NextJS also provides other features such as bundling, optimised assets for production, code splitting, API routes, TypeScript support and more. All there for you as a developer to make your life easier.

Getting started

Open a terminal and run either this npm or yarn script.

npx create-next-app@latest

or

yarn create next-app

If you want your project to be in TypeScript you can add the --typescript flag after the initial command and you will have all the support and configuration files included in the boilerplate.

This will give you an application with a simple project structure.

If you open the newly created folder and run npm run dev or yarn dev you will get an app running on localhost.

Basic concepts

NextJS provides many features out of the box. These things will make your life easier as a developer and you will create better applications.

Pages

The most important feature is the /pages folder. This magic folder lets you create files that will turn into pages in your application. For example adding the file /pages/info will generate the path /info. Try to create this page and add the following:

export default function Info() {
  return <div>Information content</div>;
}

Then run your application with npm run dev or yarn dev. The new page is then accessible at localhost:3000/info. You can create pages by filename, but you can also create a subfolder that will map to subpaths. Another approach to the page above is to create a folder called /info and have a index.js file exporting the same code. This also opens up the option to create other files in the /info folder. If we were to add a new file called contact.js to the /info folder it would map to the path /info/contact.

This works great with static content. What if we want to fetch some data from an api and render it?

Fetching data

NextJS provides different ways of fetching data depending on what type of application or specific page you have. The pages could be exported as static HTML pages, i.e. created at build time, or created on every request. Both ways will serve pre-rendered content and complete HTML files. Along with minimal JavaScript that is only needed to make the page interactive. You might have heard about the term hydration. This is the process of making a static HTML page interactive by applying the JavaScript code served alongside. This is the recommended approach to use, it has advantages on both performance and SEO. Although not being applicable in all situations.

NextJS provides 3 functions that you need to understand to be working with dynamic data.

  • getServerSideProps
  • getStaticProps
  • getStaticPaths

When working with these features I always prefer to work with a real API. There are lots of free API:s to use as a developer. One of my favourites is the Star Wars API (https://swapi.dev/).

Lets create a page that will show a list of Planets from the Star Wars universe. The data for this could be fetched from the API with https://swapi.dev/api/planets. This will return a list of planets with lots of information about each.

Lets create a new folder called /planets inside our pages folder. Inside that folder we create an index.js file.

Lets add the following HTML:

function PlanetsPage({ planets }) {
  return (
    <main>
      <h1>Planets</h1>
      {planets.map((planet) => (
        <div key={planet.name}>
          <h2>{planet.name}</h2>
          <p>Population: {planet.population}</p>
          <p>Terrain: {planet.terrain}</p>
        </div>
      ))}
    </main>
  );
}

This expect props to have an array of planets with name, population and terrain information. Lets get this data from the Star Wars API. For this we use the getServerSideProps function. To get this to work you only have to export a function called getServerSideProps from the page file. Put this above or below the PlanetsPage function.

export async function getServerSideProps() {
  const res = await fetch('https://swapi.dev/api/planets');
  const data = await res.json();

  return { props: { planets: data.results } };
}

Here two things are happening:

  1. Data is being fetched from the example API
  2. An object with a props property is returned from the function

What we return as props will be available as props in the PlanetsPage function. This is magically handled by NextJS.

Now when navigating to localhost:3000/planets you should see a list of planets.

When using getServerSideProps the HTML document will be created on every request. The example data we are working with is very static and is perfect to be statically generated. For this we can use the getStaticProps function. It works the same as getServerSideProps and the only thing we need to do is to replace the function name.

export async function getStaticProps() {
  const res = await fetch('https://swapi.dev/api/planets');
  const data = await res.json();

  return { props: { planets: data.results } };
}

This should work exactly the same as before. The difference is this will create the HTML pages at build time. You can try this out and see for yourself. If you run npm run build with getStaticProps you can see the generated HTML page at .next/server/pages/planets.html. Changing the method to getServerSideProps, deleting the .next folder and run npm run build again. The same HTML file will not be created. Being able to decide what methods to use means you can use statically generated pages on some pages and server side rendered on others. This is a really powerful feature.

Dynamic data

It’s often common to have dynamic paths depending on the content being fetched. A blog post might have a specific slug for example. In this example we want to add a page for a specific planet. We can fetch a planet by adding an id to the path (https://swapi.dev/api/planets/1). In our application we then want to add a path for /planets/1. To achieve this we will create a new page under the planets folder and use the function getStaticPaths. This needs to be in combination with getStaticProps to work.

Create a file called /planets/[id].js. The square bracket notion means a dynamic path.

First add the HTML part that only requires the data for one planet this time.

function PlanetPage({ planet }) {
  return (
    <main>
      <h1>{planet.name}</h1>
      <p>Population: {planet.population}</p>
      <p>Terrain: {planet.terrain}</p>
    </main>
  );
}

Then we add the getStaticProps to get the data for one specific planet. Getting the id from the url. getStaticProps receives a context object from which we can get the params for the current path.

export async function getStaticProps(context) {
  const res = await fetch(`https://swapi.dev/api/planets/${context.params.id}`);
  const data = await res.json();

  return { props: { planet: data } };
}

Then add the getStaticPaths function to generate the pages based on the content:

export async function getStaticPaths() {
  const res = await fetch('https://swapi.dev/api/planets');
  const data = await res.json();

  const paths = data.results.map((planet, i) => ({
    params: {
      id: i.toString(),
    },
  }));

  return {
    paths,
    fallback: true,
  };
}

Here we are fetching all the planets and create an paths list.

getStaticPaths must return an object with a paths array. The array should contain objects that must have a property mapping to the name of the dynamic page. In this case id. The value of id must be a string. For a blog post this could be named slug and the filename be [slug].js etc.

Note: in this case the Star Wars API does not return the id for each entry. Normally you would want to create your pages based on some value for each entry. As the specific planet is fetched by an increasing id we can in this case use the index within the map function. This is not recommended in a real application.

If you run npm run build or yarn build you will get an output with all the pages created. For me it looks something like this:

├ ● /planets (429 ms)
└ ● /planets/[id] (3171 ms)
    ├ /planets/8 (444 ms)
    ├ /planets/5 (436 ms)
    ├ /planets/4 (412 ms)
    ├ /planets/6 (366 ms)
    ├ /planets/1 (327 ms)
    ├ /planets/2 (323 ms)
    ├ /planets/3 (319 ms)[+3 more paths]

If you start up your application again you can now navigate to /planets/1 and you should see the content for only that planet.

The complete file for the PlanetsPage then looks like this. You can place the exported functions either before or after the page function. I prefer to have them before.

export async function getStaticProps(context) {
  const res = await fetch(`https://swapi.dev/api/planets/${context.params.id}`);
  const data = await res.json();

  return { props: { planet: data } };
}

export async function getStaticPaths() {
  const res = await fetch('https://swapi.dev/api/planets');
  const data = await res.json();

  const paths = data.results.map((planet, i) => ({
    params: {
      id: i.toString(),
    },
  }));

  return {
    paths,
    fallback: false,
  };
}

function PlanetPage({ planet }) {
  return (
    <main>
      <h1>{planet.name}</h1>
      <p>Population: {planet.population}</p>
      <p>Terrain: {planet.terrain}</p>
    </main>
  );
}

export default PlanetPage;

Going forward

This was just a quick introduction to NextJS. We have learned about the concepts of pages and filename routing. How we could fetch data depending on specific situations. I will continue to add more posts about all the other specific features in the future.

To continue working on your application and explore all the other features of NextJS I recommend to start reading their documentation https://nextjs.org/docs/getting-started.