Architecture Overview
Server Runtime
Rendering Pipeline
Client Navigation
Caching and Export
Development Tools
Build and Configuration
Ecosystem Packages
Testing Infrastructure
How It Works
The following files were used as context for generating this wiki page:
Create Next App is the official command-line bootstrapping tool designed to set up new Next.js applications quickly and reliably. It automates the entire project initialization lifecycle, eliminating manual configuration overhead by providing interactive prompts, resolving package dependencies, and scaffolding complete directory structures tailored for either the App Router or Pages Router.
Sources: packages/create-next-app/package.json:9-9, packages/create-next-app/index.ts:41-123, packages/create-next-app/create-app.ts:28-264
The create-next-app executable initializes as a Node.js CLI tool via #!/usr/bin/env node, parsing arguments with commander and managing persistent user preferences through conf. The CLI entry point configures the command-line interface, handles terminal signals (SIGINT and SIGTERM), and detects the executing package manager from environment variables or explicit flags.
The CLI parses positional directory arguments and options using commander. When options are passed, they determine project behavior such as template selection, package manager overrides, and installation flags.
The CLI determines which package manager invoked the process by inspecting process.env.npm_config_user_agent. The resolution follows a strict evaluation order across explicit flags and environment strings.
Sources: packages/create-next-app/index.ts:128-137, packages/create-next-app/helpers/get-pkg-manager.ts:5-21
The package manager call-chain executes as follows: getPkgManager() inspects process.env.npm_config_user_agent, checking string prefixes for yarn, pnpm, or bun, and falls back to npm. When version inspection is required, getPackageManagerVersion(packageManager) checks userAgentMatch via regex new RegExp(${packageManager}/([\d.]+[\w.-]*)), falling back to spawning execSync(${packageManager} --version). Finally, getPnpmMajorVersion() calls getPackageManagerVersion('pnpm'), splits the version string by periods, and parses the major integer via parseInt().
Caution
If a user aborts an interactive prompt (state.aborted), the CLI explicitly writes the terminal cursor restoration escape sequence \x1B[?25h to process.stdout before exiting with status code 1. Omitting this restoration step leaves the user's terminal cursor permanently hidden.
When interactive prompts run, onPromptState catches user abort events. If --reset-preferences is supplied, Conf clears saved settings and exits. Otherwise, default preferences are initialized with TypeScript enabled (typescript: true), linter disabled (eslint: false, linter: 'eslint'), Tailwind CSS enabled (tailwind: true), App Router enabled (app: true), src/ directory disabled (srcDir: false), and import alias set to @/*.
Once user inputs, CLI flags, and package manager selections are validated, createApp orchestrates the target project creation flow. This includes verifying directory permissions, creating target folders, downloading or scaffolding templates, executing package installations, generating agent configuration files, initializing git repositories, and handling execution errors.
The project creation flow follows a strict execution path through its core orchestration functions:
createApp() → isWriteable() → mkdirSync() → isFolderEmpty() → process.chdir() → install() → generateAgentFiles() → tryGitInit()
createApp(): Receives resolved configuration options including appPath, packageManager, template choices, and flags.
Sources: packages/create-next-app/create-app.ts:28-66isWriteable(): Evaluates dirname(root) to verify whether the parent directory has write permissions before attempting file creation.
Sources: packages/create-next-app/create-app.ts:134-144mkdirSync(): Creates the destination folder using resolve(appPath) with { recursive: true }.
Sources: packages/create-next-app/create-app.ts:134-148isFolderEmpty(): Inspects the target directory to confirm it contains no conflicting files; exits process code 1 if non-empty.
Sources: packages/create-next-app/create-app.ts:149-151process.chdir(): Changes the current working directory to root.
Sources: packages/create-next-app/create-app.ts:160-160install(): Spawns the package manager process via cross-spawn when installing dependencies or examples.
Sources: packages/create-next-app/create-app.ts:223-227, packages/create-next-app/helpers/install.ts:11-49generateAgentFiles(): Conditionally runs when agentsMd is enabled to emit agent guidance files.
Sources: packages/create-next-app/create-app.ts:261-263tryGitInit(): Initializes a git repository in root unless disableGit is explicitly set.
Sources: packages/create-next-app/create-app.ts:265-271Dependency installation is managed by install(), which handles offline detection and passes explicit environment variables to the package manager spawn process.
export async function install(
packageManager: PackageManager,
isOnline: boolean
): Promise<void> {
const args: string[] = ['install']
if (!isOnline) {
console.log(
yellow('You appear to be offline.\nFalling back to the local cache.')
)
args.push('--offline')
}
return new Promise((resolve, reject) => {
const child = spawn(packageManager, args, {
stdio: 'inherit',
env: {
...process.env,
ADBLOCK: '1',
NODE_ENV: 'development',
DISABLE_OPENCOLLECTIVE: '1',
},
})
child.on('close', (code) => {
if (code !== 0) {
reject({ command: `${packageManager} ${args.join(' ')}` })
return
}
resolve()
})
})
}Warning
When packageManager is set to yarn and the host environment is offline, createApp forces isOnline to false via getOnline(), which appends the --offline flag to installation arguments and instructs Yarn to fall back to its local cache.
Sources: packages/create-next-app/create-app.ts:153-154, packages/create-next-app/helpers/install.ts:17-23
Sources: packages/create-next-app/create-app.ts:136-144, packages/create-next-app/create-app.ts:229-236, packages/create-next-app/helpers/install.ts:33-40
The installTemplate function handles copying internal template files into the target project root directory, configuring file exclusions based on user selections for ESLint, Biome, and Tailwind CSS.
Sources: packages/create-next-app/templates/index.ts:48-95
const templatePath = path.join(__dirname, template, mode);
const copySource = ["**"];
if (!eslint) copySource.push("!eslint.config.mjs");
if (!biome) copySource.push("!biome.json");
if (!tailwind) copySource.push("!postcss.config.mjs");
await copy(copySource, root, {
parents: true,
cwd: templatePath,
rename(name) {
switch (name) {
case "gitignore": {
return `.${name}`;
}
case "README-template.md": {
return "README.md";
}
default: {
return name;
}
}
},
});Note
README-template.md is renamed to README.md during the copy operation to bypass limitations with webpack-asset-relocator-loader used by ncc.
Sources: packages/create-next-app/templates/index.ts:85-89
Package managers are identified through the npm_config_user_agent environment variable or by invoking --version via execSync.
Sources: packages/create-next-app/helpers/get-pkg-manager.ts:1-54
During test runs, local workspace tarball paths supplied via NEXT_TEST_PKG_PATHS override standard package versions to ensure sibling packages install correctly.
Sources: packages/create-next-app/templates/index.ts:213-234
The install function spawns the chosen package manager with explicit environment overrides to ensure reliable execution across different user environments.
Sources: packages/create-next-app/helpers/install.ts:11-49
export async function install(
packageManager: PackageManager,
isOnline: boolean
): Promise<void> {
const args: string[] = ['install']
if (!isOnline) {
console.log(
yellow('You appear to be offline.\nFalling back to the local cache.')
)
args.push('--offline')
}
return new Promise((resolve, reject) => {
const child = spawn(packageManager, args, {
stdio: 'inherit',
env: {
...process.env,
ADBLOCK: '1',
NODE_ENV: 'development',
DISABLE_OPENCOLLECTIVE: '1',
},
})
child.on('close', (code) => {
if (code !== 0) {
reject({ command: `${packageManager} ${args.join(' ')}` })
return
}
resolve()
})
})
}Templates configured for the App Router utilize a structured setup across TypeScript and JavaScript variants. The configuration files establish the typing foundation for Next.js projects using TypeScript definitions while leaving inline placeholders for user-defined options. Sources: packages/create-next-app/templates/app-tw-empty/ts/next.config.ts:1-8, packages/create-next-app/templates/app-tw/ts/next.config.ts:1-8, packages/create-next-app/templates/app-empty/ts/next.config.ts:1-8
The base TypeScript configuration file imported across empty and Tailwind-enabled App Router templates defines a strictly typed NextConfig object exported as the default module.
Sources: packages/create-next-app/templates/app-tw-empty/ts/next.config.ts:1-8, packages/create-next-app/templates/app-tw/ts/next.config.ts:1-8, packages/create-next-app/templates/app-empty/ts/next.config.ts:1-8
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;Tailwind-enabled templates include pre-styled page components under the App Router structure. The default component renders a flexible container layout incorporating responsive sizing, font styling, dark mode support via dark: modifiers, and external links to Vercel templates, learning resources, and documentation.
Sources: packages/create-next-app/templates/app-tw/js/app/page.js:1-66
import Image from "next/image";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={
Pages Router templates provided by create-next-app structure initial project files under the pages/ directory. These templates incorporate Google Fonts via next/font/google, CSS modules or Tailwind CSS, and metadata control using next/head.
Sources: packages/create-next-app/templates/default/js/pages/index.js:1-24, packages/create-next-app/templates/default-tw/js/pages/index.js:1-12
Pages Router starter files instantiate font loaders from next/font/google for variable typography. Both standard and Tailwind variants configure Geist and Geist_Mono with Latin subsets and CSS variable mappings.
Sources: packages/create-next-app/templates/default/js/pages/index.js:3-14, packages/create-next-app/templates/default-tw/js/pages/index.js:2-12, packages/create-next-app/templates/default-tw/ts/pages/index.tsx:2-12
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});Note
Font variables defined through next/font/google are injected directly into root container class names alongside module styles or utility classes.
Sources: packages/create-next-app/templates/default/js/pages/index.js:25-27
Standard Pages Router templates import Head from next/head inside pages/index.js to render document-level metadata including page title, meta description, viewport settings, and favicon links.
Sources: packages/create-next-app/templates/default/js/pages/index.js:1-24
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>The Pages Router templates provide different styling integrations across JavaScript and TypeScript flavors.
Sources: packages/create-next-app/templates/default-tw/js/pages/index.js:1-79, packages/create-next-app/templates/default/js/pages/index.js:1-88, packages/create-next-app/templates/default-tw/ts/pages/index.tsx:1-79
create-next-app handles remote examples by inspecting GitHub URLs, validating repository existence via GitHub REST endpoints, fetching gzipped tarballs from codeload.github.com, and piping them through the tar extractor with path filtering.
Sources: packages/create-next-app/helpers/examples.ts:1-148
When an example argument is passed, createApp parses it as a URL object. If valid, it verifies that the origin equals https://github.com. The helper function getRepoInfo splits the pathname into path segments ([, username, name, t, _branch, ...file]) to extract repository metadata. If t (such as tree) is missing or empty, it queries https://api.github.com/repos/${username}/${name} to resolve the repository's default branch.
Sources: packages/create-next-app/create-app.ts:71-96, packages/create-next-app/helpers/examples.ts:23-62
Repository presence is validated by hasRepo, which performs an HTTP HEAD request via isUrlOk to check if package.json exists in the target repository contents URL at the specified branch reference. If the input is not a URL, existsInRepo checks the Vercel Next.js repository examples folder directly.
Sources: packages/create-next-app/helpers/examples.ts:14-87
Once repository info is verified, downloadAndExtractRepo or downloadAndExtractExample fetches the tarball stream and extracts it into the target directory.
Sources: packages/create-next-app/helpers/examples.ts:99-147
The download call chain executes through the following steps:
downloadAndExtractRepo() → downloadTarStream() → fetch() → Readable.fromWeb() → pipeline() → x() (tar extractor).
Sources: packages/create-next-app/helpers/examples.ts:89-130
Note
downloadTarStream validates that res.body is present before wrapping the Web API ReadableStream into a Node.js Readable stream using Readable.fromWeb().
Sources: packages/create-next-app/helpers/examples.ts:89-97
The tar extraction filter normalizes Windows path separators to POSIX style (posix.sep) and dynamically determines the unpacked root directory from the first path segment. This prevents failures if a repository has been renamed on GitHub while an old name was used in the URL.
Sources: packages/create-next-app/helpers/examples.ts:111-128
export async function downloadAndExtractRepo(
root: string,
{ username, name, branch, filePath }: RepoInfo
) {
let rootPath: string | null = null
await pipeline(
await downloadTarStream(
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`
),
x({
cwd: root,
strip: filePath ? filePath.split('/').length + 1 : 1,
filter: (p: string) => {
const posixPath = p.split(sep).join(posix.sep)
if (rootPath === null) {
const pathSegments = posixPath.split(posix.sep)
rootPath = pathSegments.length ? pathSegments[0] : null
}
return posixPath.startsWith(
`${rootPath}${filePath ? `/${filePath}/` : '/'}`
)
},
})
)
}