What Next.js Adds to React

Next.js positions itself as a production-ready React framework by removing the repetitive setup work that usually surrounds a React project. Routing, code-splitting, and data fetching strategies are all handled through framework conventions, which means you spend less time wiring up infrastructure and more time building the application itself.

For developers who already know React, the learning curve is intentionally gentle. You do not need to adopt every feature at once. The framework layers in capabilities progressively: start with static pages, then opt into server rendering, incremental static regeneration, or API routes as the need arises.

The framework ships with a number of conveniences that React alone does not provide:

  • File-system routing: No router library required. The location and name of a file inside your pages directory directly determines its URL.
  • Built-in CSS handling: styled-jsx is included out of the box, so scoped styles work immediately without additional configuration.
  • TypeScript support: Configuration and compilation are handled automatically when TypeScript is detected.
  • Flexible data fetching: Static site generation (SSG) and server-side rendering (SSR) are both supported, and you can choose per page which approach fits best.

Beyond these headline items, smaller quality-of-life features accumulate as you dig deeper: the next/head component for managing the document head, optional chaining support, and no longer needing to import React in every file that uses JSX.

Setting Up Your Environment

Before creating an app, confirm you have Node.js and a package manager available. Check your Node installation with:

# It should respond with a version number
node -v

npm and npx are typically installed alongside Node.js. Verify both with:

# Run this. It should respond with a version number
npm -v

# Then Run This. It Should Also Respond With A Version Number
npx -v

If any command does not return a version number, install Node.js first. Yarn users can install it globally then confirm with:

# Installs yarn globally
npm i -g yarn
# It should also respond with a version number
yarn -v

Creating a Project

There are two supported paths to a new Next.js application. The fastest is via create-next-app:

# Create a new Next.js app with npx
npx create-next-app <app-name>

# Create A New Next.js App With npm
npm create-next-app <app-name>

# With Yarn
yarn create next-app <app-name>

The CLI also provides access to starter templates from the Next.js examples repository, covering common combinations like Redux, Tailwind CSS, or a Sanity CMS integration.

The alternative is a manual setup, which only requires three packages:

# With npm
npm install next react react-dom

# With Yarn
yarn add next react react-dom

Then add the standard lifecycle scripts to package.json:

"scripts": {
  "dev": "next dev",
  "start": "next start",
  "build": "next build"
}

These three commands map to the development server, the production server, and the production build, respectively.

Understanding the Folder Layout

A freshly generated Next.js project keeps its structure intentionally minimal. The framework only imposes three directories that matter to it: pages, public, and styles.

# other files and folders, .gitignore, package.json...
- pages
  - api
    - hello.js
  - _app.js
  - index.js
- public
  - favicon.ico
  - vercel.svg
- styles
  - globals.css
  - Home.module.css

The rest of the project organization is left entirely up to you.

Pages as Components

In the pages directory, every JavaScript file is a page, and every page is a React component. A file named about.js is both the component and the route /about. The default page, pages/index.js, is served at the root path /.


// Location: /pages/homepage.js
// <HomePage/> is just a basic React component
export default HomePage() {
  return <h1>Welcome to Next.js</h1>
}

Custom App and Document

Two underscore-prefixed files hold special meaning. The _app.js file is a custom component Next.js uses to initialize every page and is the standard place to import global styles or shared providers. The _document.js file augments the surrounding HTML structure, which is necessary because individual pages do not define their own <html> and <body> markup.

Routing by Convention

The tightest coupling Next.js introduces is between file names and route paths. The file pages/books.js maps to /books. This file-based system covers the common routing patterns with a few structural rules.

- pages
  - index.js # url: /
  - books.js # url: /books
  - profile.js # url: /profile

Index Routes

Each folder can contain an index.js file to represent that folder's root route. This is optional but prevents repeated segment names in the URL. A file at pages/users/index.js resolves to /users rather than /users/users.

- pages
  - index.js
  - users
    - index.js
    - [user].js

Nested and Dynamic Routes

Deeper paths simply require deeper folders. A route like /dashboard/user/:id comes from nested directories ending in a bracketed file name:

- pages
  - index.js
  - dashboard
    - index.js
    - user
      - [id].js # dynamic id for each user

Brackets are the syntax for dynamic segments. The file [id].js inside a folder captures whatever value appears at that position in the URL. Dynamic segments can appear anywhere in the path structure:

- pages
  - dashboard
    - user
      - [id].js
          - profile

This structure would make the file accessible at /dashboard/user/:id/profile, giving you the profile page for a specific user.

A nested sequence of unknown segments can also be handled by a catch-all route, which uses the spread syntax in the file name:

- pages
  - news
    - [...id].js

With that structure, a URL path such as /news/sport/football/epl/liverpool is fully captured by the single file.

Reading Route Parameters

To access the dynamic values inside your component, import useRouter from next/router:

import { useRouter } from 'next/router';

export default function Post() {
  // useRouter returns the router object
  const router = useRouter();

  console.log({ router });
  return <div> News </div>;
}

The route parameters live in the query property of the object the hook returns. If the current route does not carry any dynamic segments, query is simply an empty object.

Moving Between Pages

Client-side navigation does not require a separate library. The Link component exported from next/link handles that role. Given these two pages:

- pages
  - index.js
  - profile.js
  - settings.js
  - users
    - index.js
    - [user].js

You can connect them with:

import Link from "next/link";

export default function Users({users) {
  return (
    <div>
      <Link href="/">Home</Link>
      <Link href="https://www.smashingmagazine.com/profile">Profile</Link>
      <Link href="https://www.smashingmagazine.com/settings">
        <a> Settings </a>
      </Link>
      <Link href="https://www.smashingmagazine.com/users">
        <a> Settings </a>
      </Link>
      <Link href="https://www.smashingmagazine.com/users/bob">
        <a> Settings </a>
      </Link>
    </div>
  )
}

The Link component only requires the href prop, which behaves like its HTML anchor counterpart in that it points to the target URL. Additional recognized props are available:

PropDefault valueDescription
asSame as hrefIndicates what to show in the browser URL bar.
passHreffalseForces the Link component to pass the href prop to its child./td>
prefetchtrueAllows Next.js to proactively fetch pages currently in the viewport even before they’re visited for faster page transitions.
replacefalseReplaces the current navigation history instead of pushing a new URL onto the history stack.
scrolltrueAfter navigation, the new page should be scrolled to the top.
shallowfalseUpdate the path of the current page without re-running getStaticProps, getServerSideProps, or getInitialProps, allows the page to have stale data if turned on.

Styling Approaches

Styling options are available without any new dependencies. Next.js ships with first-class support for three methods: global CSS files, CSS Modules for component-scoped class names, and styled-jsx for inline-scoped styles. The choice between them is a matter of project preference rather than capability, and each interoperates cleanly with the route structure described above.

Keeping Code Consistent: Linting and Formatting

Most JavaScript projects settle on ESLint for linting and Prettier for formatting. A popular way to combine both is Wes Bos's ESLint and Prettier setup, which extends eslint-config-airbnb and routes Prettier formatting through ESLint.

To add it to a local Next.js project, install the package first:

# This will install all peer dependencies required for the package to work
npx install-peerdeps --dev eslint-config-wesbos

Then create a .eslintrc file at the root, next to the pages, styles, and public directories:

{
  "extends": [
    "wesbos"
  ]
}

You can run linting manually by adding two npm scripts, or let your editor handle it. The manual scripts are:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
  "lint": "eslint .", # Lints and show you errors and warnings alone
  "lint:fix": "eslint . --fix" # Lints and fixes
},

If you use VSCode, install the ESLint extension and update your VSCode settings to trigger linting and formatting on save:

# Other setting
"editor.formatOnSave": true,
"[javascript]": {
  "editor.formatOnSave": false
},
"[javascriptreact]": {
  "editor.formatOnSave": false
},
"eslint.alwaysShowStatus": true,
"editor.codeActionsOnSave": {
  "source.fixAll": true
},
"prettier.disableLanguages": ["javascript", "javascriptreact"],

You will likely need to override some rules. For example, Next.js's custom _app.js component spreads pageProps, which triggers the react/jsx-props-no-spreading rule. Disable it in your .eslintrc:

{
  "extends": [
    "wesbos"
  ],
  "rules": {
    "react/jsx-props-no-spreading": 0
  }
}

Serving Static Assets

All static assets in Next.js must live in the public folder. This name is not configurable. Files placed there are served at the route root, as the structure below shows:

- pages
  profile.js
- public
  - favicon.ico #url /favicon.ico
  - assets
    - fonts
      - font-x.woff2
      - font-x.woff # url: /assets/fonts/font-x.woff2
    - images
      - profile-img.png # url: /assets/images/profile-img.png
- styles
  - globals.css

You can reference an image from a component like this:

// <Profile/> is a React component
export default function Profile() {
  return {
      <div className="profile-img__wrap">
        <img src="https://www.smashingmagazine.com/assets/images/profile-img.png" alt="a big goofy grin" />
      </div>
  }
}

Or reference font files from CSS:

/* styles/globals.css */
@font-face {
  font-family: 'font-x';
  src: url(/assets/fonts/font-x.woff2) format('woff2'),
       url(/assets/fonts/font-x.woff) format('woff');
}

Understanding Pre-rendering and Data Fetching

Next.js pre-renders every page by default. That means it generates the HTML for each page in advance, along with the minimum JavaScript required for hydration. The two pre-rendering modes differ in when data is fetched:

  • Static Generation (SG): data is fetched at build time and reused on every request, so the result can be cached.
  • Server-side Rendering (SSR): data is fetched on every request.

These modes are not mutually exclusive. A single app can mix them, and both can be combined with Client-side Rendering using tools like fetch, Axios, SWR, or React Query. Next.js provides three special functions for data fetching:

  1. getStaticProps — for SG when page content depends on external data.
  2. getStaticPaths — for SG when page paths depend on external data.
  3. getServerSideProps — for SSR.

getStaticProps

getStaticProps is an async function that fetches data at build time and returns it as a props object, which is mapped to the default exported component of the page. In the example below, the page content depends on an external list of accounts:

// accounts get passed as a prop to <AccountsPage/> from getStaticProps()
// Much more like <AccountsPage {...{accounts}} />
export default function AccountsPage({accounts}) {
  return (
    <div>
      <h1>Bank Accounts</h1>
      {accounts.map((account) => (
        <div key={account.id}>
          <p>{account.Description}</p>
        </div>
      ))}
    </div>
  )
}

export async function getStaticProps() {
  // This is a real endpoint
  const res = await fetch('https://sampleapis.com/fakebank/api/Accounts');
  const accounts = await res.json();

  return {
    props: {
      accounts: accounts.slice(0, 10),
    },
  };
}

getStaticPaths

When a page path is dynamic (for example, /states/[id]), Next.js needs to know which paths to pre-render at build time. That is the job of getStaticPaths. It returns an array of paths; each path will then be passed to getStaticProps via its params argument. Consider a folder structure with a states directory containing an [id].js file:

- pages
  - index.js
  - states
    - index.js # url: /states
    - [id].js # url /states/[id].js
 

The list page component would look like this:

// The states will be passed as a prop from getStaticProps
export default function States({states}) {
  // We'll render the states here
}

export async function getStaticProps() {
  // This is a real endpoint.
  const res = await fetch(`https://sampleapis.com/the-states/api/the-states`);
  const states = await res.json();
  
  // We return states as a prop to <States/>
  return {
    props: {
      states
    }
  };
}

The dynamic page for a single state follows. It matches routes such as /states/1 and /states/2:

// We start by expecting a state prop from getStaticProps
export default function State({ state }) {
    // We'll render the states here
}

// getStaticProps has a params prop that will expose the name given to the
// dynamic path, in this case, `id` that can be used to fetch each state by id.
export async function getStaticProps({ params }) {
  const res = await fetch(
    `https://sampleapis.com/the-states/api/the-states?id=${params.id}`
  );
  const state = await res.json();

  return {
    props: {
      state: state[0]
    }
  };
}

Without getStaticPaths, this page fails with the error "getStaticPaths is required for dynamic SSG pages and is missing for /states/[id]." Once defined, getStaticProps receives the params (here, the id) so it can fetch the corresponding content.

Helpful Extras

Absolute Imports

Since Next.js 9.4, you can avoid relative import chains. Instead of:

import FormField from "../../../../../../components/general/forms/formfield"

write:

import FormField from "components/general/forms/formfield";

This requires a jsconfig.json (or tsconfig.json for TypeScript) that defines the base URL:

{
  "compilerOptions": {
      "baseUrl": "."
  }
}
This assumes the components folder exists at the app root, alongside pages, styles, and public.

Experimental ES Features

Non-stable JavaScript syntax such as the nullish coalescing operator (??) and optional chaining (?.) is supported. Enabling them just requires adjusting the targeted ES version in your config:

export default function User({user) {
  return <h1>{person?.name?.first ?? 'No name'}</h1>
}