Notes on the File Structure of an Auto-Generated Next.js Project
- 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
- 2. Infrastructure & Config Files
- 3. Global Styles
- 4. Root Layout
- 5. Shared Components
- 6. Pages
- 7. How Everything Connects
- 8. Summary
- 9. References
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>orpnpm <script-name>:dev– starts the development server with hot reloadingbuild– compiles the app for production (what deployment runs)start– serves the production build locallylint– runs ESLint to check code quality
- Dependencies (runtime, shipped in final app):
next,react,react-dom– The core frameworkgray-matter– Parses YAML frontmatter from markdown filesremark/remark-gfm/remark-html– Markdown processing pipelinerehype-highlight– Syntax highlighting for code blocks
- DevDependencies (development/build only):
tailwindcssand@tailwindcss/postcss– Tailwind CSS framework@types/*– TypeScript type definitionseslintandeslint-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 featuresstrict: true– Enable all strict type-checking options (catches more bugs)noEmit: true– TypeScript only checks types, doesn't output.jsfiles (Next.js uses its own compiler, SWC)jsx: "react-jsx"– Use modern JSX transform (no need forimport Reactin every file)paths: {"@/*": ["./*"]}– Path alias that lets you writeimport Nav from "@/components/Nav"instead ofimport 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
.tsextension 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.jsto CSS (@themeblocks) - The
.mjsextension means "ES module JavaScript" (allowsexport defaultsyntax)
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) andtypescript(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):
- Dependencies –
node_modules/(often hundreds of megabytes, regenerated bynpm 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 logs –
npm-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 (
.proseclass)
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 withvar(--name). They cascade and can be overridden (like in dark mode). @themein Tailwind v4: Registers custom theme values directly in CSS (replaces the oldtailwind.config.jsapproach). After registration, you can usebg-background,text-foreground,text-muted,border-borderin 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..prosepattern: 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.tsxviaimport "./globals.css" - Processed by PostCSS with the Tailwind plugin (configured in
postcss.config.mjs) - The
-font-*variables are set by the font loader inlayout.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 setstitle: "About", the%sis replaced, producing "About — My Site"description– Meta description tag (shown in search results)
Layout structure:
antialiased– Tailwind utility for smooth text renderingflex min-h-screen flex-col– Flexbox column layout, full viewport height (classic "sticky footer" pattern)flex-1on<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/aboutandchildrenbecomesapp/about/page.tsxoutput
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) childrenprop: Next.js automatically passes the matched page aschildren
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>fromnext/linkfor 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-3xlandpx-6match the layout's content width - Hover effects:
transition-colors hover:text-foregroundcreates 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">
© {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-6matches 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 →
</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 →
</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.tsxmakes it the home page (/) - No metadata export: Uses the
defaulttitle 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
metadataobject, merged with parent layout metadata. - Title template: Root layout defines
title: { template: "%s — My Site" }. This page setstitle: "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
paramsprop contains route parameters - In Next.js 15+,
paramsis 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:
- Config files (
package.json,tsconfig.json,next.config.ts) that configure the toolchain - Global styles (
app/globals.css) that import Tailwind and define the design system - Root layout (
app/layout.tsx) that wraps all pages with Nav, Footer, fonts, and metadata - Shared components (
components/Nav.tsx,Footer.tsx) used across pages - Page files (
app/page.tsx,app/about/page.tsx) that define routes via the file system
Key mental models:
- The file system is the router –
app/about/page.tsxautomatically 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
@themein CSS instead oftailwind.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 helperscontent/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.