Next.js第二课 - 项目结构详解 - 优栈

0 阅读6分钟

上节我们搭建好了 Next.js 开发环境,本节就来详细了解一下 Next.js 的项目结构。很多初学者刚打开项目时会看到一堆文件和文件夹,不知道每个都是干什么的。别担心,本节会带你理清这些目录和文件的用途,让你对项目结构有一个清晰的认识。

项目结构概览

my-nextjs-app/
├── app/                          # App Router(主要工作目录)
│   ├── (auth)/                   # 路由组(不影响 URL)
│   │   ├── login/
│   │   │   └── page.tsx
│   │   └── register/
│   │       └── page.tsx
│   ├── api/                      # API 路由
│   │   └── users/
│   │       └── route.ts
│   ├── blog/                     # 应用路由
│   │   ├── [slug]/              # 动态路由
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── layout.tsx               # 根布局
│   ├── page.tsx                 # 首页
│   ├── loading.tsx              # 加载状态
│   ├── error.tsx                # 错误处理
│   ├── not-found.tsx            # 404 页面
│   └── globals.css              # 全局样式
├── components/                   # 共享组件
│   ├── ui/                      # UI 基础组件
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   └── Card.tsx
│   └── layout/                  # 布局组件
│       ├── Header.tsx
│       └── Footer.tsx
├── lib/                         # 工具函数
│   ├── utils.ts
│   ├── api.ts
│   └── constants.ts
├── hooks/                       # 自定义 Hooks
│   ├── useAuth.ts
│   └── useData.ts
├── types/                       # TypeScript 类型
│   └── index.ts
├── public/                      # 静态资源
│   ├── images/
│   ├── fonts/
│   └── favicon.ico
├── styles/                      # 样式文件(可选)
│   └── globals.css
├── .env.local                   # 环境变量
├── .eslintrc.json              # ESLint 配置
├── .gitignore                  # Git 忽略文件
├── next.config.js              # Next.js 配置
├── package.json                # 项目配置
├── tsconfig.json               # TypeScript 配置
└── README.md                   # 项目说明

image.png

核心目录详解

1. app/ 目录 - App Router

app/ 目录是 Next.js 13+ 推荐的新路由系统,基于 React Server Components 构建。这是你工作中最常打交道的目录,绝大部分页面和路由都会放在这里。

特殊文件

文件用途必需
layout.tsx定义布局和 UI可选
page.tsx定义路由的独特 UI必需(可访问路由)
loading.tsx加载时的 UI 替换可选
error.tsx错误边界 UI可选
not-found.tsx404 页面可选
route.tsAPI 端点API 路由必需

示例结构

app/
├── (marketing)/              # 路由组
│   ├── about/
│   │   └── page.tsx         # /about
│   ├── layout.tsx           # 营销页面共享布局
│   └── page.tsx             # /
├── (shop)/                   # 另一个路由组
│   ├── account/
│   │   └── page.tsx         # /account
│   └── layout.tsx           # 商店页面共享布局
├── products/
│   ├── [id]/                # 动态段
│   │   └── page.tsx         # /products/123
│   └── page.tsx             # /products
├── api/
│   └── users/
│       └── route.ts         # /api/users (API)
├── layout.tsx               # 根布局(所有页面共享)
└── page.tsx                 # 首页

2. components/ 目录

这里存放可复用的 React 组件。当你发现一段 UI 代码在多个页面重复出现时,就可以把它抽取成一个组件放到这里。随着项目变大,良好的组件组织会让代码更容易维护。

components/
├── ui/                       # 基础 UI 组件
│   ├── Button.tsx
│   ├── Input.tsx
│   ├── Modal.tsx
│   └── Table.tsx
├── layout/                   # 布局组件
│   ├── Header.tsx
│   ├── Footer.tsx
│   ├── Sidebar.tsx
│   └── Navigation.tsx
├── features/                 # 功能组件
│   ├── UserProfile.tsx
│   ├── ProductCard.tsx
│   └── CommentList.tsx
└── forms/                    # 表单组件
    ├── LoginForm.tsx
    └── ContactForm.tsx

组件示例

// components/ui/Button.tsx
interface ButtonProps {
  children: React.ReactNode
  onClick?: () => void
  variant?: 'primary' | 'secondary'
}

export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`px-4 py-2 rounded ${
        variant === 'primary'
          ? 'bg-blue-500 text-white'
          : 'bg-gray-200 text-gray-800'
      }`}
    >
      {children}
    </button>
  )
}

3. lib/ 目录

这里存放工具函数、API 客户端、常量等辅助代码。把不属于任何特定业务逻辑的通用代码放在这里是个好习惯。

lib/
├── utils/                    # 工具函数
│   ├── format.ts            # 格式化函数
│   ├── validation.ts        # 验证函数
│   └── helpers.ts           # 辅助函数
├── api/                      # API 客户端
│   ├── client.ts
│   ├── users.ts
│   └── products.ts
├── db/                       # 数据库相关
│   ├── connect.ts
│   └── queries.ts
└── constants.ts              # 常量定义

工具函数示例

// lib/utils/format.ts

export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  }).format(date)
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('zh-CN', {
    style: 'currency',
    currency: 'CNY',
  }).format(amount)
}

4. hooks/ 目录

存放自定义 React Hooks。如果你有一些状态逻辑需要在多个组件中复用,就可以封装成自定义 Hook 放在这里。

hooks/
├── useAuth.ts                # 认证相关
├── useData.ts                # 数据获取
├── useForm.ts                # 表单处理
└── useLocalStorage.ts        # 本地存储

Hook 示例

// hooks/useAuth.ts
'use client'

import { useState, useEffect } from 'react'

export function useAuth() {
  const [isAuthenticated, setIsAuthenticated] = useState(false)
  const [user, setUser] = useState(null)

  useEffect(() => {
    // 检查认证状态
    const token = localStorage.getItem('token')
    setIsAuthenticated(!!token)
  }, [])

  return { isAuthenticated, user }
}

5. public/ 目录

这里存放静态资源,比如图片、字体、favicon 等。放在 public 目录下的文件可以直接通过 URL 访问,不需要 import。

public/
├── images/
│   ├── logo.png
│   └── banner.jpg
├── fonts/
│   └── custom-font.woff2
├── favicon.ico
└── robots.txt

使用方式

// 在组件中引用
<Image src="/images/logo.png" alt="Logo" width={200} height={100} />

6. 根配置文件

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,

  // 图片优化配置
  images: {
    domains: ['example.com'],
    formats: ['image/avif', 'image/webp'],
  },

  // 环境变量
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },

  // 重定向
  async redirects() {
    return [
      {
        source: '/old-path',
        destination: '/new-path',
        permanent: true,
      },
    ]
  },
}

module.exports = nextConfig

tsconfig.json

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

路由组织最佳实践

1. 使用路由组

路由组 (group-name) 是 Next.js 的一个很有用的特性,它不会影响 URL 路径,但可以帮助你更好地组织代码和共享布局。比如你想把某些页面放在一起管理,但又不想改变 URL 结构,就可以用路由组。

app/
├── (marketing)/              # /about, /contact
│   ├── about/
│   │   └── page.tsx
│   ├── contact/
│   │   └── page.tsx
│   └── layout.tsx           # 营销页面共享布局
├── (dashboard)/              # /dashboard, /settings
│   ├── dashboard/
│   │   └── page.tsx
│   ├── settings/
│   │   └── page.tsx
│   └── layout.tsx           # 需要认证的布局
└── page.tsx                 # 首页

2. 动态路由

动态路由在实际开发中非常常见,比如博客文章页、用户详情页等。使用方括号 [param] 就可以创建动态路由段,Next.js 会自动匹配并解析参数。

app/
├── blog/
│   ├── [slug]/              # /blog/hello-world
│   │   └── page.tsx
│   └── page.tsx             # /blog
├── products/
│   ├── [category]/          # /products/electronics
│   │   └── page.tsx
│   └── page.tsx             # /products
└── users/
    └── [id]/                # /users/123
        ├── [action]/        # /users/123/edit
        │   └── page.tsx
        └── page.tsx         # /users/123

3. 并行和拦截路由

并行路由和拦截路由是 Next.js 的高级特性,可以实现一些复杂的交互效果,比如模态框、并行加载多个页面等。这些特性在实际项目中非常有用,但理解起来可能需要一点时间。

app/
├── @dashboard/               # 并行路由槽
│   └── page.tsx
├── (.)modal/                 # 拦截路由
│   └── photo/[id]/page.tsx
├── dashboard/
│   └── page.tsx
└── layout.tsx

文件命名约定

路由相关

模式说明示例 URL
folder/page.tsx标准路由/folder
folder/[slug]/page.tsx动态路由/folder/value
folder/[[...slug]]/page.tsx捕获所有路由/folder/a/b/c
(group)/page.tsx路由组/page
folder/(.)modal/...拦截路由-

特殊文件

文件说明
_filename.tsx私有文件,不创建路由
filename.server.tsx仅在服务器运行
filename.client.tsx仅在客户端运行

代码组织建议

1. 按功能组织

按功能组织是一种常见的项目结构方式,把相关的功能放在一起。这种方式适合中小型项目,代码结构清晰易懂。

app/
├── (auth)/
│   ├── login/
│   ├── register/
│   └── forgot-password/
├── (dashboard)/
│   ├── overview/
│   ├── analytics/
│   └── settings/
└── (public)/
    ├── about/
    ├── contact/
    └── pricing/

2. 按层级组织

按层级组织适合大型项目,比如有 API 版本管理、多级管理后台等场景。这种方式可以让结构更有层次感。

app/
├── api/
│   ├── v1/
│   │   ├── users/
│   │   └── posts/
│   └── v2/
│       └── users/
└── admin/
    └── users/
        ├── [id]/
        └── new/

3. 组件分层

组件分层是一种借鉴原子设计的组织方式,把组件按照复杂度分成原子、分子、组织、模板等层级。这种方式适合 UI 组件库或者设计系统比较完善的项目。

components/
├── atoms/                    # 最小单元
│   ├── Button.tsx
│   └── Input.tsx
├── molecules/                # 组合原子
│   ├── SearchBar.tsx
│   └── FormField.tsx
├── organisms/                # 复杂组件
│   ├── Header.tsx
│   └── ProductCard.tsx
└── templates/                # 页面模板
    └── BlogLayout.tsx

环境变量

环境变量用来存储一些敏感信息或者配置,比如数据库连接字符串、API 密钥等。创建 .env.local 文件来存放这些信息,记得把这个文件加到 .gitignore 里,不要提交到代码仓库。

# 数据库
DATABASE_URL=postgresql://...

# API 密钥
API_KEY=your_api_key
API_SECRET=your_api_secret

# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000

访问方式:

// 服务器端
const dbUrl = process.env.DATABASE_URL

// 客户端(必须以 NEXT_PUBLIC_ 开头)
const appUrl = process.env.NEXT_PUBLIC_APP_URL

总结

本节我们详细了解了 Next.js 的项目结构,包括各个目录的用途、路由组织方式、以及一些最佳实践。掌握项目结构是学好 Next.js 的基础,建议你多花点时间理解这些内容。

如果你对本节内容有任何疑问,欢迎在评论区提出来,我们一起学习讨论。

原文链接:https://blog.uuhb.cn/archives/Next-js-02.html