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.
Search across all documentation pages
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.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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 devWhen 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.
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.mdThe 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;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.
package.json with "dev": "next dev --turbopack".npm run dev -- -p 4000 or set PORT=4000 npm run dev.npx create-next-app@latest ./apps/web ....--disable-git to the command.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 create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.yarn create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.bun create next-app my-app --typescript --app --no-tailwind --no-eslint --no-src-dir.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.
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.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.AGENTS.md - flags that did not exist in Next 13 or 14. Always pin to @latest or an exact version in your docs.node -v before npm run dev; use nvm to switch if you are on an older LTS.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.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".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.| Tool | When to use it | Trade-off vs Next.js 15 |
|---|---|---|
| Vite + React Router | SPAs, dashboards behind auth, no SEO needs | No Server Components, no built-in SSR, no file-system routing |
| Remix | Form-heavy apps, nested routing, progressive enhancement | Smaller ecosystem; different data loading model (loader/action) |
| Astro | Content sites, marketing pages, blogs | React is a guest; no full React 19 Server Components story |
| Next.js Pages Router | Legacy codebases, libraries still tied to getServerSideProps | Being superseded by App Router; no Server Components |
Quick review of this page - click to expand.
--app. Without it, create-next-app may default to App Router in Next 15 but the flag makes the choice explicit and non-interactive.
Pass --no-tailwind --no-eslint to create-next-app. Both flags are required because each defaults to on in Next 15.
It writes a paths entry in tsconfig.json so @/lib/x resolves to ./lib/x (or ./src/lib/x if you used --src-dir).
Everything in app/ is a Server Component unless the file starts with "use client". That is why you can await inside the component body.
No. Turbopack is the default dev bundler. The flag is only useful if you want to pin it in package.json for clarity.
18.18.0. Anything older fails at startup. Gotcha: several popular CI images still ship 18.17 by default.
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.
No. It is regenerated on every build and any edits will be lost. Leave it committed and untouched.
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.
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.
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.
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.
Reviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥