Command Palette

Search for a command to run...

Blog
PreviousNext

The Complete Beginner's Guide to Next.js: From Zero to Production

Next.js is a powerful React framework that enables you to build full-stack web applications with ease. It provides server-side rendering, static site generation, API routes, and many other features out of the box. Whether you're building a simple blog or a complex enterprise application, Next.js has you covered.


Part 1: Prerequisites and Installation

Requirements

Before we begin, make sure you have:

  1. Node.js 18.17 or later - Download here
  2. npm, yarn, pnpm, or bun - Any package manager works
  3. Code Editor - VS Code is recommended
  4. Terminal/Command Line - Basic familiarity

Step 1: Installation

Open your terminal and run the following command to create a new Next.js application:

# Using pnpm (recommended for speed)
pnpm create next-app@latest my-app --yes
 
# Using npm
npx create-next-app@latest my-app --yes
 
# Using yarn
yarn create next-app my-app --yes
 
# Using bun
bun create next-app my-app --yes

The --yes flag accepts all default options. Let's break down what this command does:

  1. Creates a new directory called my-app
  2. Sets up a complete Next.js project with TypeScript, ESLint, and Tailwind CSS
  3. Configures the project with best practices

Step 2: Navigate and Run

# Change to your project directory
cd my-app
 
# Start the development server
pnpm dev
# or
npm run dev
# or
yarn dev
# or
bun dev

Step 3: View Your Application

Open your browser and visit:

http://localhost:3000

You should see the default Next.js starter page! 🎉


Part 2: Understanding the Project Structure

Understanding how Next.js organizes files is crucial. Let's explore the structure created by create-next-app.

Top-Level Folders

my-app/
├── app/              # App Router (primary routing system)
├── public/           # Static assets (images, fonts, etc.)
├── src/              # Optional source folder (if you choose this option)
├── node_modules/     # Dependencies
└── [various config files]

Top-Level Files Explained

FilePurposeTrack in Git?
next.config.jsNext.js configuration✅ Yes
package.jsonDependencies and scripts✅ Yes
tsconfig.jsonTypeScript configuration✅ Yes
.envEnvironment variables❌ No
.env.localLocal environment variables❌ No
.gitignoreFiles to ignore in Git✅ Yes
next-env.d.tsNext.js TypeScript types❌ No
eslint.config.mjsESLint configuration✅ Yes

The app Directory Structure

The app directory uses file-system based routing. Each folder represents a route segment, and files define the UI for those routes.

app/
├── layout.tsx        # Root layout (wraps all pages)
├── page.tsx          # Home page (/)
├── globals.css       # Global styles
└── favicon.ico       # Site favicon

Part 3: Core Concepts - Layouts and Pages

What is a Page?

A page is UI that renders at a specific route. Create pages by adding page.tsx files.

Example: Home Page (/)

export default function HomePage() {
  return (
    <main className="min-h-screen p-8">
      <h1 className="text-4xl font-bold">Welcome to My App!</h1>
      <p className="mt-4 text-lg">
        This is your home page. Edit this file to customize.
      </p>
    </main>
  );
}

What is a Layout?

A layout is UI that's shared between multiple pages. It preserves state during navigation.

Example: Root Layout

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
 
const inter = Inter({ subsets: ["latin"] });
 
export const metadata: Metadata = {
  title: "My Next.js App",
  description: "A beginner-friendly Next.js tutorial",
};
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {/* Header that appears on all pages */}
        <header className="bg-blue-600 p-4 text-white">
          <nav className="container mx-auto">
            <h2 className="text-2xl font-bold">My App</h2>
          </nav>
        </header>
 
        {/* Main content area - pages render here */}
        <main className="container mx-auto p-4">{children}</main>
 
        {/* Footer that appears on all pages */}
        <footer className="mt-8 bg-gray-800 p-4 text-white">
          <div className="container mx-auto text-center">
            © 2025 My App. All rights reserved.
          </div>
        </footer>
      </body>
    </html>
  );
}

Creating Nested Routes

Want a /blog section? Just create a folder!

app/
├── blog/
│   ├── layout.tsx    # Layout specific to /blog
│   ├── page.tsx      # Blog list page (/blog)
│   └── [slug]/
│       └── page.tsx  # Individual blog post

Blog Layout:

export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="blog-container">
      <aside className="sidebar">
        <h3>Blog Categories</h3>
        {/* Sidebar content */}
      </aside>
      <article className="content">{children}</article>
    </div>
  );
}

Blog List Page:

import Link from "next/link";
 
// Mock data - in real apps, fetch from database or API
const blogPosts = [
  { id: 1, slug: "first-post", title: "My First Blog Post" },
  { id: 2, slug: "second-post", title: "Learning Next.js" },
];
 
export default function BlogPage() {
  return (
    <div>
      <h1 className="mb-6 text-3xl font-bold">Blog Posts</h1>
      <ul className="space-y-4">
        {blogPosts.map((post) => (
          <li key={post.id} className="rounded-lg border p-4">
            <Link
              href={`/blog/${post.slug}`}
              className="text-xl text-blue-600 hover:underline"
            >
              {post.title}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Part 4: Dynamic Routes

Dynamic routes allow you to create pages from data. Use square brackets [] to create dynamic segments.

Creating a Dynamic Blog Post Page

interface BlogPostPageProps {
  params: Promise<{ slug: string }>;
}
 
// This function runs at build time to generate static paths
export async function generateStaticParams() {
  return [
    { slug: "first-post" },
    { slug: "second-post" },
    { slug: "third-post" },
  ];
}
 
// Mock function to fetch blog post data
async function getPost(slug: string) {
  // In a real app, fetch from database or CMS
  const posts = {
    "first-post": {
      title: "My First Blog Post",
      content: "This is the content of my first blog post...",
      date: "2025-01-15",
    },
    "second-post": {
      title: "Learning Next.js",
      content: "Next.js makes React development so much easier!",
      date: "2025-01-20",
    },
  };
 
  return posts[slug] || null;
}
 
export default async function BlogPostPage({ params }: BlogPostPageProps) {
  const { slug } = await params;
  const post = await getPost(slug);
 
  if (!post) {
    return <div>Post not found</div>;
  }
 
  return (
    <article className="mx-auto max-w-3xl">
      <h1 className="mb-4 text-4xl font-bold">{post.title}</h1>
      <time className="text-gray-600">{post.date}</time>
      <div className="prose mt-8">
        <p>{post.content}</p>
      </div>
    </article>
  );
}

Types of Dynamic Segments

PatternExample FolderMatchesNotes
[slug]app/blog/[slug]/blog/helloSingle parameter
[...slug]app/shop/[...slug]/shop/a/b/cCatch-all segments
[[...slug]]app/docs/[[...slug]]/docs or /docs/a/bOptional catch-all

Part 5: Handling Query Parameters

Server Components with Search Params

interface SearchPageProps {
  searchParams: Promise<{ q?: string }>;
}
 
export default async function SearchPage({ searchParams }: SearchPageProps) {
  const { q } = await searchParams;
 
  return (
    <div>
      <h1 className="mb-6 text-3xl font-bold">Search</h1>
 
      {/* Search form */}
      <form className="mb-8">
        <input
          type="text"
          name="q"
          defaultValue={q || ""}
          placeholder="Search..."
          className="w-full max-w-md rounded border p-2"
        />
        <button
          type="submit"
          className="ml-2 rounded bg-blue-600 px-4 py-2 text-white"
        >
          Search
        </button>
      </form>
 
      {/* Search results */}
      {q && (
        <div>
          <h2 className="mb-4 text-xl font-semibold">Results for: {q}</h2>
          {/* Display search results here */}
        </div>
      )}
    </div>
  );
}

Visit: http://localhost:3000/search?q=nextjs

Client Components with Search Params

"use client";
 
import { useSearchParams, useRouter } from "next/navigation";
 
export default function ProductFilter() {
  const searchParams = useSearchParams();
  const router = useRouter();
 
  const currentCategory = searchParams.get("category") || "all";
 
  const categories = ["all", "electronics", "clothing", "books"];
 
  function handleCategoryChange(category: string) {
    const params = new URLSearchParams(searchParams.toString());
 
    if (category === "all") {
      params.delete("category");
    } else {
      params.set("category", category);
    }
 
    router.push(`/products?${params.toString()}`);
  }
 
  return (
    <div className="mb-6 flex gap-2">
      {categories.map((category) => (
        <button
          key={category}
          onClick={() => handleCategoryChange(category)}
          className={`rounded px-4 py-2 ${
            currentCategory === category
              ? "bg-blue-600 text-white"
              : "bg-gray-200"
          }`}
        >
          {category.charAt(0).toUpperCase() + category.slice(1)}
        </button>
      ))}
    </div>
  );
}

Part 6: Route Organization Tips

Route Groups

Organize routes without affecting URLs:

app/
├── (marketing)/
│   ├── layout.tsx    # Marketing pages layout
│   ├── page.tsx      # /
│   └── about/
│       └── page.tsx  # /about
├── (dashboard)/
│   ├── layout.tsx    # Dashboard layout
│   ├── page.tsx      # /dashboard
│   └── settings/
│       └── page.tsx  # /dashboard/settings

Private Folders

Prefix with underscore _ to make folders non-routable:

app/
├── blog/
│   ├── page.tsx      # /blog
│   ├── _components/  # NOT accessible via URL
│   │   ├── PostCard.tsx
│   │   └── AuthorBio.tsx
│   └── _lib/         # NOT accessible via URL
│       ├── data.ts
│       └── utils.ts

Part 7: TypeScript Helpers

Next.js provides built-in TypeScript helpers for better type safety:

PageProps Helper

export default async function Page(props: PageProps<"/blog/[slug]">) {
  // TypeScript knows params has a 'slug' property
  const { slug } = await props.params;
  const searchParams = await props.searchParams;
 
  return <h1>Blog post: {slug}</h1>;
}

LayoutProps Helper

export default function Layout(props: LayoutProps<"/dashboard">) {
  return (
    <div>
      {props.children}
      {/* TypeScript knows about @analytics slot if it exists */}
      {/* {props.analytics} */}
    </div>
  );
}

Part 8: Best Practices for Beginners

1. Start Simple

Begin with static pages before adding dynamic features.

2. Use the App Router

The App Router (using app/ directory) is the recommended way to build Next.js apps.

3. Organize by Feature

app/
├── features/
│   ├── auth/
│   ├── blog/
│   └── shop/
├── components/   # Shared components
├── lib/         # Utilities and config
└── types/       # TypeScript definitions

4. Use Server Components by Default

  • Fetch data in server components
  • Use client components only when you need interactivity or browser APIs

5. Implement Error Boundaries

"use client";
 
export default function ErrorBoundary({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="p-8 text-center">
      <h2 className="text-2xl font-bold text-red-600">Something went wrong!</h2>
      <p className="mt-2">{error.message}</p>
      <button
        onClick={reset}
        className="mt-4 rounded bg-blue-600 px-4 py-2 text-white"
      >
        Try again
      </button>
    </div>
  );
}

6. Add Loading States

export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="mb-4 h-8 w-3/4 rounded bg-gray-300"></div>
      <div className="mb-2 h-4 w-1/2 rounded bg-gray-300"></div>
      <div className="h-4 w-2/3 rounded bg-gray-300"></div>
    </div>
  );
}

Part 9: Next Steps

What to Learn Next

  1. Data Fetching - fetch(), caching, revalidation
  2. API Routes - Create backend endpoints
  3. Middleware - Handle requests before they reach your pages
  4. Authentication - NextAuth.js or similar
  5. Database Integration - Prisma, Supabase, etc.
  6. Deployment - Vercel, Netlify, or self-hosted

Project Ideas for Practice

  1. Personal portfolio website
  2. Blog with markdown content
  3. E-commerce product catalog
  4. Dashboard with charts and data
  5. Social media feed clone

Summary

You've learned:

  • ✅ How to install and set up Next.js
  • ✅ Understanding the project structure
  • ✅ Creating layouts and pages
  • ✅ Building nested and dynamic routes
  • ✅ Handling query parameters
  • ✅ Organizing your project effectively
  • ✅ TypeScript integration
  • ✅ Best practices for beginners

Remember: Practice is key! Start building small projects, experiment with features, and don't hesitate to check the official Next.js documentation when you get stuck.

Happy coding! 🚀

Next.js Version: 16.1.6
Last Updated: December 9, 2025