Skip to content

shared/ 目录

shared/ 是 Nuxt 4 新增的目录,用于存放前后端共享的代码

为什么需要 shared/?

在 Nuxt 4 中,app/server/ 使用不同的 TypeScript 项目,它们不能直接互相导入。这带来了一个实际问题:

场景没有 shared/ 时有 shared/ 时
定义用户类型app/server/ 各写一遍写在 shared/types/ 一处即可
前后端共用的格式化函数复制粘贴两份写在 shared/utils/ 一处即可
分页常量两边各定义一次写在 shared/constants.ts 一处即可

核心问题

Nuxt 4 的类型隔离是为了安全(防止客户端误用服务端 API),但也导致前后端无法直接共享代码。shared/ 就是连接两者的桥梁。

类比

app/ 是前端的地盘,server/ 是后端的地盘,shared/ 是双方共享的公共区域。

目录结构

text
shared/
├── types/
│   ├── user.ts          # 用户类型定义
│   └── api.ts           # API 响应类型
├── utils/
│   └── format.ts        # 前后端共用的格式化函数
└── constants.ts         # 常量

推荐的子目录结构

  • types/ — 类型定义(interface、type)
  • utils/ — 工具函数
  • constants.ts — 常量
  • validators/ — 校验逻辑(如手机号、邮箱验证)

使用场景

1. 共享类型定义(最常用)

ts
// shared/types/user.ts
export interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
  createdAt: string
}

export interface UserProfile extends User {
  avatar: string
  bio: string
}

为什么类型定义要放 shared/?

在一个典型的 CRUD 应用中:

  • 服务端 API 返回 User 类型数据
  • 客户端页面接收和显示 User 类型数据
  • 如果类型定义只在一方,另一方就得重新定义或用 any(失去类型检查)

放在 shared/types/ 后,两边都能用,类型保持一致。

在服务端和客户端都可以使用:

ts
// server/api/users/[id].get.ts
import type { User } from '~~/shared/types/user'

export default defineEventHandler((event): User => {
  // 返回值有类型检查,确保返回的数据结构正确
  return { id: 1, name: 'Alice', email: 'a@test.com', role: 'user', createdAt: '...' }
})
vue
<!-- app/pages/profile.vue -->
<script setup lang="ts">
import type { User } from '~~/shared/types/user'

const { data } = await useFetch<User>('/api/users/1')
// data.value 的类型是 User | undefined,有完整的类型提示
</script>

2. 共享工具函数

ts
// shared/utils/format.ts
export const formatDate = (date: string) => {
  return new Date(date).toLocaleDateString('zh-CN')
}

export const truncateText = (text: string, maxLength: number) => {
  return text.length > maxLength ? text.slice(0, maxLength) + '...' : text
}

哪些工具函数适合放 shared/?

  • ✅ 纯函数(输入 → 输出,没有副作用)
  • ✅ 格式化函数(日期、货币、数字)
  • ✅ 校验函数(邮箱、手机号)
  • ✅ 计算函数(折扣、税费)
  • ❌ 依赖浏览器 API 的(如 localStorage
  • ❌ 依赖 Node.js API 的(如 fspath

3. 共享常量

ts
// shared/constants.ts
export const PAGE_SIZE = 20
export const MAX_UPLOAD_SIZE = 10 * 1024 * 1024 // 10MB
export const ROLES = ['admin', 'user'] as const

为什么 as const

as const 让 TypeScript 把 ROLES 推断为只读元组 ['admin', 'user'],而不是 string[]。这样你在代码中使用 ROLES 时,类型是精确的,不会出现 ROLES[0]string 的问题。

4. 共享校验逻辑

ts
// shared/utils/validation.ts
export const isValidEmail = (email: string) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

export const isValidPhone = (phone: string) => {
  return /^1[3-9]\d{9}$/.test(phone)
}

为什么校验逻辑也要共享?

  • 服务端:API 接收请求时验证数据合法性
  • 客户端:表单提交前验证数据合法性
  • 逻辑必须一致!如果服务端允许 10 位手机号,客户端只允许 11 位,就会出现"客户端通过但服务端拒绝"的 bug。

导入路径

shared/ 导入时,使用 ~~/shared/#shared/ 路径:

ts
import type { User } from '~~/shared/types/user'
import { formatDate } from '~~/shared/utils/format'

~~/#shared/ 的区别

  • ~~/ 是路径别名,指向项目根目录,~~/shared/ 就是完整的路径
  • #shared/ 是模块别名,直接指向 shared/ 目录
  • 两者效果相同,#shared/ 更简洁

注意

shared/utils/ 下的函数也支持自动导入,和 app/utils/server/utils/ 一样,不需要手动 import。

自动导入

shared/utils/ 下的函数也支持自动导入,与 app/utils/server/utils/ 类似。

ts
// shared/utils/format.ts
export const formatDate = (date: string) => {
  return new Date(date).toLocaleDateString('zh-CN')
}

app/server/ 中都可以直接使用 formatDate(),无需 import。

注意事项

不要在 shared/ 中使用仅客户端或仅服务端的 API

不可用原因替代方案
ref()reactive()Vue 响应式 API,服务端不需要放在 app/composables/
useState()Nuxt 客户端状态,服务端无法使用放在 app/composables/
readBody()getQuery()Nitro 服务端 API,客户端无法使用放在 server/utils/
windowdocument浏览器 API,服务端没有放在 app/ 代码中
fspathNode.js API,浏览器没有放在 server/ 代码中

确保代码在两端都能运行

shared/ 的铁律:纯 TypeScript,不依赖任何运行时环境。

ts
// ✅ 正确:纯函数,两端都能用
export const add = (a: number, b: number) => a + b

// ❌ 错误:依赖浏览器 API
export const getWidth = () => window.innerWidth

// ❌ 错误:依赖 Node.js API
export const readFile = (path: string) => fs.readFileSync(path)

// ✅ 正确:类型定义,两端都能用
export interface User { id: number; name: string }

三个 utils 目录的对比

目录作用域自动导入典型内容
app/utils/仅客户端(+ SSR 时的服务端渲染)UI 工具函数、DOM 操作辅助
server/utils/仅服务端数据库连接、加密函数
shared/utils/前后端都可用格式化、校验、纯函数

选择原则

如果函数前后端都要用 → shared/utils/;只前端用 → app/utils/;只后端用 → server/utils/。拿不准就放 shared/utils/

基于 Nuxt 4 官方文档整理编写