Next.js 16 Project Setup & App Router: Your First Page

Step 1 of 31Next.js Tutorial Series | Source code for this step

Live Demo →


Commands in This Step

CommandPurpose
pnpm create next-app@latest nextjs-16-crud --yesScaffold a new Next.js project

What You Will Build

↑ Index

By the end of this step you will have a running Next.js application with a custom home page styled with Tailwind CSS. You will understand the core architecture that makes Next.js different from plain React.

Goal: See your own "Superblog" home page at http://localhost:3000.


Table of Contents

  1. What is Next.js? (For React Developers)
  2. Create the Project
  3. Project Structure Explained
  4. Core Concept: The App Router
  5. Core Concept: Server Components
  6. Understanding layout.tsx — The Root Layout
  7. Understanding page.tsx — Your Route Component
  8. Customize Your Home Page
  9. Update the Root Layout
  10. Run the App
  11. Summary & Key Takeaways

What is Next.js? (For React Developers)

↑ Index

You already know React — it is a library for building UI with components. But React alone does not give you:

  • Routing — navigating between pages (you need React Router or similar)
  • Server-side rendering (SSR) — generating HTML on the server for faster load and SEO
  • API endpoints — handling backend logic like database queries
  • File-based routing — creating pages simply by adding files, no configuration
  • Built-in optimizations — image optimization, font loading, code splitting

Next.js is a full-stack framework built on top of React that provides all of these out of the box. Think of it this way:

React = the engine. Next.js = the complete car.

Next.js takes your React components and adds the infrastructure around them: a server, a router, a bundler (Turbopack), and production optimizations.


Create the Project

↑ Index

Open your terminal, navigate to the folder where you want the project, and run:

pnpm create next-app@latest nextjs-16-crud --yes

This command scaffolds a new Next.js project with all the recommended defaults:

  • TypeScript — type safety
  • Tailwind CSS v4 — utility-first styling
  • App Router — the modern routing system
  • Turbopack — fast dev server bundler

Now enter the project folder:

cd nextjs-16-crud

Project Structure Explained

↑ Index

After creation, your project looks like this:

nextjs-16-crud/
├── app/
│   ├── favicon.ico        # Browser tab icon
│   ├── globals.css         # Global CSS (Tailwind is imported here)
│   ├── layout.tsx          # Root layout — wraps ALL pages
│   └── page.tsx            # Home page — the "/" route
├── public/                 # Static files (images, SVGs)
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── eslint.config.mjs       # Linting rules
├── next.config.ts          # Next.js configuration
├── package.json            # Dependencies and scripts
├── postcss.config.mjs      # PostCSS config (used by Tailwind v4)
├── tsconfig.json           # TypeScript configuration
└── pnpm-lock.yaml          # Locked dependency versions

The most important directory is app/ — this is where all your pages and UI live.

Key rule: Only two files inside app/ actually matter for routing:

  • layout.tsx — the wrapper (shell) around your pages
  • page.tsx — the actual page content for a given URL

Every other file (globals.css, favicon.ico) is just a supporting file that gets imported or served.


Core Concept: The App Router

↑ Index

Next.js has two routing systems. The modern one is called the App Router (introduced in Next.js 13). It uses the app/ directory.

The fundamental rule is simple:

Every folder inside app/ that contains a page.tsx becomes a URL route.

File pathURL
app/page.tsx/
app/about/page.tsx/about
app/posts/page.tsx/posts
app/posts/new/page.tsx/posts/new

This is called file-based routing. You do not need React Router, createBrowserRouter, or any routing library. You just create folders and files.

Why is this better? In plain React, you would write:

// Plain React — you configure routes manually
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/posts" element={<Posts />} />
</Routes>

In Next.js, you skip all of that. The file system is the router.


Core Concept: Server Components

↑ Index

This is the single biggest difference between plain React and Next.js App Router.

In regular React, every component runs in the browser. The browser downloads JavaScript, executes it, and renders the UI. This is called Client-Side Rendering (CSR).

In the App Router, every component is a Server Component by default. This means:

FeatureServer Component (default)Client Component ("use client")
Where it runsOn the serverIn the browser
Can access database✅ Yes❌ No
Can use useState❌ No✅ Yes
Can use useEffect❌ No✅ Yes
Can use onClick❌ No✅ Yes
Sends JavaScript to browser❌ No (just HTML)✅ Yes

Why Server Components?

  • Faster page loads — the server sends ready-made HTML, not JavaScript the browser has to execute
  • Direct data access — you can query a database right inside the component, no API layer needed
  • Smaller bundle size — server-only code never reaches the browser

When you need interactivity (state, effects, click handlers), you add "use client" at the very top of the file:

'use client' // ← This makes it a Client Component

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>
}

The rule of thumb: Start with Server Components. Only add "use client" when you need browser interactivity. We will see this in practice in Step 3.


Understanding layout.tsx — The Root Layout

↑ Index

Open app/layout.tsx. This is the Root Layout — the outermost wrapper for your entire application.

import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  )
}

Let's break down what each part does:

Fonts

const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'] })

Next.js has a built-in font optimization system (next/font). Instead of adding a <link> to Google Fonts (which blocks page rendering), Next.js downloads the font at build time and serves it as a local file. This is faster and avoids layout shift.

The variable option creates a CSS custom property (--font-geist-sans) so you can reference the font in Tailwind or CSS.

Metadata

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

In plain React, you would use react-helmet or manually set document.title. In Next.js, you export a metadata object from any layout.tsx or page.tsx, and Next.js automatically generates the <head> tags (<title>, <meta>, etc.).

This works because the component runs on the server — it can set the HTML head before the page reaches the browser.

💡 Tip: You can export metadata from both layout.tsx (applies to all pages that layout wraps) and from individual page.tsx files (overrides the layout's metadata for that specific page). In Step 2 we will add per-page metadata to each of our new pages and see exactly how the inheritance works.

Server Components only. export const metadata is only valid in Server Components. If you add 'use client' to a file, you cannot export metadata from it — Next.js will throw a build error. For pages that need client-side interactivity and custom metadata, keep the metadata export in a parent layout or a separate server component wrapper.

The Layout Component

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

This is the only place in a Next.js app where you write <html> and <body> tags. The Root Layout is required — every app must have one.

The {children} prop is where your page content gets injected. When you visit /, the children will be the component from app/page.tsx. When you visit /about, it will be from app/about/page.tsx.

Key insight: The Root Layout is a Server Component. It wraps every single page in your app. This is where you put shared UI like headers, footers, and global providers.


Understanding page.tsx — Your Route Component

↑ Index

Open app/page.tsx. This is the component that renders when someone visits / (the home page).

The important thing to understand is the naming convention:

File namePurpose
page.tsxThe UI for a route — this is what users see
layout.tsxShared wrapper that persists across child routes
loading.tsxLoading UI shown while page is loading
error.tsxError UI shown when something fails
not-found.tsx404 page
route.tsAPI endpoint (no UI)

For now, we only care about page.tsx and layout.tsx. We will use the others in later steps.

The rule: The default export of page.tsx is what Next.js renders for that route. It must be a default export.


Customize Your Home Page

↑ Index

Now let's make this project our own. Replace the entire contents of app/page.tsx with:

export default function Home() {
  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white shadow-sm">
        <div className="mx-auto max-w-4xl px-6 py-4">
          <h1 className="text-2xl font-bold text-gray-900">Superblog</h1>
        </div>
      </header>

      <main className="mx-auto max-w-4xl px-6 py-12">
        <h2 className="mb-4 text-4xl font-bold text-gray-900">
          Welcome to Superblog
        </h2>
        <p className="mb-8 text-lg text-gray-600">
          A full-stack blog application built with Next.js, Prisma, and
          NextAuth.js. We are building this step by step.
        </p>

        <div className="grid gap-6 md:grid-cols-2">
          <div className="rounded-lg bg-white p-6 shadow-md">
            <h3 className="mb-2 text-lg font-semibold text-gray-800">
              📝 Blog Posts
            </h3>
            <p className="text-gray-600">
              Create, read, and delete blog posts. Coming once we connect to the
              database.
            </p>
          </div>

          <div className="rounded-lg bg-white p-6 shadow-md">
            <h3 className="mb-2 text-lg font-semibold text-gray-800">
              🔐 Authentication
            </h3>
            <p className="text-gray-600">
              User registration and login with NextAuth.js. Coming in a later
              step.
            </p>
          </div>
        </div>
      </main>

      <footer className="mx-auto max-w-4xl px-6 py-8 text-center text-sm text-gray-400">
        Built with Next.js 16, React 19, Prisma & NextAuth.js
      </footer>
    </div>
  )
}

Let's examine what is happening here and why:

  1. This is a Server Component — notice there is no "use client" at the top. This component runs entirely on the server. The server generates the HTML and sends it to the browser. Zero JavaScript is shipped to the client for this page.

  2. No imports needed — we are using only HTML elements and Tailwind CSS classes. No React hooks, no state, no effects. Server Components are perfect for static or data-driven pages.

  3. Tailwind CSS classesmin-h-screen, bg-gray-50, max-w-4xl, mx-auto, shadow-md, etc. These are utility classes provided by Tailwind CSS v4, which was set up automatically when we created the project. Each class maps to a single CSS property.

  4. The default exportexport default function Home() is what Next.js renders when the user visits /. The function name (Home) can be anything, but the export default is required.


Update the Root Layout

↑ Index

Now update app/layout.tsx to reflect our project. Replace the entire contents with:

import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Superblog',
  description: 'A full-stack blog built with Next.js, Prisma, and NextAuth.js',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  )
}

What changed and why:

  • We updated metadata.title to "Superblog" — this sets the browser tab title. Every page in our app will inherit this title unless a specific page overrides it.
  • We updated metadata.description — this sets the <meta name="description"> tag, which search engines display in results.
  • Everything else stays the same — the font setup and {children} rendering are already correct.

Next.js concept: Metadata inheritance. When you set metadata in layout.tsx, all pages that this layout wraps inherit it. A page.tsx can override individual fields by exporting its own metadata object — Next.js merges them, with the page value taking precedence.


Run the App

↑ Index

Start the development server:

pnpm dev

Open your browser and visit http://localhost:3000.

You should see your custom Superblog home page with:

  • A header with "Superblog" text
  • A welcome message
  • Two feature cards (Blog Posts and Authentication)
  • A footer

Congratulations! You have a running Next.js application. Everything you see was rendered on the server as a Server Component — the browser received pure HTML with zero client-side JavaScript for this page.


Summary & Key Takeaways

↑ Index

Here is what you learned in this step:

ConceptWhat it means
Next.jsA full-stack React framework with routing, SSR, and APIs built in
App RouterThe modern routing system using the app/ directory
File-based routingapp/page.tsx = /, app/about/page.tsx = /about
page.tsxThe component rendered for a specific URL route
layout.tsxA wrapper component shared across pages (provides <html> and <body>)
Server ComponentsComponents that run on the server by default — no JS sent to browser
Client ComponentsComponents marked with "use client" that run in the browser
metadata exportHow Next.js sets <title> and <meta> tags — no react-helmet needed
next/fontBuilt-in font optimization — downloads fonts at build time

What is Next

In Step 2, we will create new pages (/about, /posts) and learn how file-based routing works in practice. You will see that creating a new page is as simple as creating a new folder with a page.tsx file inside it.