Back to blog

The Complete Guide to Next.js Project Setup in 2025

A step-by-step guide to setting up a Next.js project in 2025.

March 7, 2025
@berta.codes
11 min read
The Complete Guide to Next.js Project Setup in 2025

Setting up a Next.js project in 2025 has never been more straightforward, yet the options and configurations available can be overwhelming for newcomers. This guide provides clear, actionable steps to create a production-ready Next.js application with modern best practices.

Why Choose Next.js in 2025? #

Before diving into the setup process, let's quickly review why Next.js remains a top choice for developers:

Unified Development Experience: Server and client components in one codebase

Performance Optimization: Automatic code splitting, image optimization, and font optimization

Flexible Rendering Options: Server-side rendering, static site generation, and client-side rendering

Built-in Routing: File-system based routing with support for layouts, loading states, and error boundaries

Developer Experience: Fast refresh, TypeScript support, ESLint integration, and more

Setting Up a Next.js Project Automatic Installation (Recommended)

The easiest way to start a new Next.js project is using create-next-app, which sets up everything automatically. Here's how to do it:

npx create-next-app@latest my-nextjs-app

During installation, you'll be prompted with several configuration options:

What is your project named? my-nextjs-app
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use src/ directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/<em>)? Yes

What import alias would you like configured? @/</em>

Let's break down these options:

TypeScript: Provides type safety and better developer experience

ESLint: Helps maintain code quality and consistency

Tailwind CSS: A utility-first CSS framework for rapid UI development

src/ directory: Separates your application code from configuration files

App Router: The recommended routing system (vs. the older Pages Router)

Import alias: Creates a shorthand for imports (e.g., @/components/Button instead of ../../components/Button)

Manual Installation

If you prefer more control, you can set up a Next.js project manually:

  1. Create a new project directory and initialize a package.json file:

mkdir my-nextjs-app
cd my-nextjs-app

npm init -y

  1. Install the required dependencies:

npm install next@latest react@latest react-dom@latest

  1. Open your package.json file and add the following scripts:

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

}

  1. Create the basic folder structure:

mkdir -p app public

  1. Create the essential files:

// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/page.js
export default function Home() {
  return (
    <main>
      <h1>Hello, Next.js!</h1>
    </main>
  );

}

Understanding the Project Structure #

After setting up your Next.js project, you'll have a directory structure that looks something like this:

my-nextjs-app/
├── app/                  # App Router directory
│   ├── layout.js         # Root layout (required)
│   └── page.js           # Home page
├── public/               # Static assets
├── src/                  # Optional source directory
├── .eslintrc.json        # ESLint configuration
├── next.config.js        # Next.js configuration
├── package.json          # Project dependencies and scripts

└── tsconfig.json # TypeScript configuration

Key Directories and Files 1. The app Directory

The App Router uses a file-system based router where folders define routes. Here's how it works:

app/
├── layout.js             # Root layout (applied to all pages)
├── page.js               # Home page (/)
├── about/
│   └── page.js           # About page (/about)
├── blog/
│   ├── layout.js         # Blog layout (applied to all blog pages)
│   ├── page.js           # Blog index page (/blog)
│   └── [slug]/           # Dynamic route segment

│ └── page.js # Individual blog post page (/blog/post-1)

Special files in the App Router:

layout.js: Shared UI for a segment and its children

page.js: UI for a route and makes it publicly accessible

loading.js: Loading UI for a segment and its children

error.js: Error UI for a segment and its children

not-found.js: UI for 404 errors

2. The public Directory

The public directory is used for static assets like images, fonts, and other files that don't need to be processed by Next.js. These files are served from the base URL of your application.

public/
├── favicon.ico
├── images/
│   └── hero.jpg
└── fonts/

└── inter.woff2

To use these assets in your application:

// Image from public directory
<img src="/images/hero.jpg" alt="Hero image" />

// Font from public directory
<style jsx global>{
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter.woff2') format('woff2');
  }

}</style>

3. Configuration Files

next.config.js: Configure Next.js behavior

.eslintrc.json: ESLint rules

tsconfig.json: TypeScript configuration

.env.local: Environment variables

Creating Your First Pages and Routes #

Let's create a few pages to understand how routing works in Next.js:

1. Home Page (Already Created)

// app/page.js
export default function Home() {
  return (
    <div>
      <h1>Welcome to My Next.js App</h1>
      <p>This is the home page.</p>
    </div>
  );

}

2. About Page

// app/about/page.js
export default function About() {
  return (
    <div>
      <h1>About Us</h1>
      <p>Learn more about our company.</p>
    </div>
  );

}

3. Blog Index Page

// app/blog/page.js
export default function Blog() {
  return (
    <div>
      <h1>Blog</h1>
      <ul>
        <li>
          <a href="/blog/first-post">First Post</a>
        </li>
        <li>
          <a href="/blog/second-post">Second Post</a>
        </li>
      </ul>
    </div>
  );

}

4. Dynamic Blog Post Page

// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
  return (
    <div>
      <h1>Blog Post: {params.slug}</h1>
      <p>This is the content of the blog post.</p>
    </div>
  );

}

Advanced Routing Features #

Next.js App Router provides several advanced routing features:

1. Layouts

Layouts allow you to share UI between multiple pages. The root layout is required and must contain html and body tags:

// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header>
          <nav>
            <a href="/">Home</a>
            <a href="/about">About</a>
            <a href="/blog">Blog</a>
          </nav>
        </header>
        <main>{children}</main>
        <footer> 2025 My Next.js App</footer>
      </body>
    </html>
  );

}

You can also create nested layouts:

// app/blog/layout.js
export default function BlogLayout({ children }) {
  return (
    <div>
      <h1>Blog</h1>
      <div className="blog-container">
        <div className="content">{children}</div>
        <aside className="sidebar">
          <h2>Recent Posts</h2>
          {/<em> Sidebar content </em>/}
        </aside>
      </div>
    </div>
  );

}

2. Loading States

Create loading UI that displays while page content is loading:

// app/blog/loading.js
export default function Loading() {
  return <div className="loading-spinner">Loading...</div>;

}

3. Error Handling

Create error UI for specific segments:

// app/blog/[slug]/error.js
'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function 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>
  );

}

4. Route Groups

Route groups allow you to organize routes without affecting the URL structure:

app/
├── (marketing)/           # Route group (doesn't affect URL)
│   ├── about/
│   │   └── page.js        # /about
│   └── contact/
│       └── page.js        # /contact
└── (shop)/                # Another route group
    ├── products/
    │   └── page.js        # /products
    └── cart/

└── page.js # /cart

5. Parallel Routes

Parallel routes allow you to simultaneously show multiple pages in the same view:

app/
├── dashboard/
│   ├── layout.js          # Layout that defines slots
│   ├── page.js            # Main dashboard content
│   ├── @stats/            # Stats slot
│   │   └── page.js        # /dashboard (stats section)
│   └── @activity/         # Activity slot

│ └── page.js # /dashboard (activity section)

// app/dashboard/layout.js
export default function DashboardLayout({ children, stats, activity }) {
  return (
    <div className="dashboard">
      <div className="main">{children}</div>
      <div className="stats-panel">{stats}</div>
      <div className="activity-feed">{activity}</div>
    </div>
  );

}

Data Fetching #

Next.js provides multiple ways to fetch data:

1. Server Components (Default)

// app/users/page.js
async function getUsers() {
  const res = await fetch('https://api.example.com/users');
  return res.json();
}

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );

}

2. Client Components

// app/counter/page.js
'use client';

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const res = await fetch('/api/data');
      const json = await res.json();
      setData(json);
    }

    fetchData();
  }, []);

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {data && <div>Data: {JSON.stringify(data)}</div>}
    </div>
  );

}

3. API Routes

// app/api/hello/route.js
export async function GET() {
  return Response.json({ message: 'Hello, Next.js!' });
}

export async function POST(request) {
  const body = await request.json();
  return Response.json({
    message: 'Data received',
    data: body,
  });

}

Styling Your Next.js Application #

Next.js supports various styling methods:

1. Global CSS

// app/globals.css
body {
  font-family: 'Inter', sans-serif;
  margin: 0;
  padding: 0;
  color: #333;
}

h1 {
  font-size: 2.5rem;
}

/<em> Import in your root layout </em>/
// app/layout.js

import './globals.css';

2. CSS Modules

// app/components/Button.module.css
.button {
  padding: 0.5rem 1rem;
  background-color: #0070f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button:hover {
  background-color: #0051a2;
}

// app/components/Button.js
import styles from './Button.module.css';

export default function Button({ children }) {
  return (
    <button className={styles.button}>
      {children}
    </button>
  );

}

3. Tailwind CSS

If you selected Tailwind CSS during setup:

// app/page.js
export default function Home() {
  return (
    <div className="p-6 max-w-4xl mx-auto">
      <h1 className="text-3xl font-bold text-blue-600">
        Welcome to My Next.js App
      </h1>
      <p className="mt-4 text-gray-700">
        This page is styled with Tailwind CSS.
      </p>
      <button className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
        Get Started
      </button>
    </div>
  );

}

Optimizing Images and Fonts #

Next.js provides built-in components for optimizing images and fonts:

1. Image Optimization

// app/gallery/page.js
import Image from 'next/image';

export default function Gallery() {
  return (
    <div>
      <h1>Image Gallery</h1>
      <div className="grid grid-cols-2 gap-4">
        <Image
          src="/images/photo1.jpg"
          alt="Photo 1"
          width={500}
          height={300}
          priority
        />
        <Image
          src="/images/photo2.jpg"
          alt="Photo 2"
          width={500}
          height={300}
        />
      </div>
    </div>
  );

}

2. Font Optimization

// app/layout.js
import { Inter, Roboto_Mono } from 'next/font/google';

// Load Inter font for general text
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
});

// Load Roboto Mono for code blocks
const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>
        <main>{children}</main>
        <pre className={robotoMono.className}>
          {function hello() {
  console.log('Hello, Next.js!');
}}
        </pre>
      </body>
    </html>
  );

}

Environment Variables and Configuration #

1. Environment Variables

Create a .env.local file in your project root:

# .env.local
DATABASE_URL=postgres://user:password@localhost:5432/mydb

API_KEY=your_api_key_here

Access environment variables in your code:

// Server Component (secure, not exposed to the client)
export default async function Page() {
  const apiKey = process.env.API_KEY;
  // Use apiKey to fetch data
}

// For client-side code, prefix with NEXT_PUBLIC_
// .env.local
NEXT_PUBLIC_ANALYTICS_ID=UA-123456789-1

// Client Component
'use client';

export default function AnalyticsComponent() {
  const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;
  // Use analyticsId

}

2. Next.js Configuration

Customize Next.js behavior with next.config.js:

// next.config.js
/<em> @type {import('next').NextConfig} </em>/
const nextConfig = {
  // Enable React strict mode
  reactStrictMode: true,

  // Configure image domains for next/image
  images: {
    domains: ['example.com', 'cdn.example.com'],
  },

  // Add redirects
  async redirects() {
    return [
      {
        source: '/old-blog/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
    ];
  },

  // Add headers
  async headers() {
    return [
      {
        source: '/:path',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Deploying Your Next.js Application #

Next.js applications can be deployed to various platforms:

1. Vercel (Recommended)

# Install Vercel CLI
npm install -g vercel

# Deploy

vercel

2. Self-Hosting

Build your application:

npm run build

Start the production server:

npm run start

Conclusion #

Setting up a Next.js project in 2025 has never been easier. With the App Router, server components, and built-in optimizations, Next.js provides a powerful foundation for building modern web applications.

By following this guide, you've learned how to:

• Set up a Next.js project using automatic or manual installation

• Understand the project structure and routing system

• Create pages, layouts, and handle loading and error states

• Implement data fetching strategies

• Style your application with various methods

• Optimize images and fonts

• Configure environment variables and Next.js settings

• Deploy your application

Next.js continues to evolve with each release, so be sure to check the official documentation for the latest features and best practices.

Happy coding!

Share this post

This website uses cookies to analyze traffic and enhance your experience. By clicking "Accept", you consent to our use of cookies for analytics purposes. You can withdraw your consent at any time by changing your browser settings. Cookie Policy