Notes on the File Structure of an Auto-Generated Next.js Project

EN|ZH

  • Description: A detailed walkthrough of every file in a typical Next.js project — what each file does, why it exists, and how they all connect.
  • My Notion Note ID: K2A-F3-2
  • Created: 2025-02-13
  • Updated: 2025-02-13
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. File Structure Overview

Assume we want to create a simple personal website. Here's what a typical simple Next.js project looks like:

my-nextjs-site/
├── app/                        # App Router directory (pages & layouts)
│   ├── layout.tsx              # Root layout (wraps all pages)
│   ├── page.tsx                # Home page (/)
│   ├── globals.css             # Global styles
│   ├── about/
│   │   └── page.tsx            # About page (/about)
│   ├── blog/
│   │   ├── page.tsx            # Blog index (/blog)
│   │   └── [slug]/
│   │       └── page.tsx        # Individual blog post (/blog/post-name)
│   └── favicon.ico             # Site favicon
├── components/                 # Reusable UI components
│   ├── Nav.tsx
│   └── Footer.tsx
├── content/                    # Markdown content files
│   ├── blog/
│   └── notes/
├── lib/                        # Utility functions & helpers
│   └── markdown.ts
├── public/                     # Static assets (images, fonts, etc.)
│   └── images/
├── package.json                # Node.js dependencies & scripts
├── tsconfig.json               # TypeScript configuration
├── next.config.ts              # Next.js configuration
├── postcss.config.mjs          # PostCSS configuration (for Tailwind)
├── eslint.config.mjs           # ESLint configuration
└── .gitignore                  # Git ignore rules

2. Infrastructure & Config Files

These files configure the toolchain.


2.1 package.json

What: The manifest for the Node.js project. It declares the project name, scripts you can run, and every dependency (library) the project needs.

Why: Without package.json, npm install / pnpm install would not know what to download, and build scripts would not exist. Required by every Node.js project.

Example:

{
  "name": "my-nextjs-site",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "gray-matter": "^4.0.3",
    "next": "16.1.6",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "rehype-highlight": "^7.0.2",
    "remark": "^15.0.1",
    "remark-gfm": "^4.0.1",
    "remark-html": "^16.0.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

Key concepts:

  • Scripts: Commands you run with npm run <script-name> or pnpm <script-name>:
    • dev – starts the development server with hot reloading
    • build – compiles the app for production (what deployment runs)
    • start – serves the production build locally
    • lint – runs ESLint to check code quality
  • Dependencies (runtime, shipped in final app):
    • next, react, react-dom – The core framework
    • gray-matter – Parses YAML frontmatter from markdown files
    • remark / remark-gfm / remark-html – Markdown processing pipeline
    • rehype-highlight – Syntax highlighting for code blocks
  • DevDependencies (development/build only):
    • tailwindcss and @tailwindcss/postcss – Tailwind CSS framework
    • @types/* – TypeScript type definitions
    • eslint and eslint-config-next – Code linting
  • Version prefixes:
    • ^4.0.3 – "compatible with 4.0.3" (allows minor/patch updates but not major)
    • 16.1.6 – "exactly this version"

Connections: Every other file depends on packages listed here. npm install / pnpm install reads this file and downloads everything into node_modules/.


2.2 tsconfig.json

What: TypeScript compiler configuration. Tells TypeScript how to check and compile .ts and .tsx files.

Why: Without this file, TypeScript would use default settings that wouldn't work with Next.js (wrong module system, no JSX support, no path aliases).

Example:

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [{"name": "next"}],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Key settings explained:

  • target: "ES2017" – Compile down to ES2017 JavaScript (supports async/await, etc.)
  • lib: ["dom", "dom.iterable", "esnext"] – Include browser API types (document, window) and modern JavaScript features
  • strict: true – Enable all strict type-checking options (catches more bugs)
  • noEmit: true – TypeScript only checks types, doesn't output .js files (Next.js uses its own compiler, SWC)
  • jsx: "react-jsx" – Use modern JSX transform (no need for import React in every file)
  • paths: {"@/*": ["./*"]}Path alias that lets you write import Nav from "@/components/Nav" instead of import Nav from "../../components/Nav". The @/ prefix maps to the project root.

Key concepts:

  • TypeScript checks types but doesn't run your code. The actual JavaScript compilation is done by Next.js's SWC compiler.
  • The @/* path alias is a project convention (not a TypeScript standard). It must be configured here and Next.js reads it automatically.

Connections: Every .ts and .tsx file is governed by this config. The path alias @/* is used throughout the project to avoid relative path hell (../../../).


2.3 next.config.ts

What: Next.js framework configuration. This is where you customize Next.js behavior (redirects, rewrites, image optimization, experimental features, etc.).

Why: Next.js looks for this file at startup. Even if empty, it serves as the designated place for future configuration.

Example:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  // Example options:
  // images: { domains: ['example.com'] },
  // redirects: async () => [{ source: '/old', destination: '/new', permanent: true }],
};

export default nextConfig;

Key concepts:

  • The .ts extension means this config file is TypeScript (Next.js 16 supports this natively)
  • Common configurations: image optimization domains, URL redirects, custom headers, environment variables, experimental features

Connections: Read by Next.js at startup. Affects every page and API route.


2.4 postcss.config.mjs

What: Configuration for PostCSS, a CSS processing tool. In most Next.js projects, its only job is to wire up Tailwind CSS.

Why: Tailwind CSS v4 works as a PostCSS plugin. PostCSS runs during the build, reads your CSS files, and transforms them (expanding Tailwind utility classes into real CSS).

Example:

const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

export default config;

Key concepts:

  • PostCSS is a tool for transforming CSS with JavaScript plugins (like Babel for CSS)
  • In Tailwind v4, the config moved from tailwind.config.js to CSS (@theme blocks)
  • The .mjs extension means "ES module JavaScript" (allows export default syntax)

Connections: PostCSS processes app/globals.css (which contains @import "tailwindcss"). Every Tailwind utility class in .tsx files is resolved through this pipeline.


2.5 eslint.config.mjs

What: ESLint (JavaScript/TypeScript linter) configuration. Catches common mistakes like unused variables, missing key props, accessibility issues.

Why: Ensures code quality and catches bugs before they reach production.

Example:

import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
  ...nextVitals,    // Performance and Next.js best practices
  ...nextTs,        // TypeScript-specific rules
  globalIgnores([
    ".next/**",
    "out/**",
    "build/**",
    "next-env.d.ts",
  ]),
]);

export default eslintConfig;

Key concepts:

  • ESLint 9 uses "flat config" format (arrays of config objects)
  • Next.js provides preset rule sets: core-web-vitals (performance) and typescript (type safety)
  • Linting is separate from type-checking (TypeScript) and formatting (Prettier)

Connections: Run via npm run lint / pnpm lint. Development tool only, doesn't affect build or runtime.


2.6 .gitignore

What: Everyone who is familiar with git should know this .gitignore. It tells Git which files and directories to exclude from version control.

Why: Prevents committing generated files, dependencies, secrets, and OS junk that bloat the repository or leak sensitive information.

What gets ignored (categories):

  • Dependenciesnode_modules/ (often hundreds of megabytes, regenerated by npm install)
  • Build output.next/, out/, build/ (generated during build, not source code)
  • Environment files.env* (API keys, database URLs, secrets)
  • OS files.DS_Store (macOS), Thumbs.db (Windows)
  • Debug logsnpm-debug.log*, yarn-debug.log*, .pnpm-debug.log*
  • TypeScript cache.tsbuildinfo, next-env.d.ts (auto-generated)
  • Deployment config.vercel/ (Vercel CLI local config)

Key syntax:

  • Leading / means "only at repository root"
  • is a wildcard, ! negates a previous ignore pattern

3. Global Styles

3.1 app/globals.css

What: The global stylesheet for the entire application. Imports Tailwind CSS, defines the color system, and provides typography styles.

Why: Every web app needs a base stylesheet. This file:

  • Activates Tailwind CSS
  • Defines custom colors (light and dark mode)
  • Registers colors as Tailwind utilities
  • Styles rendered markdown content (.prose class)

Core structure:

/* Import Tailwind CSS framework */
@import "tailwindcss";

/* Light mode colors */
:root {
  --background: #fafafa;
  --foreground: #171717;
  --muted: #737373;
  --border: #e5e5e5;
  --accent: #171717;
}

/* Register colors as Tailwind utilities */
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-muted: var(--muted);
  --color-border: var(--border);
  --color-accent: var(--accent);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

/* Dark mode override */
@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
    --muted: #a3a3a3;
    --border: #262626;
    --accent: #ededed;
  }
}

/* Base body styles */
body {
  background: var(--background);
  color: var(--foreground);
  font-family: var(--font-sans), system-ui, sans-serif;
}

/* Global link reset */
a {
  color: inherit;
  text-decoration: none;
}

/* Prose styles for markdown content */
.prose h1, .prose h2, .prose h3 { ... }
.prose p { margin-bottom: 1.25em; line-height: 1.75; }
.prose code { font-family: var(--font-mono), monospace; ... }
.prose pre { background: #1e1e1e; color: #d4d4d4; ... }

Key concepts:

  • CSS Custom Properties (variables): Defined with -name: value, used with var(--name). They cascade and can be overridden (like in dark mode).
  • @theme in Tailwind v4: Registers custom theme values directly in CSS (replaces the old tailwind.config.js approach). After registration, you can use bg-background, text-foreground, text-muted, border-border in your JSX.
  • prefers-color-scheme: CSS media query that detects the user's OS dark/light mode preference. When dark mode is active, the CSS variables switch automatically.
  • .prose pattern: A common convention for styling rendered HTML content where you can't add utility classes to individual elements. Wrap markdown output in <div class="prose"> and headings, paragraphs, code blocks, lists all get styled automatically.

Connections:

  • Imported by app/layout.tsx via import "./globals.css"
  • Processed by PostCSS with the Tailwind plugin (configured in postcss.config.mjs)
  • The -font-* variables are set by the font loader in layout.tsx

4. Root Layout

4.1 app/layout.tsx

What: The root layout for the entire application. In Next.js App Router, app/layout.tsx wraps every page. It defines the <html> and <body> tags, loads fonts, sets metadata, and includes UI elements that appear on every page.

Why: Next.js App Router requires a root layout at app/layout.tsx. Without it, Next.js cannot render any page. This is the most important file for understanding Next.js architecture.

Example:

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import Nav from "@/components/Nav";
import Footer from "@/components/Footer";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: {
    default: "My Site",
    template: "%s — My Site",
  },
  description: "Description placeholder",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable}${geistMono.variable} antialiased`}
      >
        <div className="flex min-h-screen flex-col">
          <Nav />
          <main className="flex-1">
            <div className="mx-auto max-w-3xl px-6 py-12">
              {children}
            </div>
          </main>
          <Footer />
        </div>
      </body>
    </html>
  );
}

Font loading:

next/font downloads fonts at build time and self-hosts them. Benefits:

  • Privacy – No requests to Google's servers
  • Performance – No extra DNS lookup
  • No layout shift – Automatic font loading optimization

The variable option creates a CSS variable instead of applying the font directly.

Metadata:

Next.js Metadata API replaces manual <title> and <meta> tags:

  • title.default – Used when no child page overrides it (home page)
  • title.template – When a child page sets title: "About", the %s is replaced, producing "About — My Site"
  • description – Meta description tag (shown in search results)

Layout structure:

  • antialiased – Tailwind utility for smooth text rendering
  • flex min-h-screen flex-col – Flexbox column layout, full viewport height (classic "sticky footer" pattern)
  • flex-1 on <main> – Expands to fill remaining space (pushes footer to bottom)
  • mx-auto max-w-3xl px-6 py-12 – Centered content column, max 768px wide, with padding
  • {children} – Where page content renders. Visit /about and children becomes app/about/page.tsx output

Key concepts:

  • Layouts wrap pages and persist across navigation (don't re-render when navigating)
  • Server Components: Runs on the server by default (no "use client" directive)
  • children prop: Next.js automatically passes the matched page as children

5. Shared Components

These are reusable UI components used across multiple pages. They live in components/ (outside app/) because they're not route-specific.


5.1 components/Nav.tsx

What: The top navigation bar displayed on every page.

Why: A navigation bar is still useful (at least I think so)

Example:

import Link from "next/link";

const navLinks = [
  { href: "/about", label: "About" },
  { href: "/blog", label: "Blog" },
  { href: "/projects", label: "Projects" },
];

export default function Nav() {
  return (
    <nav className="border-b border-border">
      <div className="mx-auto flex max-w-3xl items-center justify-between px-6 py-4">
        <Link
          href="/"
          className="font-mono text-sm font-medium tracking-tight"
        >
          My Site
        </Link>
        <div className="flex gap-6">
          {navLinks.map((link) => (
            <Link
              key={link.href}
              href={link.href}
              className="text-sm text-muted transition-colors hover:text-foreground"
            >
              {link.label}
            </Link>
          ))}
        </div>
      </div>
    </nav>
  );
}

Key points:

  • Always use <Link> from next/link for internal navigation (not <a>): enables client-side navigation (no full page reload) and automatic prefetching
  • Data-driven rendering: Array of link objects + .map() (cleaner than hardcoding)
  • Server Component: No interactivity needed, renders on the server
  • Layout alignment: max-w-3xl and px-6 match the layout's content width
  • Hover effects: transition-colors hover:text-foreground creates smooth color change

Connections: Imported by app/layout.tsx


5.2 components/Footer.tsx

What: Site footer displayed at the bottom of every page.

Example:

export default function Footer() {
  return (
    <footer className="border-t border-border">
      <div className="mx-auto max-w-3xl px-6 py-8">
        <p className="text-sm text-muted">
          &copy; {new Date().getFullYear()} Your Name
        </p>
      </div>
    </footer>
  );
}

Key points:

  • Semantic HTML: <footer> is meaningful to screen readers and search engines
  • Dynamic year: new Date().getFullYear() runs at build time (Server Component)
  • Consistent widths: mx-auto max-w-3xl px-6 matches nav and main content

Connections: Imported by app/layout.tsx


6. Pages

In Next.js App Router, every page.tsx file inside app/ becomes a route. The file path maps directly to the URL.


6.1 app/page.tsx (Home Page)

What: The home page, rendered at /.

Example:

import Link from "next/link";

const sections = [
  {
    title: "Blog",
    href: "/blog",
    description: "Some user-given description",
  },
  {
    title: "Projects",
    href: "/projects",
    description: "Some user-given description",
  },
];

export default function Home() {
  return (
    <div className="space-y-16">
      {/* Intro section */}
      <section>
        <h1 className="text-2xl font-semibold tracking-tight">Name</h1>
        <p className="mt-4 leading-7 text-muted">
          Some user-given description
        </p>
        <Link
          href="/about"
          className="mt-3 inline-block text-sm text-muted transition-colors hover:text-foreground"
        >
          More about me &rarr;
        </Link>
      </section>

      {/* Section links */}
      {sections.map((section) => (
        <section key={section.href}>
          <div className="flex items-baseline justify-between">
            <h2 className="font-mono text-xs font-medium uppercase tracking-widest text-muted">
              {section.title}
            </h2>
            <Link
              href={section.href}
              className="text-sm text-muted transition-colors hover:text-foreground"
            >
              View all &rarr;
            </Link>
          </div>
          <div className="mt-3 border-t border-border pt-4">
            <p className="text-sm text-muted">{section.description}</p>
          </div>
        </section>
      ))}
    </div>
  );
}

Key concepts:

  • File-based routing: This file at app/page.tsx makes it the home page (/)
  • No metadata export: Uses the default title from root layout
  • Typography hierarchy: Different levels use different styles
  • space-y-16: Tailwind spacing utility for consistent vertical rhythm (64px between sections)

6.2 app/about/page.tsx

What: The About page, rendered at /about.

Example:

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "About",
};

export default function AboutPage() {
  return (
    <div className="space-y-8">
      <h1 className="text-2xl font-semibold tracking-tight">About</h1>
      <div className="space-y-4 leading-7 text-muted">
        <p>Hi, I'm Name.</p>
        <p>
          Description placeholder
        </p>
      </div>
    </div>
  );
}

Key concepts:

  • Page-level metadata: Each page can export its own metadata object, merged with parent layout metadata.
  • Title template: Root layout defines title: { template: "%s — My Site" }. This page sets title: "About", resulting in "About — My Site" in the browser tab.

6.3 Nested Routes

Next.js uses the file system for routing. Dynamic and nested routes use brackets and folders:

File Path URL Description
app/blog/page.tsx /blog Blog index page
app/blog/[slug]/page.tsx /blog/post-name Individual blog post (dynamic route)
app/blog/[...slug]/page.tsx /blog/2024/jan/post Catch-all route (matches any depth)
app/docs/[[...slug]]/page.tsx /docs or /docs/a/b/c Optional catch-all (includes the base path)

Dynamic route example (app/blog/[slug]/page.tsx):

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  // Fetch post data using slug
  return <article>Content for {slug}</article>;
}

Key concepts:

  • [slug] in folder name creates a dynamic route
  • The params prop contains route parameters
  • In Next.js 15+, params is a Promise and must be awaited
  • [...slug] catches all segments (returns an array like ['2024', 'jan', 'post'])

7. How Everything Connects

There are a lot of files and I got confused at first. Here's a visual summary of how files relate:

Browser requests /about
        |
        v
Next.js matches route: app/about/page.tsx
        |
        v
Next.js wraps it in: app/layout.tsx (root layout)
        |
        v
layout.tsx renders:
  <html>
    <body class="[font classes] antialiased">
      <div class="flex min-h-screen flex-col">
        <Nav />                          <-- components/Nav.tsx
        <main class="flex-1">
          <div class="mx-auto max-w-3xl px-6 py-12">
            {children}                   <-- app/about/page.tsx output
          </div>
        </main>
        <Footer />                       <-- components/Footer.tsx
      </div>
    </body>
  </html>
        |
        v
globals.css provides:                   <-- app/globals.css
  - Tailwind utilities (via @import "tailwindcss")
  - Color variables (via :root and @theme)
  - Font references (via @theme --font-sans/--font-mono)
  - Prose styles for markdown content
        |
        v
PostCSS processes the CSS              <-- postcss.config.mjs
Tailwind generates utility classes      <-- @tailwindcss/postcss plugin
TypeScript checks types                 <-- tsconfig.json
Next.js serves the result               <-- next.config.ts

Metadata inheritance:

Root layout metadata:
  title: { default: "My Site", template: "%s — My Site" }

About page metadata:
  title: "About"

Result in browser tab: "About — My Site"

Import graph:

app/layout.tsx
  ├── imports: next (Metadata type)
  ├── imports: next/font/google (Geist, Geist_Mono)
  ├── imports: @/components/Nav.tsx
  │     └── imports: next/link (Link)
  ├── imports: @/components/Footer.tsx
  │     └── imports: nothing
  └── imports: ./globals.css
        └── processed by: @tailwindcss/postcss (via postcss.config.mjs)

app/page.tsx
  └── imports: next/link (Link)

app/about/page.tsx
  └── imports: next (Metadata type)

8. Summary

A typical simple Next.js project has:

  1. Config files (package.json, tsconfig.json, next.config.ts) that configure the toolchain
  2. Global styles (app/globals.css) that import Tailwind and define the design system
  3. Root layout (app/layout.tsx) that wraps all pages with Nav, Footer, fonts, and metadata
  4. Shared components (components/Nav.tsx, Footer.tsx) used across pages
  5. Page files (app/page.tsx, app/about/page.tsx) that define routes via the file system

Key mental models:

  • The file system is the routerapp/about/page.tsx automatically becomes /about
  • Layouts wrap pages and persist across navigation
  • Server Components by default (unless you add "use client")
  • Path aliases (@/* → project root) avoid relative path hell
  • Metadata API replaces manual <title> tag management
  • Tailwind v4 uses @theme in CSS instead of tailwind.config.js

This structure scales well for personal sites, blogs, and small web apps. As projects grow, you may add:

  • lib/ utilities for data fetching and helpers
  • content/ directories for markdown/MDX files
  • Dynamic routes ([slug]) for blog posts or documentation
  • API routes in app/api/ for backend functionality

9. References

The example code in this note is based on a project scaffolded with create-next-app (Next.js's official project generator), then customized with navigation, footer, placeholder pages, and a Tailwind CSS design system.

To generate a similar project yourself, you could just run:

npx create-next-app@latest my-nextjs-site

The scaffolder will prompt you to choose TypeScript, Tailwind CSS, App Router, etc. The resulting project structure closely matches what is described in this note.