React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

nextjssetupcreate-next-appapp-routertypescript

Basic Next.js Setup

The most minimal Next.js 15 setup — bare create-next-app with TypeScript and the App Router, nothing else. No Tailwind, no ESLint, no src/ directory. Just the essentials for a Server Component–first project on React 19.

Recipe

Quick-reference recipe card — copy-paste ready.

# Create the app with only the flags we want
npx create-next-app@latest my-app \
  --typescript \
  --app \
  --no-tailwind \
  --no-eslint \
  --no-src-dir \
  --import-alias "@/*"
 
# Enter the project and start the dev server
cd my-app
npm run dev

When to reach for this: Learning Next.js, writing a reproducible minimal bug repro, or starting a project where you plan to layer in your own tooling (Biome, Vanilla Extract, your own linter) rather than accept the defaults.

Working Example

After the recipe above finishes, you will have this directory layout:

my-app/
├── app/
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── public/
│   ├── next.svg
│   └── vercel.svg
├── next.config.ts
├── next-env.d.ts
├── package.json
├── tsconfig.json
└── README.md

The root layout is a Server Component and wraps every route:

// app/layout.tsx
import type { Metadata } from "next";
import "./globals.css";
 
export const metadata: Metadata = {
  title: "My App",
  description: "Minimal Next.js 15 + React 19 setup",
};
 
export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

A Server Component page that fetches data at request time — no useEffect, no client bundle cost:

// app/page.tsx
type Post = { id: number; title: string };
 
async function getPosts(): Promise<Post[]> {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5", {
    // Next 15 defaults to no automatic caching for fetch; opt in explicitly
    next: { revalidate: 60 },
  });
  if (!res.ok) throw new Error("Failed to fetch posts");
  return res.json();
}
 
export default async function HomePage() {
  const posts = await getPosts();
  return (
    <main>
      <h1>Latest posts</h1>
      <ul>
        {posts.map((p) => (
          <li key={p.id}>{p.title}</li>
        ))}
      </ul>
    </main>
  );
}

The generated tsconfig.json turns on strict mode and wires up the @/* alias:

// tsconfig.json (abridged)
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [\{ "name": "next" \}],
    "paths": \{ "@/*": ["./*"] \}
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

And the config file is now TypeScript by default:

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  // Add config here as you need it.
};
 
export default nextConfig;

Deep Dive

How It Works

create-next-app is a small scaffolder distributed as its own npm package. When you run npx create-next-app@latest, npx downloads the latest published version into a temporary cache, runs it, and passes the remaining arguments to it. The scaffolder copies a template directory from inside the package, runs your chosen package manager's install, and (unless --disable-git is set) runs git init with an initial commit.

Every flag in the recipe maps 1:1 to an answer to an interactive prompt, so --typescript --app --no-tailwind --no-eslint --no-src-dir is the same as answering those five prompts manually. Passing all five means create-next-app runs non-interactively and your setup is reproducible in CI.

Once installed, npm run dev starts the Next.js dev server. In Next 15, Turbopack is the default dev bundler — you do not need a separate flag.

Variations

  • Turbopack dev (explicit): it is on by default, but you can force it in package.json with "dev": "next dev --turbopack".
  • Custom port: npm run dev -- -p 4000 or set PORT=4000 npm run dev.
  • Custom directory: pass any path as the project name, e.g. npx create-next-app@latest ./apps/web ....
  • Without git init: add --disable-git to the command.
  • Manual setup: mkdir my-app && cd my-app && npm init -y && npm install next@latest react@latest react-dom@latest && npm install -D typescript @types/react @types/node, then create app/layout.tsx and app/page.tsx by hand; Next generates next-env.d.ts on first run.
  • pnpm: pnpm create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.
  • yarn: yarn create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.
  • bun: bun create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.

TypeScript Notes

The --typescript flag writes a tsconfig.json with strict: true, moduleResolution: "bundler", and the Next.js TypeScript plugin registered under compilerOptions.plugins. That plugin is what powers autocomplete for metadata, route params, and the generated route types in .next/types/.

The paths entry "@/*": ["./*"] is relative to the project root, not to src/, because you opted out of --src-dir. If you later move files into src/, you must update the alias to "@/*": ["./src/*"].

next-env.d.ts is auto-generated on every dev/build. Do not edit it; do not delete it. The standard .gitignore commits it.

Gotchas

  1. App Router vs Pages Router. The app/ directory is the App Router. If you also create a pages/ directory, both routers will run side by side and you will get collisions on shared routes. Delete pages/ unless you are intentionally migrating.
  2. src/ vs root app/ placement. With --no-src-dir, the App Router lives at ./app. If any tutorial tells you to put files in ./src/app, that is the --src-dir layout; mixing the two silently breaks imports.
  3. Defaults change between Next.js versions. In Next 15 the default prompts include Turbopack and AGENTS.md — flags that did not exist in Next 13 or 14. Always pin to @latest or an exact version in your docs.
  4. Node.js 18.18 or newer is required. Next 15 refuses to start on Node 18.17 and below. Run node -v before npm run dev; use nvm to switch if you are on an older LTS.
  5. Port 3000 already in use. npm run dev will bail with EADDRINUSE if another process holds 3000. Either kill it (lsof -iTCP:3000 -sTCP:LISTEN) or run with -p 3001.
  6. fetch is not cached by default in Next 15. If you expect the old "fetch is cached forever" behavior from Next 13/14, you must opt in explicitly with next: \{ revalidate: N \} or cache: "force-cache".
  7. next.config.ts requires a TypeScript-aware Node runtime. It works out of the box in Next 15, but some older tooling that shells out to next build from a plain Node script may complain — fall back to next.config.mjs if you hit this.

Alternatives

ToolWhen to use itTrade-off vs Next.js 15
Vite + React RouterSPAs, dashboards behind auth, no SEO needsNo Server Components, no built-in SSR, no file-system routing
RemixForm-heavy apps, nested routing, progressive enhancementSmaller ecosystem; different data loading model (loader/action)
AstroContent sites, marketing pages, blogsReact is a guest; no full React 19 Server Components story
Next.js Pages RouterLegacy codebases, libraries still tied to getServerSidePropsBeing superseded by App Router; no Server Components

FAQs

Quick review of this page — click to expand.

Which flag selects the App Router?

--app. Without it, create-next-app may default to App Router in Next 15 but the flag makes the choice explicit and non-interactive.

How do I opt out of Tailwind and ESLint at the same time?

Pass --no-tailwind --no-eslint to create-next-app. Both flags are required because each defaults to on in Next 15.

What does --import-alias "@/*" actually set?

It writes a paths entry in tsconfig.json so @/lib/x resolves to ./lib/x (or ./src/lib/x if you used --src-dir).

Why is my app/page.tsx a Server Component by default?

Everything in app/ is a Server Component unless the file starts with "use client". That is why you can await inside the component body.

Do I need a separate --turbopack flag in Next 15?

No. Turbopack is the default dev bundler. The flag is only useful if you want to pin it in package.json for clarity.

What is the minimum Node.js version?

18.18.0. Anything older fails at startup. Gotcha: several popular CI images still ship 18.17 by default.

What happens if port 3000 is busy?

next dev exits with EADDRINUSE. Run npm run dev -- -p 3001 or kill the process listening on 3000. Gotcha: some older Next versions auto-bumped the port; Next 15 does not.

Is next-env.d.ts safe to edit?

No. It is regenerated on every build and any edits will be lost. Leave it committed and untouched.

How do I enable strict TypeScript?

It is already on. create-next-app writes "strict": true in tsconfig.json by default when you pass --typescript. TS note: that also enables strictNullChecks, noImplicitAny, and friends.

Why does my fetch call hit the network every request?

Next 15 changed the default: fetch is no longer cached unless you opt in. Add next: \{ revalidate: 60 \} or cache: "force-cache" to the fetch options.

Can I type next.config.ts with full IntelliSense?

Yes. Import NextConfig from next and annotate the exported constant. TS note: NextConfig is the canonical type in Next 15; the older /** @type \{import('next').NextConfig\} */ JSDoc comment is no longer needed.

What is the difference between app/ and pages/?

app/ is the App Router (React 19, Server Components, layouts, streaming). pages/ is the legacy Pages Router. You can run both, but start new projects on app/ only.