Install Next.js and Build Your First App
A practical walkthrough for installing Next.js 15 with create-next-app, understanding every file in the generated project, running the dev server, and shipping a production build.
What you'll learn
- ✓How to scaffold a Next.js 15 project with create-next-app
- ✓What every file in a fresh Next.js project actually does
- ✓How the dev server, fast refresh, and production build work
- ✓How to edit app/page.tsx and see changes instantly
- ✓The difference between npm run dev, build, and start
Prerequisites
- •A first reading of What is Next.js?
- •Node.js 20 or newer installed — see React Setup with Vite for the Node install
This post takes you from an empty folder to a running Next.js 15 application in about ten minutes. We use the official create-next-app scaffolder, tour the generated project file by file, and finish with a production build you could deploy today.
Before you start
Confirm you have a modern Node:
# Check Node version — must be 20 or newer
node --version # v22.x.x is ideal
npm --version # 10.x.x or newer
If you are below Node 20, install the current LTS from nodejs.org or via a version manager like fnm or nvm. Next.js 15 will refuse to run on older Node.
You can use npm, pnpm, yarn, or bun. Every command in this post works with all four — just swap the package manager name.
Step 1: Scaffold a new project
Pick a directory where you keep code, then run:
# Create a new Next.js project named my-next-app
npx create-next-app@latest my-next-app
The installer asks a handful of questions. Sensible defaults for this series:
? 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 your code inside a `src/` directory? › No
? Would you like to use App Router? › Yes
? Would you like to use Turbopack for `next dev`? › Yes
? Would you like to customize the import alias? › No
Why these answers:
- TypeScript is the default in modern Next.js. The type errors will help you more than they hurt.
- App Router is the only choice for new projects. Pages Router is in maintenance mode.
- Turbopack is the default dev bundler in Next.js 15. It is much faster than Webpack.
- No
src/directory keeps the file tree shorter for learning. You can always add it later.
When the install finishes:
cd my-next-app
npm run dev
Open the URL it prints — almost always http://localhost:3000. You should see the Next.js welcome page. That is a complete, modern Next.js 15 app running on your machine.
Step 2: Tour the project
Open the project in your editor. The tree looks like this:
my-next-app/
├── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── public/
│ ├── next.svg
│ └── vercel.svg
├── .eslintrc.json
├── .gitignore
├── next-env.d.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── tailwind.config.ts
└── tsconfig.json
A short tour of what you will actually touch.
app/page.tsx
The home page of your site, mapped to the / URL.
// app/page.tsx
export default function Home() {
return (
<main>
<h1>Welcome to Next.js</h1>
</main>
);
}
This is a React Server Component. It runs on the server, returns JSX, and Next.js streams the resulting HTML to the browser. There is no ReactDOM.render, no createRoot, no entry-point file you wire up by hand.
app/layout.tsx
The root layout. It wraps every page in the app and is the only place where the <html> and <body> tags live.
// app/layout.tsx
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: 'My Next App',
description: 'Built with Next.js 15',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Two things to notice:
metadatais the modern, type-safe way to set the page title and meta tags. You no longer need<Head>.childrenis the current page. The router slots it in for every route.
We cover layouts in depth in The Next.js App Router: Pages, Layouts, and Routing.
app/globals.css
Global styles. The default file imports Tailwind’s three layers:
@tailwind base;
@tailwind components;
@tailwind utilities;
You can delete those if you do not want Tailwind, or extend them with your own rules.
public/
Static assets. Anything in public/ is served at the root of your site. A file at public/logo.png is reachable at /logo.png. Use this for favicons, robots.txt, and images you reference by URL.
next.config.ts
Next.js configuration. The default is almost empty, which is the point.
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// your config goes here
};
export default nextConfig;
You will edit this when you add an image domain, set up redirects, or enable an experimental feature. Not on day one.
package.json
The project manifest. The scripts section is what matters:
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
npm run dev— the development server you are already runningnpm run build— produces a production build in.next/npm run start— serves the production build (runbuildfirst)npm run lint— runs ESLint across the project
tsconfig.json and next-env.d.ts
TypeScript config and Next.js’s auto-generated type declarations. You can ignore both for a while. The @/* import alias is defined here — that is why you will write import x from '@/components/x' instead of long relative paths.
Step 3: Make a first edit
With the dev server running, open app/page.tsx and replace it with:
// app/page.tsx
export default function Home() {
return (
<main style={{ fontFamily: 'system-ui', padding: '2rem' }}>
<h1>My Next.js App</h1>
<p>This page is rendered by a React Server Component.</p>
<p>Server time: {new Date().toLocaleTimeString()}</p>
</main>
);
}
Save the file. The browser updates instantly — that is Fast Refresh in action. Notice the time: it is the server’s time, computed when the page was rendered, not the browser’s. Refresh the page and the time changes. That is your first hint that this code is running on a server, not in the browser.
Try it yourself. Add a second paragraph that displays process.env.NODE_ENV. Save and confirm it reads development. Now stop the dev server, run npm run build followed by npm run start, and reload. The value should switch to production. You just observed the difference between the two modes.
Step 4: Add a second page
The App Router uses folders for routes. Create app/about/page.tsx:
// app/about/page.tsx
export default function About() {
return (
<main style={{ padding: '2rem' }}>
<h1>About</h1>
<p>This page is served at /about.</p>
</main>
);
}
Visit http://localhost:3000/about. The page is live. You did not register a route anywhere — the folder created it.
Now link the two pages together. Edit app/page.tsx:
// app/page.tsx
import Link from 'next/link';
export default function Home() {
return (
<main style={{ padding: '2rem' }}>
<h1>Home</h1>
<Link href="/about">Go to About</Link>
</main>
);
}
next/link is Next’s client-side navigation primitive. Clicking the link does not reload the page — Next swaps in the new route while keeping the layout mounted. We cover this in detail in the next post.
Step 5: Build for production
When you eventually want to deploy:
# Compile the app to .next/
npm run build
# Serve the production build locally
npm run start
The build output tells you which routes are static, which are dynamic, and how big each route’s JavaScript payload is. Reading that output is a useful habit — it is the fastest way to spot accidentally enormous bundles.
A typical line looks like:
Route (app) Size First Load JS
┌ ○ / 142 B 87.5 kB
└ ○ /about 120 B 87.4 kB
The little circle means statically rendered at build time. A solid circle (●) means server-rendered on every request. A lambda icon (ƒ) means dynamic. We unpack what triggers each mode in Data Fetching in Next.js.
To actually ship: push to GitHub and connect the repo to a host. Vercel is one click; Netlify, Cloudflare Pages, and a Node container all work. We will cover deployment in a later post.
Try it yourself. Create a third page at app/contact/page.tsx, add it to the home page nav, then run npm run build. Read the route table at the end. Confirm all three routes are statically rendered. Now change the home page to display new Date().toISOString() and rebuild. The home route should still be static — Next.js renders it once at build time. Add export const dynamic = 'force-dynamic'; at the top of the file and rebuild. Now it is dynamic. You just learned the single most important caching knob in the App Router.
Things that commonly trip people up
A short checklist for when something is not working.
Port 3000 in use— pass--port 3001tonext dev, or kill the other process.Module not found: @/...— the@/*alias maps to the project root by default. If you chosesrc/, it maps tosrc/. Checktsconfig.json.- A page renders, but
useStatethrows — Server Components cannot use hooks. Add'use client'at the top of the file. We cover this in Next.js Server Components Explained. - Stale data after
npm run build— Next cachesfetch()calls by default. We unpack the cache rules in the data-fetching post. - Tailwind classes do nothing — confirm
tailwind.config.tsincludes./app/**/*.{ts,tsx}in itscontentarray.
Useful editor setup
Two extensions in VS Code are worth installing on day one:
- ESLint — surfaces problems as you type. The Next template ships a working config.
- Tailwind CSS IntelliSense — autocompletes class names and shows the resolved CSS.
Optionally, install React DevTools for Chrome or Firefox. It works the same in Next.js as in any React app, with a small twist: Server Components appear in the tree but cannot be inspected the same way — they have already run on the server.
Recap
You now have:
- A fresh Next.js 15 project scaffolded with
create-next-app - An understanding of
app/page.tsx,app/layout.tsx, and thepublic/folder - A working dev server with Fast Refresh
- A second route added by creating a folder
- A production build command that produces a deployable
.next/directory - An eye for the route table that
next buildprints
Next steps
The next post zooms into the router itself — pages, layouts, nested routes, dynamic segments, Link, useRouter, and the special loading.tsx and error.tsx files.
→ Next: The Next.js App Router: Pages, Layouts, and Routing
Questions or feedback? Email codeloomdevv@gmail.com.