DEV Community

Cover image for Demystifying the Next.js Folder Structure - A Comprehensive Guide
B U C H M A N💻
B U C H M A N💻

Posted on

Demystifying the Next.js Folder Structure - A Comprehensive Guide

Introduction

Next.js is a widely used framework for making modern web apps with React. It simplifies development by including server-side rendering, static site generation, and routing. This helps create fast and SEO-friendly sites, and you can pick between server-side rendering or static generation. Next.js also offers easy file-based routing and built-in CSS support for dynamic interfaces.

Having a neat folder setup in web development is essential. It keeps things organized, helps find files fast, lowers error chances, and makes work smoother. An organized structure boosts readability, eases upkeep, and supports teamwork. It's like a map for your project, making it clear for everyone involved.

This guide aims to help you fully grasp the folder setup in a Next.js project. It wants to make it clear how things are organized and work together. It will explain what each folder does, how files connect, and offer practical tips for using Next.js in your projects.

Prerequisite

Before diving into learning the folder structure, it’s helpful to have a strong understanding of:

  • JavaScript: Next.js is built on top of React and relies heavily on JavaScript, so a solid grasp of JavaScript fundamentals is crucial.
  • React: Next.js is a framework for React applications, so familiarity with React concepts like components, state, props, and JSX will be essential.
  • HTML/CSS: Having a good foundation in HTML and CSS will aid in understanding how Next.js components and styles are structured.
  • Nodejs: Next.js is built on Node.js, so having a basic understanding of how Node.js works will be beneficial.
  • Basic Routing: Understanding how web applications handle routing and navigation will make it easier to grasp Next.js's routing system.
  • APIs: Next.js can be used to build server-side rendered and static websites, so a basic understanding of APIs and data fetching concepts will be helpful.

Once you have a solid foundation in these areas, you'll be better equipped to delve into the specific structure and features of Next.js applications.

Understanding Next.js Folder Structure


- /app
  - page.js
  - layout.js
  - globals.css
- /public
  - ...
- /styles
  - ...
- /components
  - ...
- package.json
- next.config.js
- ...

Enter fullscreen mode Exit fullscreen mode

Here’s a brief explanation of each directory(folder) and file:

  • /app: Most important folder in our Nextjs application. Contains layout.js, page.js, globals.css (these mentioned are the most important files for now)

    • layout.js: In our application, think of "layout.js" as the key starting point. It wraps around all the components, like a parent, which lets you give your application's pages a consistent look and feel using a shared layout. Moreover, it also helps in tweaking things like the language setting (e.g., lang="en"), updating metadata for all pages, and including script tags, links, or fonts to tailor the appearance of your HTML documents.
    • page.js: In our application, consider "page.js" as the representation of the homepage route, which means it corresponds to the main landing page of the application (specifically, it would be something like "localhost:3000").
    • globals.css: "globals.css" is where you'll find the universal CSS file for the entire application. It holds the styles that apply across the entire app.
  • /public: In the "/public" directory, you can store files such as images, fonts, and other assets. These files are directly accessible to users without needing to pass through the Next.js server.

  • /styles: In Next.js, you have the option to keep your overall design styles or styles specific to individual components. Next.js provides support for different approaches to working with CSS, such as CSS Modules, CSS-in-JS libraries, and more. Alternatively, the globals.css file can be at the styles directory.

  • /components: In this directory, you can place your React components that can be used in multiple pages throughout your application.

  • package.json: The package.json file serves as a document that holds information about your project, including details about what it consists of and the dependencies it relies on.

  • next.config.js: The next.config.js file serves as a configuration hub where you can tailor different aspects of your Next.js application. This includes tasks like adjusting the webpack configuration, defining environment variables, and more, to suit your specific needs.

In a default Next.js project structure, you'll find these fundamental directories and files. As your project expands, you can organize your code more by creating subdirectories or additional folders to better suit your project's evolving requirements.

Exploring Each Directory and File

Here's a brief rationale behind some of the key design choices:

App Directory

The /app directory holds your route files. Each file inside this directory becomes a route in your application. This choice simplifies routing and server-side rendering setup.

To establish a "/user" route, begin by accessing the app directory. Inside the app directory, create a new folder named "user." Within this "user" folder, generate a "page.js" file. Inside the "page.js" file, you can construct a React functional component.

const page = () => {
return (
     <div>Hello user</div>
    )
}

export default page
Enter fullscreen mode Exit fullscreen mode

You can access it at localhost:3000/user.

Now, let's tackle a more complex task. Imagine you need to build a blog application with distinct routes for various functionalities. This entails showcasing all posts through the "/post" route and crafting a new post via the "/post/new" route. This concept is referred to as nested routing in React, and it would appear something like this:


import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import NewPost from './pages/NewPost';
import Post from './pages/Post';

const App = () => {
    return (
        <Router>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="about" element={<Home />} />
                <Route path="posts" element={<Post />}>
                    <Route path="new" element={<NewPost />} />
                </Route>
            </Routes>
        </Router>
    );
}

export default App;



Enter fullscreen mode Exit fullscreen mode

To implement the same structure in Next.js, you simply need to nest folders within each other. To establish this routing pattern, start by creating a new folder named "posts" inside the "app" folder. Within the "posts" folder, you'll create a page (e.g., page.js) responsible for displaying all the posts.


import React from 'react';

const Page = () => {
    return (
        <div>POSTS</div>
    );
}

export default Page;

Enter fullscreen mode Exit fullscreen mode

To create a new nested route for posting, follow these steps:

  1. Create a folder named "new" within the "posts" folder.

  2. Inside the "new" folder, add a "page.js" file.

  3. Within "page.js," create a React component for handling the new post creation.


import React from 'react';

const Page = () => {
  return (
    <div>
      NEW POST
    </div>
  );
};

export default Page;

Enter fullscreen mode Exit fullscreen mode

This will render at /posts/new. Everything operates on a file-based system, so you create your folders, and your routing is set up automatically.

Now as our post application kept growing, we kept adding a new feature, and a need for something known as Dynamic routing.

Dynamic Routing involves creating website pages based on various variables or data, providing a flexible system. Instead of manually crafting static pages like "mydirectory/mypage," which remain unchanged, dynamic routing enables websites to generate URLs on-the-fly based on user preferences. To illustrate, in a blog application, we can implement dynamic routes to display specific blog post details, such as /posts/:postid, where ":postid" can dynamically represent different blog posts, like:
/posts/:postid i.e
/posts/:blog-post-1
/posts/:blog-post-2
/posts/:blog-post-3

To achieve these in React, we’ll have:


import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import NewPost from './pages/NewPost';
import Post from './pages/Post';

const App = () => {
    return (
        <Router>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="about" element={<Home />} />
                <Route path="posts" element={<Post />}>
                    <Route path="new" element={<NewPost />} /> // Nested Route 
                    <Route path=‘:postid element={<Post />} /> // Dynamic Route 
                </Route>
            </Routes>
        </Router>
    );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

In Next.js, to achieve the same functionality, follow a similar approach by creating a folder. However, this time, wrap it in square brackets [], like "[postid]." This indicates that it will be a dynamic id within the "posts" folder. Inside this folder, you can create a regular "page.js" file and develop a functional component:


import React from 'react';

const Page = ({ postid }) => {
  return (
    <div>{postid}</div>
  );
};

export default Page;

Enter fullscreen mode Exit fullscreen mode

Within the "Posts" folder, you have the option to create a "layout.js" file.

Layout.js file serves the purpose of enabling the sharing of UI components among different routes. For example, you can create a new React functional component with the following logic:


import React from 'react';

const Layout = () => {
  return (
    <div>Navigate to top</div>
  );
}

export default Layout;

Enter fullscreen mode Exit fullscreen mode

You can achieve the goal of reusing a component exclusively within pages located in the "posts" folder. By placing the "layout.js" file within the "posts" folder, it will be accessible in pages using [postid], in the "NewPosts" folder, and across all pages within the "posts" category, such as "page.js."

In addition to introducing the new layout for each sub-folder, you can also incorporate a loader or "loading.js" file into each of these sub-folders. The loading functionality will resemble something like this:


import React from 'react';
import LoadingSkeleton from './LoadingSkeleton'; // Import your LoadingSkeleton component if needed

const Loading = () => {
  // You can add any UI of your choice, including a skeleton 
  return <LoadingSkeleton />; // Make sure to render your LoadingSkeleton or desired UI component
}

export default Loading;

Enter fullscreen mode Exit fullscreen mode

This code renders a loading skeleton, which serves as a visual representation of the page being loaded. It can also function as a general spinner. It's important to note that while the "page.js" is loading, the "loading.js" component will be displayed.

Error handling: In certain scenarios, pages may fail to load correctly. In such cases, it becomes imperative to perform error handling. Handling errors gracefully involves catching these errors and subsequently presenting meaningful error messages to the client-side.

In Next.js, handling errors is straightforward, much like creating a loading UI file. To establish an error-handling convention, all you need to do is create an "error.js" file within the "posts" folder. This file will automatically execute when an error occurs, allowing for the graceful presentation of errors to the user. A typical error file may appear as follows:

use client // Error components must be client components

import React, { useEffect } from 'react';

const Error = ({ error, reset }) => {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try Again</button>
    </div>
  );
};

export default Error;

Enter fullscreen mode Exit fullscreen mode

Having worked with these unique file names, you've delved into the majority of Next.js's functionalities. You now possess a comprehensive understanding of the architecture of a modern Next.js application.

API routes directory for serverless functions

In Next.js, you define your server-side functions, which can be accessed via HTTP requests, in the "api" directory, typically located within your project's app directory alongside files like page.js and layout.js.

Next.js offers three options for data fetching. Let's explore each of them one by one, starting with:

Certainly, here's the provided information formatted in a proper markdown style:

Server Side Rendering (SSR)

Server Side Rendering (SSR) refers to dynamic server-rendered data. It involves fetching data fresh with each request. SSR ensures that every request to the server initiates a new rendering cycle and data fetch, keeping the content consistently up to date.

Here's a quick example:

async function Page({ params }) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`, { cache: 'no-store' });
  const data = await res.json();

  return (
    <div className="grid grid-cols-6 gap-x-6 gap-y-3">
      <div className="col-span-full space-y-3 lg:col-span-4">
        <h1 className="truncate text-2xl font-medium capitalize text-gray-200">{data.title}</h1>
        <p className="font-medium text-gray-500">{data.body}</p>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • In the async function "Page," we aim to retrieve data from the JSON placeholder API.
  • This is a dynamic page, where we obtain the "id" through the "params" of the page.
  • By using "{cache: 'no store'}," we instruct it not to store the data, but to fetch it and display it.
  • This approach ensures that it fetches the data anew with every request, achieving server-side rendering.

Static Site Generation (SSG)

To enable Static Site Generation (SSG), simply remove the "{cache: 'no store'}" option.

  • By default, Next.js utilizes SSG, which not only fetches data from sources like the JSON placeholder API but also caches it.
  • This approach is well-suited for content that remains relatively static, such as blog posts, documentation, or marketing pages.
  • On the initial request, it performs a fetch, stores the data, and subsequently displays it efficiently.

Incremental Static Generation (ISR)

Instead of managing the cache directly, you can utilize an alternative parameter called "revalidate," for example: {next: {revalidate: 10}}. This parameter offers a unique blend of the advantages of Server Side Rendering (SSR) and Static Site Generation (SSG) for both dynamic and static content.

With incremental generation, you have the flexibility to specify which data should be fetched statically during the build process and establish a revalidation time interval. This approach involves caching the data but refreshing it automatically after a specified timeframe, ensuring that you consistently have up-to-date information. This makes it an optimal solution for managing dynamic content effectively.

Full-stack capabilities

To harness full-stack capabilities within a Next.js application, we should concentrate on leveraging Serverless Route Handlers. Next.js enables you to manage HTTP requests and build backend functionality seamlessly using its file-based routing system, eliminating the need for an external server.

Let's explore the steps required to create a simple GET Request Route using a regular Express.js server:

  1. Begin by importing all necessary dependencies, including express.
  2. Then, require these dependencies.
  3. Create a new route using "app.get('/api/users...')".
  4. Ensure that you return the desired data within this route.
  5. Finally, make your application listen on a specific port, as servers need to remain active to handle incoming requests.

This example illustrates how to:

  • Establish an Express.js application.
  • Define a route specifically for users using a GET request.
  • When this route receives a GET request, it executes the associated code and subsequently returns the user data.

const express = require('express');
const app = express();

app.get('/api/users', (req, res) => {
  // Handle GET requests for /api/users 

  const users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Bob' }
  ];

  // Send the users as a response 
  res.json(users);
});

Enter fullscreen mode Exit fullscreen mode

Next.js encompasses all the features typically present in traditional backend servers. These encompass:

  • Middleware integration
  • Data parsing capabilities
  • Authentication checks
  • Serverless functions for streamlined deployment and scalable API route management.

It's important to note that there are two distinct methods for defining route handlers:

  1. You can establish file-based route handlers within the "api" folder, situated within the "app directory."
  2. Alternatively, you can create a direct route handler directly within the "app directory" itself.

However, there's a limitation with the second approach. To create a route that begins with '/', similar to a regular page, you must create a special "route.js" file to define the backend API route. If both "page.js" and "route.js" exist and share the same route prefix, such as '/post', Next.js may encounter ambiguity regarding whether it's a standard frontend page or a backend API route. This can result in conflicts between 'posts' as a regular page and 'posts' as an API route.

>app
 >users
  >route.js
  >page.js
Enter fullscreen mode Exit fullscreen mode

I recommend sticking with the first approach, where you create API routes separate from the "app" root directory. This keeps your code clean and understandable by maintaining a clear separation between backend-related logic and API endpoints within the API folder. This way, it's evident that the backend of your application is distinct from everything else in the "app directory," which enhances clarity.
The process of creating a simple post API route will look like this:

>api
 >users
  >route.js
Enter fullscreen mode Exit fullscreen mode

This will now function as an API backend route. Similar to "error.js," "layout.js," and "loading.js" in "page.js," "route.js" is also a specialized file name that enables you to create the backend route.

Below, Next.js provides support for the following HTTP methods:

  • GET: Fetches data or resources from the server.
  • POST: Sends data to the server to create a new resource.
  • PUT: Modifies or substitutes an existing resource on the server.
  • PATCH: Partially updates an existing resource on the server.
  • DELETE: Removes a specific resource from the server.
  • HEAD: Retrieves the headers of a resource without fetching its body.
  • OPTIONS: Fetches the supported HTTP methods and other communication choices for a resource.

Creating an HTTP method within the "route.js" file is a straightforward process. You just need to write a "get function" and implement your backend function within it:


export async function GET(request) {
  return new Response('Hello, Next.js!');
}

Enter fullscreen mode Exit fullscreen mode

You can also utilize HTTP verbs within the same file in a similar manner:

export async function HEAD(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function DELETE(request: Request) {}
export async function PATCH(request: Request) {}

Enter fullscreen mode Exit fullscreen mode

Now, to create the previous endpoint shown using Nextjs, all you simply have to do is:


export async function GET(request) {
  // Handle GET request for /api/users
  // Retrieve users from the database or any data source
  const users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Bob' },
  ];
  // Send the users as a response
  return new Response(JSON.stringify(users));
}

Enter fullscreen mode Exit fullscreen mode

Retrieve the users and return them. You don't have to set up additional Express configuration; focus solely on obtaining the required data from the specific endpoint, consider the business logic, and let Next.js take care of the rest of the details.

To observe this in the browser's API, the URL will appear as follows:
//http://localhost:3000/api/users

Impact on performance, SEO, and UX

Search engines consider page speed as a ranking factor. Websites that load quickly tend to rank higher in search results. Next.js's server-side rendering and static site generation capabilities can improve SEO performance by delivering content faster.

Server Side Rendering(SSR) and Client Side Rendering(CSR)

React 18 and Next.js 13 introduce new methods for rendering components in two environments: the client and the server.

By default, all components created in the Next.js "app" folder are React Server Components (SSC). This means Next.js utilizes Server-Side Rendering (SSR) to enhance the initial page loading speed, resulting in improved SEO and user experience.

To change an SSC into a Client-Side Component (CSC), you can add the "use client" directive at the top of your page, transforming it into a Client-Side Component.

Utilizing both Server-Side Components (SSC) and Client-Side Components (CSC) enables you to enjoy the advantages of SSR while still leveraging React's capabilities to build dynamic and interactive user interfaces.

Here's a typical example of a Server-Side Component (SSC):


import SearchBar from './SearchBar';
import Logo from './Logo';

export default function Counter() {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>

      <main>
        <p>This component will be rendered on the server side</p>
      </main>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

To convert the code snippet above into a Client-Side Component (CSC), you should include "use client" at the top of the component. It's crucial to declare a component as a CSC when utilizing states, hooks like useState and useEffect, or other client-side management solutions.

use client

import SearchBar from './SearchBar';
import Logo from './Logo';

export default function Counter() {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>

      <main>
        <p>This component will be rendered on the server side</p>
      </main>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

In React, state management, including the use of hooks, primarily occurs on the client side, where the component's state is managed and updated within the user's browser.

When to use server vs client components

To simplify the decision between server and client components, we suggest the following:

  • Start by using server components, which is the default behavior in the "app" directory, unless you specifically need a client component.
  • For detailed guidance on when to use Client-Side Components (CSC) and Server-Side Components (SSC), refer to the documentation for a helpful table.

In simple terms, let Next.js handle server-side rendering by default. When you require specific React capabilities like useState, useEffect, or interactivity, you can simply add "use client" to enable client-side rendering for those specific components.

How to improve the SEO of your Nextjs application:

We can define metadata in two ways:

  • Static
  • Dynamic

To modify metadata in a static manner, you need to perform actions like the following:

export const metadata = {
  title: 'Home'
};

Enter fullscreen mode Exit fullscreen mode

Output:


<head>
  <title>Home</title>
</head>

export default function Page() {
  return (
    <h1>My Next.js page with static metadata</h1>
  );
}

Enter fullscreen mode Exit fullscreen mode

We can also utilize dynamic metadata, which will appear something like this:

export async function generateMetadata({params, searchParams}) {
const product = await getProduct(params.id);
return {title: product.title}
}
Enter fullscreen mode Exit fullscreen mode

Output:


<head>
  <title>My Product </title>
</head>

export default function Page() {
  return (
    <h1>My Next.js page with dynamic metadata</h1>
  );
}

Enter fullscreen mode Exit fullscreen mode

generateMetadata obtains dynamic parameters, such as the product ID.

  • Based on the product ID, it calls the "getProduct" function.
  • As the page title, it returns a dynamic title matching the title of the specific product.

This approach significantly enhances SEO.

Conclusion

A typical Nextjs folder structure includes:


- /app
  - /api
    - /users 
      - route.js
  - page.js
  - layout.js
  - error.js
  - globals.css
- /public
  - ...
- /styles
  - ...
- /components
  - ...
- package.json
- next.config.js
- ...

Enter fullscreen mode Exit fullscreen mode

Applying your knowledge to real-world projects in Next.js is a crucial step to solidify your skills. Practical experience not only reinforces your understanding but also helps you navigate challenges, make informed decisions, and create impactful applications that can benefit users and showcase your expertise. So, embrace the opportunity to turn your knowledge into tangible projects, and in doing so, you'll gain valuable insights and build a strong portfolio of work.

Attributions

Credits

Top comments (0)