Next.js 自动生成项目的文件结构笔记
- 简介: 详细解读一个典型 Next.js 项目中的每个文件——它的作用、存在的原因、以及各文件之间的关联。
- Notion 笔记 ID: K2A-F3-2
- 创建日期: 2025-02-13
- 更新日期: 2025-02-13
- 许可: 欢迎转载。 请注明作者 张宇 并附上原文链接 yuzhang.io
目录
1. 文件结构总览
假设我们要创建一个简单的个人网站。以下是一个典型的简单 Next.js 项目结构:
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. 基础设施与配置文件
这些文件配置了整个工具链。
2.1 package.json
是什么: Node.js 项目的清单文件。它声明了项目名称、可运行的脚本命令、以及项目所需的所有依赖(库)。
为什么需要: 如果没有 package.json,npm install / pnpm install 就不知道该下载什么,编译脚本也不会存在。这是每个 Node.js 项目都必需的文件。
示例:
{
"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"
}
}
核心概念:
- Scripts(脚本):
npm run <脚本名>或pnpm <脚本名>运行的命令:dev– 启动带热重载的开发服务器build– 编译生产版本(部署时运行的命令)start– 在本地启动生产版本lint– 运行 ESLint 检查代码质量
- Dependencies(运行时的依赖),包含在最终应用中:
next、react、react-dom– 核心框架gray-matter– 解析 markdown 文件中的 YAML frontmatterremark/remark-gfm/remark-html– Markdown 处理相关rehype-highlight– 代码块语法高亮
- DevDependencies(开发依赖),仅在开发和构建时使用:
tailwindcss和@tailwindcss/postcss– Tailwind CSS 框架@types/*– TypeScript 类型定义eslint和eslint-config-next– 代码检查
- 版本号前缀:
^4.0.3– "兼容 4.0.3"(允许小版本和补丁更新,但不允许大版本更新)16.1.6– "精确到这个版本"
关联: 所有其他文件都依赖于这里列出的包。npm install / pnpm install 会读取这个文件,并且将所有依赖下载到 node_modules/ 中。
2.2 tsconfig.json
是什么: TypeScript 编译器的配置。告诉 TypeScript 如何检查和编译 .ts 和 .tsx 文件。
为什么需要: 没有这个文件,TypeScript 会使用默认设置,而这些默认设置无法与 Next.js 配合工作(比如会有错误的模块系统、不支持 JSX、没有路径别名等)。
示例:
{
"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"]
}
关键设置说明:
target: "ES2017"– 设置为 ES2017 JavaScript(支持 async/await 等)lib: ["dom", "dom.iterable", "esnext"]– 包含浏览器 API 类型(document、window)和现代 JavaScript 特性strict: true– 启用所有严格类型检查选项(能捕获更多 bug)noEmit: true– TypeScript 只检查类型,不输出.js文件(Next.js 使用自己的编译器 SWC)jsx: "react-jsx"– 使用现代 JSX 转换(每个文件不再需要import React)paths: {"@/*": ["./*"]}– 路径别名,让我们可以写import Nav from "@/components/Nav"而不是import Nav from "../../components/Nav"。@/前缀映射到项目根目录。
核心概念:
- TypeScript 只检查类型,不运行代码。实际的 JavaScript 编译由 Next.js 的 SWC 编译器完成。
@/*路径别名是项目约定(不是 TypeScript 标准)。必须先在这里配置好,之后Next.js 会自动读取这个文件的。
关联: 每个 .ts 和 .tsx 文件都受此配置管辖。@/* 路径别名在整个项目中使用,避免了相对路径问题(../../../)。
2.3 next.config.ts
是什么: Next.js 框架配置。在这里自定义 Next.js 的行为(重定向、URL 重写、图片优化、实验性功能等)。
为什么需要: Next.js 启动时会查找此文件。即使内容为空,它也是未来配置的指定位置。
示例:
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;
核心概念:
.ts扩展名意味着这个配置文件是 TypeScript(Next.js 16 原生支持)- 常见配置:图片优化域名、URL 重定向、自定义请求头、环境变量、实验性功能
关联: Next.js 启动时读取。影响每个页面和 API 路由。
2.4 postcss.config.mjs
是什么: PostCSS 的配置文件,PostCSS 是一个 CSS 处理工具。在大多数 Next.js 项目中,它唯一的任务就是接入 Tailwind CSS。
为什么需要: Tailwind CSS v4 作为 PostCSS 插件工作。PostCSS 在构建过程中运行,读取 CSS 文件并进行转换(将 Tailwind 工具类展开为真正的 CSS)。
示例:
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
核心概念:
- PostCSS 是一个用 JavaScript 插件转换 CSS 的工具(类似 CSS 版的 Babel)
- 在 Tailwind v4 中,配置文件从
tailwind.config.js迁移到了 CSS 中(@theme块) .mjs扩展名表示"ES 模块 JavaScript"(允许使用export default语法)
关联: PostCSS 处理 app/globals.css(其中包含 @import "tailwindcss")。.tsx 文件中的每个 Tailwind 工具类都通过这个管线解析。
2.5 eslint.config.mjs
是什么: ESLint(JavaScript/TypeScript 代码检查工具)配置。可以捕获常见错误,如未使用的变量、缺失的 key 属性、无障碍访问问题等。
为什么需要: 为了确保代码质量,在上线之前发现 bug。
示例:
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;
核心概念:
- ESLint 9 使用"扁平配置"格式(配置对象数组)
- Next.js 提供预设规则集:
core-web-vitals(性能)和typescript(类型安全) - 代码检查中的类型检查(TypeScript)和格式检查(Prettier)是分开的
关联: 通过 npm run lint / pnpm lint 运行。它们仅是开发工具,不影响构建或运行时。
2.6 .gitignore
是什么: 熟悉 git 的人应该都知道这个 .gitignore。它告诉 Git 哪些文件和目录不纳入版本控制。
为什么需要: 防止提交生成的文件、依赖包、密钥和系统垃圾文件,避免代码库膨胀或泄露敏感信息。
被忽略的内容(分类):
- 依赖包 –
node_modules/(通常有数百 MB,由npm install重新生成) - 构建产物 –
.next/、out/、build/(构建时生成,不是源代码) - 环境文件 –
.env*(API 密钥、数据库 URL、密钥) - 系统文件 –
.DS_Store(macOS)、Thumbs.db(Windows) - 调试日志 –
npm-debug.log*、yarn-debug.log*、.pnpm-debug.log* - TypeScript 缓存 –
.tsbuildinfo、next-env.d.ts(自动生成) - 部署配置 –
.vercel/(Vercel CLI 本地配置)
关键语法:
- 开头的
/表示"仅限仓库根目录" - 是通配符,
!用于取消之前的忽略规则
3. 全局样式
3.1 app/globals.css
是什么: 整个应用的全局样式表。导入 Tailwind CSS,定义颜色体系,并提供排版样式。
为什么需要: 每个 Web 应用都需要一个基础样式表。这个文件:
- 激活 Tailwind CSS
- 定义自定义颜色(亮色和暗色模式)
- 将颜色注册为 Tailwind 工具类
- 为渲染的 markdown 内容设置样式(
.prose类)
核心结构:
/* 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; ... }
核心概念:
- CSS 自定义属性(变量): 用
--name: value定义,用var(--name)使用。它们可以级联并被覆盖(比如在暗色模式中)。 - Tailwind v4 中的
@theme: 直接在 CSS 中注册自定义主题值(取代了旧的tailwind.config.js方式)。这样我们就可以在 JSX 中使用bg-background、text-foreground、text-muted、border-border等工具类。 prefers-color-scheme: CSS 媒体查询,检测用户操作系统的深色/浅色模式偏好。当暗色模式激活时,CSS 变量可以自动切换。.prose模式: 一种常见约定,用于为渲染后的 HTML 内容设置样式(因为我们无法在单个元素上添加工具类)。如果将 markdown 输出包裹在<div class="prose">中,标题、段落、代码块、列表都会自动获得样式。
关联:
- 由
app/layout.tsx通过import "./globals.css"导入 - 由 PostCSS 和 Tailwind 插件处理(在
postcss.config.mjs中配置) --font-*变量由layout.tsx中的字体加载器设置
4. 根布局(Root Layout)
4.1 app/layout.tsx
是什么: 整个应用的根布局。在 Next.js App Router 中,app/layout.tsx 包裹每一个页面。它定义了 <html> 和 <body> 标签,加载字体,设置元数据,并包含出现在每个页面上的 UI 元素。
为什么需要: Next.js App Router 要求在 app/layout.tsx 放置一个根布局。没有根布局,Next.js 就无法渲染任何页面。这是理解 Next.js 架构最重要的一个文件。
示例:
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>
);
}
字体加载:
next/font 在构建时下载字体并自己托管。好处:
- 隐私 – 不会向 Google 的服务器发送请求
- 性能 – 不需要额外的 DNS 查询
- 无布局偏移 – 自动优化字体加载
variable 选项创建一个 CSS 变量,而不是直接应用字体。
元数据(Metadata):
Next.js Metadata API 取代了手动编写 <title> 和 <meta> 标签:
title.default– 当子页面没有覆盖时使用(首页)title.template– 当子页面设置title: "About"时,%s被替换,生成 "About — My Site"description– meta 描述标签(显示在搜索结果中)
布局结构:
antialiased– Tailwind 工具类,用于平滑的文本渲染flex min-h-screen flex-col– Flexbox 列布局,占满整个视口高度(经典的"粘性底部"模式)<main>上的flex-1– 扩展以填充剩余空间(将页脚推到底部)mx-auto max-w-3xl px-6 py-12– 居中的内容列,最大宽度 768px,带内边距{children}– 页面内容渲染的位置。访问/about时,children变成app/about/page.tsx的输出
核心概念:
- 布局不需要重建布局会在不同页面保持一致(导航时不会重新渲染)
- 默认在服务器端运行: 默认在服务器端运行(没有
"use client"指令) childrenprop: Next.js 自动将匹配的页面作为children传入
5. 共享组件
这些是跨多个页面使用的可复用 UI 组件。可以把它们放在 components/(app/ 之外),因为它们不是用于特定某个路径的。
5.1 components/Nav.tsx
是什么: 显示在每个页面顶部的导航栏。
为什么需要: 导航栏还是挺有用的(至少我个人这么觉得)
示例:
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>
);
}
要点:
- 内部导航始终使用
next/link的<Link>(而非<a>):启用客户端导航(无需完整页面刷新)和自动预取 - 数据驱动渲染: 链接对象数组 +
.map()(比直接写死更简洁一些) - 默认在服务器端运行: 不需要交互性,然后在服务器端渲染
- 布局对齐:
max-w-3xl和px-6与布局的内容宽度匹配 - 悬停效果:
transition-colors hover:text-foreground创建平滑的颜色过渡
关联: 由 app/layout.tsx 导入
5.2 components/Footer.tsx
是什么: 显示在每个页面底部的网站页脚。
示例:
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>
);
}
要点:
- 语义化 HTML:
<footer>对屏幕阅读器和搜索引擎有意义 - 动态年份:
new Date().getFullYear()在构建时运行(Server Component) - 一致的宽度:
mx-auto max-w-3xl px-6与导航栏和主内容保持一致
关联: 由 app/layout.tsx 导入
6. 页面
在 Next.js App Router 中,app/ 内的每个 page.tsx 文件都成为一个路由。文件路径直接映射到 URL。
6.1 app/page.tsx(首页)
是什么: 首页,渲染在 /。
示例:
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>
);
}
核心概念:
- 基于文件的路由: 这个文件在
app/page.tsx,所以它就是首页(/) - 没有 metadata 导出: 使用根布局中的
default标题 - 排版层次: 不同层级使用不同的样式
space-y-16: Tailwind 间距工具类,用于一致的垂直节奏(各部分之间 64px)
6.2 app/about/page.tsx
是什么: 关于页面,渲染在 /about。
示例:
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>
);
}
核心概念:
- 页面级元数据: 每个页面可以导出自己的
metadata对象,与父布局的元数据合并。 - 标题模板: 根布局定义了
title: { template: "%s — My Site" }。这个页面设置title: "About",结果在浏览器标签中显示 "About — My Site"。
6.3 嵌套路由
Next.js 使用文件系统进行路由。动态路由和嵌套路由使用方括号和文件夹:
| 文件路径 | URL | 说明 |
|---|---|---|
app/blog/page.tsx |
/blog |
博客首页 |
app/blog/[slug]/page.tsx |
/blog/post-name |
单篇博客文章(动态路由) |
app/blog/[...slug]/page.tsx |
/blog/2024/jan/post |
捕获所有路由(匹配任意深度) |
app/docs/[[...slug]]/page.tsx |
/docs 或 /docs/a/b/c |
可选捕获所有路由(包含基础路径) |
动态路由示例 (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>;
}
核心概念:
- 文件夹名中的
[slug]创建动态路由 paramsprop 包含路由参数- 在 Next.js 15+ 中,
params是一个 Promise,必须 await [...slug]捕获所有路径段(返回数组,如['2024', 'jan', 'post'])
7. 各文件之间的关联
Next.js相关文件挺多的,刚开始我也挺一头雾水的。下面是一个各文件关系的可视化总结:
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
元数据继承:
Root layout metadata:
title: { default: "My Site", template: "%s — My Site" }
About page metadata:
title: "About"
Result in browser tab: "About — My Site"
导入关系图:
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. 总结
一个典型的简单 Next.js 项目包含:
- 配置文件(
package.json、tsconfig.json、next.config.ts)配置工具链 - 全局样式(
app/globals.css)导入 Tailwind 并定义设计系统 - 根布局(
app/layout.tsx)用 Nav、Footer、字体和元数据包裹所有页面 - 共享组件(
components/Nav.tsx、Footer.tsx)跨页面使用 - 页面文件(
app/page.tsx、app/about/page.tsx)通过文件系统定义路由
核心思维模型:
- 文件系统就是路由 –
app/about/page.tsx自动变成/about - 布局包裹页面并在导航间保持不变
- 默认是 Server Components(除非你添加
"use client") - 路径别名(
@/*→ 项目根目录)避免相对路径地狱 - Metadata API 取代了手动管理
<title>标签 - Tailwind v4 在 CSS 中使用
@theme而非tailwind.config.js
这种结构对个人网站、博客和小型 Web 应用来说其实有很好的扩展性。随着项目增长,可能会添加:
lib/工具函数用于数据获取和辅助功能content/目录用于 markdown/MDX 文件- 动态路由(
[slug])用于博客文章或文档 - API 路由在
app/api/中实现后端功能
9. 参考资料
本笔记中的示例代码基于 create-next-app(Next.js 官方项目生成器)的脚手架创建的项目,然后我自己添加了导航栏、页脚、占位页面和 Tailwind CSS 设计系统。
如果想自己生成一个类似的项目,只需运行:
npx create-next-app@latest my-nextjs-site
这个工具会提示你选择 TypeScript、Tailwind CSS、App Router 啥的选项。生成的项目结构与我笔记中描述的应该非常接近。