Skip to content
VerifiedUpdated 2026-06-13
On this page

This is the start of every project I ship. Follow it top to bottom and you will end with a running Next.js app that is already structured the way the rest of this handbook assumes. The goal is a boring, repeatable start, because a boring start is what lets the interesting work stay fast later.

The path

Create the app

Run the official generator. Do not click through the prompts blindly; the flags below are the choices that matter.

terminal
npx create-next-app@latest my-app --typescript --eslint --app
cd my-app
  • --typescript is not optional for me. The first time a refactor would have silently broken three call sites, types pay for themselves.
  • --app selects the App Router, which gives you Server Components by default. That is the single biggest lever for shipping less JavaScript to the browser.
  • --eslint wires up linting from the first commit, so problems surface while they are cheap to fix.

Run it and confirm

terminal
npm run dev

Open http://localhost:3000. You should see the starter page. If the dev server starts and the page loads, your toolchain is healthy and you can build on it.

Settle the folder shape

Decide where things live now, because retrofitting structure hurts. The rule:

app/ is for routing only. Pages and layouts stay thin. Real logic lives in reusable modules they import.

A shape that scales:

project structure
app/            routes, layouts, route handlers only
components/     reusable UI
lib/            data access, validation, integrations, types
content/        markdown / data, kept out of app/
public/         static assets

The point is that app/ never becomes the place business logic hides. When you need to find how something works, you look in lib/, not in a route file.

Write your first route the secure way

Server Components are the default in the App Router. Use that: fetch data on the server, send the browser only what it needs.

app/page.tsx
// A Server Component by default. No "use client" here.
export default async function Page() {
  const message = await getWelcome();
  return <h1>{message}</h1>;
}
 
async function getWelcome() {
  // Runs on the server. Secrets, database calls, and heavy work stay here.
  return "Hello from the server";
}

When you genuinely need interactivity, push "use client" as far down the tree as possible:

Avoid

app/page.tsx
"use client"; // now the whole page ships to the browser
export default function Page() {
  /* ... */
}

Prefer

components/like-button.tsx
"use client"; // only this button is client code
export function LikeButton() {
  /* ... */
}