Guide to Getting Started with Next.js - Create a Next.js App

Introduction

Next.js is an open-source JavaScript framework created by Vercel to enhance React applications with features such as Server-Side Rendering and Static Site Generation.

Traditionally, React is used to create Single-Page Applications (SPAs) whose contents are rendered on the client side. Next.js extends this by allowing developers to create applications that can perform server-side actions, pre-fetch routes, and has support for TypeScript. On top of that - it doesn't require any extra configuration by default!

In this guide, we'll take a look at the pertinent features and workings of Next.js. To solidify the new knowledge, we'll build a full multi-page weather application that talks to external APIs and allows a user to record given states.

Link: The complete code for this application can be found on GitHub.

Installation and Setup

The easiest way to create a new Next.js application is by using the create-next-app CLI tool. You can install it via npm:

$ npm install create-next-app

Once installed, you can initialize a new Next.js application by calling the tool and supplying a name for your project:

$ npx create-next-app weather-app

Note: If you don't already have create-next-app installed - npx will prompt you to install it automatically.

Once the tool finished initializing a skeleton project, let's move to the directory and take a peek inside:

$ cd weather-app
$ ls
README.md       node_modules/      package.json  public/
next.config.js  package-lock.json  pages/        styles/

The standard package.json, package-lock.json and node_modules are there, however, we've also got the /pages, /public and /styles directories, as well as a next.config.js file!

Let's take a look at what these are.

Features of Next.js

Next.js is ultimately an extension for React, and it does introduce a couple of new things that make React application development simpler and faster - starting with Next.js pages.

Pages

Next.js makes creating multi-page applications with React ridiculously easy with its default file-system-based router. You don't need to install any additional packages, such as react-router-dom, or configure a router at all.

All Next.js projects include a default /pages directory, which is the home of all of the React components you'll be using. For each component - a router will serve a page based on that component.

A React component is a page in Next's eyes, and is served automatically on the path corresponding to its filename.

For instance, suppose we create a component, contact.js, within the /pages directory:

const Contact = () => {
    return (
        <div>
            Contact
        </div>
    )
}

export default Contact

The file-system-based router that Next.js employs will make this page accessible under the /contact route! The only exception to this rule is the index.js page, which isn't located under the /index route, but rather, it's served at /.

Additionally, you can nest routes with Next.js, so you can easily create a /weather/berlin dynamically by creating a /weather folder, and a dynamic [city].js component to act as the page.

Note: For dynamic routes, you need to name the file in a set of square brackets. Without them, it's a static literal string, and it will be parsed as such. city.js would resolve to the /weather/city route, and wouldn't match for anything else. On the other hand [city.js] would match for /weather/berlin, /weather/london, /weather/lagos, etc.

The <Link> Component

The <Link> component can be used to navigate between pages in your apps. Assuming our project page structure has several pages under the /pages directory:

- pages
  - index.js
  - weather.js
  - contact.js

The <Link> component's href attribute can be used to point to the relative path of each page, starting at the /pages directory:

import Link from "next/link";

export default function Home() {
  return (
    <div>
      <Link href="/">Home</Link>
      <Link href="/weather">Weather</Link>
      <Link href="/contact">Contact</Link>
    </div>
  )
}

Naturally, if you have a nested hierarchy of files, you can link to nested pages as well:

- pages
  - weather
    - [city].js
import Link from "next/link";

export default function Weather() {
  return (
    <div>
      <Link href="/weather/berlin">Berlin</Link>
      <Link href="/weather/london">London</Link>
      <Link href="/weather/lagos">Lagos</Link>
    </div>
  )
}

The <Link> component can also pre-fetch pages! Once a page has been loaded, and there are multiple links to other pages - if you know that a certain page is to be visited often, or would like to make sure that the page is loaded as soon as possible (without impacting the initial page), you can pre-fetch the page associated with a <Link> to make the transition faster the smoother!

In fact, Next.js prefetches all pages by default. To that end, if you'd like to speed up a transition to a certain page, you set the prefetch attribute to false for other pages.

For instance, it's conceivable that in a weather app, people are more likely to navigate to the /weather route from the home page, rather than /about. There's no need to pre-fetch the about.js page/component, since you'd be burdening the server further for a page that isn't too likely to be clicked. On the other hand - weather.js is most likely to be the next route people visit, so you can shave off some time in the transition by prefetching it:

import Link from "next/link";

export default function Home() {
  return (
    <div>
      <Link prefetch=true href="/weather">Weather</Link>
      <Link prefetch=false href="/about">About Us</Link>
    </div>
  )
}

Some other attributes include the scroll attribute (which defaults to true), which navigates the user to the top of the page when they reroute themselves with a <Link>. This is a very sensible default, though you might want to turn it off for a more specific effect you'd like to achieve.

Another attribute worth noting is the replace attribute, which defaults to false. If set to true, it'll replace the latest entry in the history stack, instead of pushing a new one, when you navigate to a new page/route with a <Link>.

Pre-Rendering Pages

Speaking of prefetching and pre-rendering pages - this feature of Next.js is one of the more pertinent ones. Again, by default, Next.js will pre-fetch all of the pages that you link to, allowing for smooth, quick transitions between them.

For each page, you may choose between Server-Side Rendering or Static Generation and which technique is used depends on the functions you use to fetch data. You're not forced to adhere to one of these techniques for the entirety of the application!

Choosing between SSR and SG is a question of where you'd like to place the load, and both techniques prerender pages, just in a different way.

If you render your pages on the server-end, they'll be rendered on each request, using your server's resources, and sent to the end-user. If you statically generate a page, it's generated once and is reusable after build time.

Note: Generally speaking, you'll want to use Static Generation whenever there's no need to use Server-Side Rendering since the page can then be cached and reused, saving valuable computation. Whenever components on pages are frequent, Server-Side Rendering is required, and the page is rendered when requested with new data (some of which may depend on the request itself).

You may also decide to let some pages or elements on the page to be rendered via Client-Side Rendering which places the load on the end-user's machine, but you have no guarantees nor control over their resources, so typically, you want to avoid any intensive computation on their end.

What does pre-rendering entail in practice?

How does this impact the end-user, and how does it improve a plain React application? Pre-rendering allows a user to see the page before any of the JavaScript code is even loaded. It takes a really short while for the JavaScript to load - but these milliseconds inadvertently affect our perception. Even though the page is shown as it will be seen by the user once all components are loaded in - none of them work yet.

It is only once the page is shown that the components are being processed and loaded to become interactive components. This process is called hydration.

Without Next.js, the page would be empty while the JavaScript loads, and the components are being initialized.

Since pre-rendering is an integral part of Next.js, we'll take a look at some of the functions you can use to facilitate pre-rendering, both through SSR and SG.

Fetching Server-Side Data - getServerSideProps()

The getServerSideProps() function is used to perform server-related operations such as fetching data from an external API. Again, you want to perform SSR whenever the data on the page changes rapidly, and it wouldn't make sense to cache it. For instance, an API may respond with updated stock prices, or the time on a clock every second, and on each request of the user - these should be up-to-date.

Here's an example that sends a request to a sample API and passes the data received as a prop to our page component:

const Weather = ({temperature}) => {
// display temperature

}
export default Weather

export async function getServerSideProps() {
  const res = fetch('http://example.com/api')
  ...
  const temperature = res.temperature
  return {
    props: {temperature},
  }
}

The getServerSideProps() receives a context object, which contains server-related information such as incoming requests, server responses, queries. This is crucial, because the rendering itself may depend on the context.

Static Generation Paths - getStaticPaths()

We use the getStaticPaths() function to define the list of paths that should be statically generated for a dynamic route. Say we have a dynamic route pages/weather/[city].js and we export a getStaticPaths() function in this file like below:

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: 'paris' } }, { params: { id: 'london' } }],
  };
}

Next.js will automatically statically generate /weather/paris and /weather/london for us at build time.

Static Generation Props - getStaticProps()

The getStaticProps() function is similar to getServerSideProps() in the sense that we use it to load props on a pre-rendered page. In this case, however, the props are statically generated at build time and are reused for all requests later, instead of rendered at request time:

export async function getStaticProps() {

  const res = await fetch('http://someapi/toget/cities')
  ...
  const cities = await res.json()
  return {
    props: {
      cities,
    },
  }
}

Note: getStaticPaths() will not work with getServerSideProps() - instead, use getStaticProps(). It's best to utilize this function only when the data to be pre-rendered loads quickly or can be publicly cached.

<Head/> & SEO

Since Single-Page Applications are difficult to crawl by search engines, optimizing React applications for search engines may be difficult. While Next.js server-side rendering addresses this, the framework also includes a special <Head /> component that makes it simple to append elements to the head of your page.

As a result, updating your app pages' SEO settings like the title tag, meta-description, and any other element you'd include in a standard HTML <head> tag is easier:

import Head from "next/head";

const Contact = () => {
  return (
    <div>
      <Head>
        <title>Contact</title>
        <meta name="description" content="Welcome to our contact page!"/>
      </Head>
    </div>
  );
};

export default Contact;

Creating API Routes with Next.js

Next.js also offers a feature to develop your own API right inside your project, and the process is similar to that of creating pages! To begin, you'll need to create a new api subdirectory under /pages (i.e. /pages/api), and any file in this directory will be routed to /api/*.

Say, we create a file /pages/api/weather.js. An endpoint is immediately accessible as /api/weather.

To get these endpoints to work, you must export a default handler() function (request handler) for each endpoint, which receives two parameters: req (incoming request), and res (server response).

To try this out, let's update our /pages/api/weather.js example with the following content:

export default function handler(req, res) {
  res.status(200)
  res.json({
    city: 'London',
    temperature: '20',
    description: 'sunny',
  });
}

If we visit or send a request to /api/weather, we should see the dummy weather information for London returned, as well as a 200 response code.

Static Assets

At some point, you'll probably want to load assets such as images, videos, fonts, and so on. All Next.js projects have a directory called /public for this purpose.

Files in the /public directory can be accessed from anywhere in your application by preceding the source with the base URL (/).

For example, if we have a file under /public/weather-icon.svg, we can access it in any component with:

const WeatherIcon = () => {
  return <img src="/weather-icon.svg" alt="Weather Icon"/>
}

export default WeatherIcon

Next.js Environment Variables

Environment variables are variables whose values are set outside of our application, and we mostly use them to keep sensitive data like API keys or server configurations to avoid pushing them to version control tools such as GitHub, GitLab, etc.

Next.js provides support for work with environment variables, through a .env.local file. All the variables in this file are mapped to the process.env.

If we have a .env.local file with the following variables:

WEATHER_API_KEY=abcd123
CITY_API_KEY=123abc

We can now access them via process.env.WEATHER_API_KEY and process.env.CITY_API_KEY.

Also, environment variables aren't exposed in the browser by default and are only accessible in the Node.js environment (on the server side). We can however choose to expose them to the client-side by prefixing the preferred variable with NEXT_PUBLIC_. For example, if we have a variable:

NEXT_PUBLIC_CITY_API_KEY=123abc
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

This variable can now be accessed anywhere in our application via process.env.NEXT_PUBLIC_CITY_API_KEY.

Building a Weather App with Next.js

We've covered a lot of ground already! Solidifying new knowledge comes best through practice, so it's helpful to create an application to supplement the new theory.

We'll be building a weather application that detects the user's city and displays weather information based on that information. In addition, we'll be implementing a feature that allows users to save specific weather information at any moment and access it later.

The application will look something like this:

If you haven't already, create a new Next.js app with the command below:

$ npx create-next-app weather-app

And we can start our app with:

$ npm run dev

For simplicity's and brevity's sake, we'll be using Bootstrap to set up our application's interface instead of writing custom CSS. You can install Bootstrap using the command below:

$ npm install bootstrap

Once the installation is complete, let's open pages/_app.js and include an entry for Bootstrap:

import "bootstrap/dist/css/bootstrap.css";
import "../styles/globals.css";

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

export default MyApp;

Note: The _app.js file is the default App component that Next.js uses to initialize pages. It serves as the starting point for all of your page's components.

Now, we can make our app more visually appealing by changing the default font and adding a nice background color. Let's open styles/global.css and make the following changes:

@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@100;200;300;400;500;800;900&display=swap');

body {
  background: #4F32FF;
  color: #fff;
  font-family: 'Be Vietnam Pro', sans-serif;
}

That's more than enough to get going! Let's define the structure of our pages and placeholders for the data when it gets fetched via an API.

Page Markup

For our front-end, let's open up pages/index.js and define the markup (structure) of our home page:

import Link from "next/link";

export default function Home() {
  return (
    <div>
      <div
        className="d-flex justify-content-center align-items-center"
        style={{ minHeight: "100vh" }}
      >
        <div>
          <div>
            <h1 className="fw-bolder" style={{ fontSize: "60px" }}>
              Null City.
            </h1>
            13 January, 2022
          </div>
          <div className="d-flex justify-content-between align-items-center mt-4">
            <div className="pe-5">
              <h2 className="d-inline">0</h2>
              <sup>°C</sup>
              <p className="text-info">Cloudy</p>
            </div>
            <div>
              <img src="/1.png" alt="" width={100} draggable="false" />
            </div>
          </div>
          <hr />
          <div className="d-md-flex justify-content-between align-items-center mt-4">
            <button className="btn btn-success border-0 save-btn px-4 py-3">
             Timestamp
            </button>
            <Link href="/history">
              <button className="btn btn-danger border-0 history-btn px-4 py-3 ms-auto">
                My History
              </button>
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}

Note: You'll need to download the weather icon from our GitHub, and include it in your project /public folder.

And, at this point, if we preview our application in the browser, we should see the following output with dummy data:

Getting Weather Information

We will use a free weather API to get the user's current weather information, but because we want to display the weather information for the city the user is currently in, we'll need to use another API to get the user's current city and pass this parameter to the weather API in order to get the desired information.

The image below describes this process

To get the weather information, we will be making use of the OpenWeather API, and while they provide a free plan, you'll need to create an account in order to acquire an API key.

And to retrieve the user's city, we'll be making use of a free IP Geolocation API which does not require an API key to use.

Also, we want to make sure to display the weather information immediately after the page is loaded, so Next.js getServerSideProps() does come in handy here!

Now, let's add the following exports to index.js to perform all of the functions mentioned above:

export async function getServerSideProps() {
  const ipRequest = await fetch(`http://ip-api.com/json/`);
  const ipData = await ipRequest.json();
  const city = ipData.regionName;

  const api_key = 'YOUR_OPEN-WEATHER_API_KEY';
  const url = `http://api.openweathermap.org/data/2.5/weather?q=${city},&appid=${api_key}&units=metric`;
  const weatherRequest = await fetch(url);
  const weatherInfo = await weatherRequest.json();

  console.log(weatherInfo);
  return { props: { weatherInfo, city } };
}

The code above performs two asynchronous operations:

  • The first is to retrieve the user's city, which we store in a variable called city.
  • The second is to send a request to the weather API.

And finally, we've passed the result returned from the weather API, as well as the city as a prop to our index page.

Note: You'll need to replace YOUR_OPEN-WEATHER_API_KEY with your own OpenWeather API key.

The required information is now stored as a prop for our index page in weatherInfo and city, and we can access them through:

...
export default function Home({ weatherInfo, city }) {
...
}

If you try logging weatherInfo to the console, you'll notice that a lot of information is returned, including the user's coordinate and some other information that isn't required for our application. According to our application design, we will just need the following data:

  • User's city
  • Current temperature
  • Weather description (e.g cloudy, light rain, snow, etc)

Finally, a weather icon based on the current temperature. The current temperature is returned at weatherInfo.main.temp, and the weather description at weatherInfo.weather[0].description.

So, let's go ahead and replace the dummy data in our markup with this information:

{/* ... */}
<div>
  <h1 className="fw-bolder" style={{fontsize: "60px"}}>
    {city}
  </h1>
  13 January, 2022
</div>
<div className="d-flex justify-content-between align-items-center mt-4">
  <div className="pe-5">
    <h2 className="d-inline">
        {Math.round(weatherInfo.main.temp)}</h2>
    <sup>°C</sup>
    <p className="text-info text-capitalize">
        {weatherInfo.weather[0].description}
    </p>
  </div>
  <div><img src='/1.png' alt="" width={100} draggable="false" /></div>
</div>
{/* ... */}

We can also use the OpenWeather API to get a weather icon depending on the current temperature by simply passing the icon name as a parameter, and luckily this is also available at $weatherInfo.weather[0].icon.

So, let's go ahead and replace the icon's <img> tag with the code below:

{/* ... */}
<img
  src={`http://openweathermap.org/img/wn/${weatherInfo.weather[0].icon}@2x.png`}
/>
{/* ... */}

And our application should be fully operational, displaying the current weather information based on the city we are currently in:

Saving Data Locally

Now, let's create a function that saves the current weather information, as well as the date and time it was stored in the browser's localStorage. Each entry will be saved as an object with the following structure:

{
  date: 'Current Date',
  time: 'Current Time',
  city: 'User\'s City',
  temperature: 'User\'s city temperature',
  description: 'Weather Description',
};

To do this, create a new function saveWeather() (still inside our index.js file) with the following code:

  const saveWeather = () => {
    const date = new Date();

    let data = {
      date: `${date.getDate()} ${date.getMonth() + 1} ${date.getFullYear()}`,
      time: date.toLocaleTimeString(),
      city: city,
      temperature: weatherInfo.main.temp,
      description: weatherInfo.weather[0].description,
    };

    let previousData = localStorage.getItem('weatherHistory');
    previousData = JSON.parse(previousData);
    if (previousData === null) {
      previousData = [];
    }
    previousData.push(data);
    localStorage.setItem('weatherHistory', JSON.stringify(previousData));
    alert('Weather saved successfully');
  };

The code above will parse any data previously stored in localStorage.weatherHistory as JSON and depending on the type of data returned, we've pushed our new entry to an array, convert this array to string, and restore it in localStorage.weatherHistory. We need to do this because localStorage can only store strings and not any other data types.

If you'd like to read more about localStorage - read our Guide to Storing Data in the Browser with LocalStorage!

And, of course, we want to call this function when the user clicks the Timestamp button, so let's add an onClick attribute to the button:

<button onClick={saveWeather}>Timestamp</button>

Weather History Page

Finally, we'll need to create a dedicated page to access all of the weather information that is saved in our browser's localStorage.

Note: We won't be able to use Next.js data fetching functions because localStorage or any other document object isn't available on the server, therefore we'll have to rely on client-side data fetching.

Create a new history.js file under the pages directory with the following content:

import { useState, useEffect } from "react";

const History = ({}) => {
  const [weatherHistory, setweatherHistory] = useState([]);


  useEffect(() => {
    setweatherHistory(
      localStorage.weatherHistory !== undefined
        ? JSON.parse(localStorage.weatherHistory)
        : []
    );
  }, []);

  return (
    <div
      className="d-flex justify-content-center align-items-center p-3"
      style={{ minHeight: "100vh" }}
    >
      <div>
        {" "}
        <h2>My Weather History</h2>
        <div className="mt-5">
          {weatherHistory.length > 0 ? (
            weatherHistory.map((weather, index) => {
              return (
                <div
                  key={index}
                  className="card mb-3"
                  style={{ width: "450px" }}
                >
                  <div className="card-body text-dark">
                    <h5 className="card-title ">
                      {weather.city} - {weather.date}
                    </h5>
                    <small>{weather.time}</small>
                    <hr />
                    <p className="card-text">
                      <span className="font-weight-bold">Temperature: </span>
                      {weather.temperature}
                      <sup>°C</sup>
                    </p>
                    <p className="card-text">
                      <span className="font-weight-bold">Condition: </span>
                      {weather.description}
                    </p>
                  </div>
                </div>
              );
            })
          ) : (
            <p>Nothing to see here - yet</p>
          )}
        </div>
      </div>
    </div>
  );
};

export default History;

The code above checks if localStorage.weatherHistory exists, if it does - we parse the data and set it to a new variable weatherHistory. If it doesn't, we've set this variable to an empty array instead.

In our markup, we check if there is at least one data entry in our weatherHistory array, and using JavaScript's .map() function, we iterate through all the items in weatherHistory, displaying them in our webpage.

Let's go ahead and click the Timestamp button on the index page to record the current weather information, and when you return to the history page, you should see something like this:

Conclusion

Next.js is a JavaScript framework designed specifically to enhance and foster the development of performant React applications.

In this guide, we've gone through the pertinent features of the library - how pages are created and routed via Next.js' file-routing system, how the <Link> component works, what prefetching and pre-rendering is and how to leverage it to enhance user experience, how API routes and request handlers can easily be created and how to work with environment variables.

To top it off - we built a weather application that communicates with external APIs to fetch data and display it to the end-user, allowing them to save any given timestamp to their local storage.

Again, the full source-code of the application is available on GitHub.

Was this article helpful?
Project

React State Management with Redux and Redux-Toolkit

# javascript# React

Coordinating state and keeping components in sync can be tricky. If components rely on the same data but do not communicate with each other when...

David Landup
Uchechukwu Azubuko
Details

Getting Started with AWS in Node.js

Build the foundation you'll need to provision, deploy, and run Node.js applications in the AWS cloud. Learn Lambda, EC2, S3, SQS, and more!

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms