Nuxt服务器目录深度解析:全栈Vue开发终极方案
Nuxt服务器目录深度解析:全栈Vue开发终极方案
【免费下载链接】nuxt The Intuitive Vue Framework. 项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt
引言:告别后端焦虑的全栈开发新范式
你是否还在为Vue项目搭建后端服务而烦恼?还在纠结Express、NestJS或Fastify的选型?Nuxt 3引入的服务器目录(server/)彻底改变了Vue开发者的全栈开发体验。通过零配置的文件系统路由、自动HMR支持和与Vue生态的无缝集成,Nuxt服务器目录让前端开发者能够以熟悉的Vue风格编写强大的后端逻辑。本文将带你深入探索这一革命性特性,从基础结构到高级模式,掌握Nuxt全栈开发的核心技能。
读完本文,你将能够:
- 构建RESTful API和服务器路由,无需额外后端框架
- 实现复杂的请求处理逻辑,包括参数验证、身份验证和错误处理
- 优化服务器性能,实现高效的数据获取和缓存策略
- 设计可扩展的服务器架构,支持大型应用开发
- 部署安全可靠的Nuxt全栈应用到各种环境
Nuxt服务器目录:架构概览与核心优势
目录结构解析
Nuxt服务器目录采用约定优于配置的设计理念,通过文件系统自动生成API路由。核心目录结构如下:
-| server/
---| api/ # API路由,自动添加/api前缀
-----| hello.ts # 对应路由: /api/hello
---| routes/ # 服务器路由,无/api前缀
-----| bonjour.ts # 对应路由: /bonjour
---| middleware/ # 服务器中间件
-----| log.ts # 全局请求日志中间件
---| plugins/ # Nitro插件
---| utils/ # 服务器工具函数
这种结构带来三大核心优势:
- 零配置路由:文件路径即API路径,无需手动配置路由表
- 自动热更新:服务器代码变更时自动应用,无需重启开发服务器
- 全栈类型安全:共享类型定义,前后端数据交互类型一致
工作原理流程图
Nuxt服务器基于Nitro引擎构建,这是一个轻量级、跨平台的服务器引擎,为Nuxt提供了强大的后端能力。Nitro支持多种部署目标,包括Node.js、Serverless、 Workers等,使Nuxt应用能够灵活部署到各种环境。
核心组件详解:从路由到中间件
服务器路由(Server Routes)
服务器路由是Nuxt服务器目录的核心,允许你通过简单的文件结构创建API端点。
基础路由示例
export default defineEventHandler((event) => {
return {
message: 'Hello from Nuxt server!',
timestamp: new Date().toISOString()
}
})
客户端调用:
{{ data.message }}
Server time: {{ data.timestamp }}
动态路由参数
Nuxt服务器路由支持动态参数,使用方括号语法:
export default defineEventHandler((event) => {
const userId = getRouterParam(event, 'id')
if (!userId) {
throw createError({
statusCode: 400,
statusMessage: 'User ID is required'
})
}
// 模拟数据库查询
const user = {
id: userId,
name: 'John Doe',
email: `user${userId}@example.com`
}
return { user }
})
HTTP方法匹配
通过文件名后缀指定HTTP方法:
export default defineEventHandler(() => {
return [
{ id: '1', name: 'John Doe' },
{ id: '2', name: 'Jane Smith' }
]
})
// server/api/users.post.ts // 处理POST请求
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 验证请求体
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
statusMessage: 'Name and email are required'
})
}
// 模拟创建用户
return {
id: Date.now().toString(),
...body,
createdAt: new Date().toISOString()
}
})
服务器中间件(Server Middleware)
服务器中间件在所有请求处理前执行,可用于日志记录、身份验证、请求转换等。
全局日志中间件
export default defineEventHandler((event) => {
const start = Date.now()
// 在响应发送后执行的逻辑
event.node.res.on('finish', () => {
const duration = Date.now() - start
const method = event.node.req.method
const url = event.node.req.url
const statusCode = event.node.res.statusCode
console.log(`[${new Date().toISOString()}] ${method} ${url} ${statusCode} (${duration}ms)`)
})
})
身份验证中间件
export default defineEventHandler((event) => {
// 排除公开路由
const publicPaths = ['/api/public', '/api/health']
if (publicPaths.includes(event.node.req.url || '')) {
return
}
const authHeader = getHeader(event, 'authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized - No token provided'
})
}
const token = authHeader.split(' ')[1]
// 简单的令牌验证(实际项目中应使用JWT或类似机制)
const isValidToken = token === useRuntimeConfig().apiSecret
if (!isValidToken) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden - Invalid token'
})
}
// 将用户信息附加到事件上下文
event.context.user = {
id: '1',
name: 'Authenticated User'
}
})
请求处理进阶:H3工具函数
Nuxt服务器提供了丰富的H3工具函数,简化常见的请求处理任务:
| 函数 | 用途 | 示例 |
|---|---|---|
getQuery(event) | 获取查询参数 | const { page, limit } = getQuery(event) |
readBody(event) | 读取请求体 | const formData = await readBody(event) |
getRouterParams(event) | 获取路由参数 | const { id, slug } = getRouterParams(event) |
getHeaders(event) | 获取请求头 | const userAgent = getHeader(event, 'user-agent') |
setResponseStatus(event, code) | 设置响应状态码 | setResponseStatus(event, 201) |
parseCookies(event) | 解析Cookie | const { sessionId } = parseCookies(event) |
useRuntimeConfig(event) | 获取运行时配置 | const apiKey = useRuntimeConfig(event).apiKey |
高级请求处理示例
export default defineEventHandler(async (event) => {
// 获取查询参数并验证
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 10
if (page < 1 || limit < 1 || limit > 100) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid pagination parameters'
})
}
// 获取请求头
const acceptHeader = getHeader(event, 'accept')
const isJsonRequest = acceptHeader?.includes('application/json')
// 使用运行时配置
const config = useRuntimeConfig(event)
// 调用外部API
try {
const response = await $fetch(`${config.apiBaseUrl}/products`, {
params: {
page,
limit,
apiKey: config.apiKey
}
})
// 设置响应头
setHeader(event, 'Cache-Control', 'public, max-age=60')
// 根据请求类型返回不同格式
if (isJsonRequest) {
return {
data: response.results,
pagination: {
total: response.total,
page,
limit,
pages: Math.ceil(response.total / limit)
}
}
} else {
// 返回纯文本
setHeader(event, 'Content-Type', 'text/plain')
return `Products: ${response.results.map(p => p.name).join(', ')}`
}
} catch (error) {
console.error('Failed to fetch products:', error)
throw createError({
statusCode: 502,
statusMessage: 'Failed to fetch products from upstream API'
})
}
})
高级模式与最佳实践
类型安全的服务器API
Nuxt 3.5+提供了增强的服务器类型支持,实现全栈类型安全:
// 共享类型定义
export interface Product {
id: string
name: string
description: string
price: number
inStock: boolean
}
export interface PaginatedResponse {
data: T[]
pagination: {
total: number
page: number
limit: number
pages: number
}
}
import type { Product } from '~/types/product'
export default defineEventHandler(async (event): Promise => {
const id = getRouterParam(event, 'id')
// 实现获取产品逻辑...
return productData
})
{{ product.name }}
{{ product.description }}
${{ product.price.toFixed(2) }}
数据验证与错误处理
使用Zod进行请求数据验证:
import { z } from 'zod'
import { getValidatedBody } from 'h3-zod'
// 定义验证模式
const UserSchema = z.object({
name: z.string().min(3, 'Name must be at least 3 characters'),
email: z.string().email('Invalid email format'),
age: z.number().int('Age must be an integer').min(18, 'Must be at least 18 years old')
})
export default defineEventHandler(async (event) => {
try {
// 验证请求体
const userData = await getValidatedBody(event, UserSchema.parse)
// 数据验证通过,处理用户创建逻辑
return {
success: true,
message: 'User data is valid',
user: {
id: crypto.randomUUID(),
...userData,
createdAt: new Date()
}
}
} catch (error) {
if (error instanceof z.ZodError) {
// 返回验证错误详情
throw createError({
statusCode: 400,
statusMessage: 'Validation failed',
data: {
issues: error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}))
}
})
}
// 其他错误
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
})
}
})
服务器存储与缓存策略
Nitro提供了统一的存储接口,支持多种存储驱动:
export default defineNuxtConfig({
nitro: {
storage: {
cache: {
driver: 'redis',
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
},
data: {
driver: 'fs',
base: './data'
}
}
}
})
缓存实现示例:
export default defineEventHandler(async (event) => {
const productId = getRouterParam(event, 'id')
const cacheKey = `product:${productId}`
// 获取缓存存储
const cacheStorage = useStorage('cache')
// 尝试从缓存获取
const cachedProduct = await cacheStorage.getItem(cacheKey)
if (cachedProduct) {
// 设置缓存命中响应头
setHeader(event, 'X-Cache', 'HIT')
return JSON.parse(cachedProduct as string)
}
// 缓存未命中,从数据库获取
setHeader(event, 'X-Cache', 'MISS')
// 模拟数据库查询
const product = await fetchProductFromDatabase(productId)
if (!product) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found'
})
}
// 存入缓存,有效期5分钟
await cacheStorage.setItem(cacheKey, JSON.stringify(product), {
ttl: 60 * 5 // 5 minutes in seconds
})
return product
})
服务器插件与生命周期
服务器插件允许你扩展Nitro服务器功能,在服务器启动和关闭时执行代码:
import { createDatabaseConnection } from '~/server/utils/db'
export default defineNitroPlugin(async (nitroApp) => {
// 服务器启动时执行
console.log('Connecting to database...')
const dbConnection = await createDatabaseConnection({
host: useRuntimeConfig().dbHost,
port: useRuntimeConfig().dbPort,
username: useRuntimeConfig().dbUsername,
password: useRuntimeConfig().dbPassword,
database: useRuntimeConfig().dbName
})
// 将数据库连接附加到Nitro应用
nitroApp.db = dbConnection
// 服务器关闭时执行清理
nitroApp.hooks.hook('close', async () => {
console.log('Closing database connection...')
await dbConnection.close()
})
})
// 扩展NitroApp类型
declare module 'nitro' {
interface NitroApp {
db: any // 实际项目中应使用具体的数据库连接类型
}
}
在路由中使用数据库连接:
export default defineEventHandler(async (event) => {
const db = event.context.nitroApp.db
const orders = await db.collection('orders').find().toArray()
return orders
})
性能优化与安全最佳实践
服务器性能优化
- 响应缓存
export default defineEventHandler(async (event) => {
// 对公共数据设置较长缓存
setHeader(event, 'Cache-Control', 'public, max-age=3600, stale-while-revalidate=300')
// 检查缓存
const cacheKey = 'stats:global'
const cachedStats = await useStorage('cache').getItem(cacheKey)
if (cachedStats) {
return JSON.parse(cachedStats as string)
}
// 计算统计数据(可能是耗时操作)
const stats = await calculateStatistics()
// 存入缓存
await useStorage('cache').setItem(cacheKey, JSON.stringify(stats), {
ttl: 3600 // 1 hour
})
return stats
})
- 数据库查询优化
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const searchTerm = query.q as string
if (!searchTerm || searchTerm.length < 2) {
throw createError({
statusCode: 400,
statusMessage: 'Search term must be at least 2 characters'
})
}
const db = event.context.nitroApp.db
// 使用索引进行高效查询
const users = await db.collection('users')
.find({
$or: [
{ name: { $regex: searchTerm, $options: 'i' } },
{ email: { $regex: searchTerm, $options: 'i' } }
]
})
.limit(20) // 限制结果数量
.project({ password: 0 }) // 排除敏感字段
.toArray()
return users
})
安全最佳实践
- 输入验证与净化
import { z } from 'zod'
import { getValidatedBody } from 'h3-zod'
import DOMPurify from 'dompurify'
const CommentSchema = z.object({
postId: z.string().regex(/^[0-9a-fA-F]{24}$/, 'Invalid post ID format'),
content: z.string().min(3, 'Comment too short').max(500, 'Comment too long'),
authorName: z.string().min(2, 'Name too short').max(50, 'Name too long')
})
export default defineEventHandler(async (event) => {
// 验证请求体
const commentData = await getValidatedBody(event, CommentSchema.parse)
// 净化HTML内容
const sanitizedContent = DOMPurify.sanitize(commentData.content)
// 检查速率限制
const ip = getRequestIP(event)
const rateLimitKey = `ratelimit:${ip}`
const cache = useStorage('cache')
const currentRequests = await cache.getItem(rateLimitKey) || 0
if (currentRequests >= 10) { // 限制10分钟内最多10条评论
throw createError({
statusCode: 429,
statusMessage: 'Rate limit exceeded. Please try again later.'
})
}
// 更新速率限制计数器
await cache.setItem(rateLimitKey, (parseInt(currentRequests as string) + 1).toString(), {
ttl: 600 // 10 minutes
})
// 保存评论
const newComment = await saveComment({
...commentData,
content: sanitizedContent,
ip: hashIP(ip), // 匿名化IP地址
createdAt: new Date()
})
return newComment
})
- 安全HTTP头设置
export default defineEventHandler((event) => {
// 设置安全相关HTTP头
setHeader(event, 'X-XSS-Protection', '1; mode=block')
setHeader(event, 'X-Content-Type-Options', 'nosniff')
setHeader(event, 'Referrer-Policy', 'strict-origin-when-cross-origin')
setHeader(event, 'X-Frame-Options', 'DENY')
// 内容安全策略
setHeader(event, 'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data:; " +
"connect-src 'self' https://api.example.com"
)
// CORS设置
const origin = getHeader(event, 'origin')
const allowedOrigins = useRuntimeConfig().allowedOrigins.split(',')
if (origin && allowedOrigins.includes(origin)) {
setHeader(event, 'Access-Control-Allow-Origin', origin)
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
}
})
部署与扩展:从开发到生产
环境配置管理
Nuxt提供了强大的运行时配置系统,让你安全地管理不同环境的配置:
export default defineNuxtConfig({
runtimeConfig: {
// 仅服务器可见的私有配置
apiSecret: '',
dbPassword: '',
// 客户端也可见的公共配置(前缀public)
public: {
apiBaseUrl: '/api',
appVersion: '1.0.0'
}
}
})
环境变量文件:
NUXT_API_SECRET=dev_secret_key
NUXT_DB_PASSWORD=dev_password
NUXT_PUBLIC_API_BASE_URL=http://localhost:3000/api
[.env.production]
NUXT_API_SECRET=${PROD_API_SECRET} # 从系统环境变量获取
NUXT_DB_PASSWORD=${PROD_DB_PASSWORD}
NUXT_PUBLIC_API_BASE_URL=https://api.example.com
在服务器路由中使用配置:
export default defineEventHandler((event) => {
const config = useRuntimeConfig(event)
return {
// 只能在服务器端访问
serverOnlyValue: config.apiSecret ? '****' : 'not set',
// 客户端也可以访问
clientAccessibleValue: config.public.apiBaseUrl
}
})
多环境部署策略
Nuxt服务器应用可以部署到多种环境,以下是常见部署目标的配置示例:
1. Node.js服务器部署
{
"scripts": {
"build": "nuxt build",
"start": "node .output/server/index.mjs"
}
}
2. Serverless部署(Vercel)
{
"buildCommand": "nuxt build",
"outputDirectory": ".output",
"installCommand": "pnpm install",
"frameworkPreset": "nuxtjs"
}
3. Docker容器化部署
FROM node:18-alpine AS base
# 安装依赖阶段
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# 生产阶段
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# 复制必要文件
COPY --from=builder /app/.output ./
COPY --from=builder /app/package.json ./
# 非root用户运行
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxtjs
USER nuxtjs
EXPOSE 3000
CMD ["node", "server/index.mjs"]
监控与日志
为生产环境添加监控和日志功能:
import { promisify } from 'util'
import { exec } from 'child_process'
const execAsync = promisify(exec)
export default defineNitroPlugin((nitroApp) => {
// 添加健康检查端点
nitroApp.router.addRoute({
method: 'GET',
path: '/health',
handler: async (event) => {
// 检查数据库连接
const dbHealthy = await checkDatabaseConnection()
// 检查磁盘空间
const diskSpace = await checkDiskSpace()
// 检查内存使用
const memoryUsage = process.memoryUsage()
// 构建健康状态响应
const status = dbHealthy && diskSpace.healthy ? 'ok' : 'degraded'
setResponseStatus(event, status === 'ok' ? 200 : 503)
return {
status,
timestamp: new Date().toISOString(),
services: {
database: dbHealthy ? 'ok' : 'error',
diskSpace: diskSpace.healthy ? 'ok' : 'warning',
memory: {
used: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
total: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB'
}
}
}
}
})
})
async function checkDatabaseConnection() {
try {
const db = useNitroApp().db
await db.command({ ping: 1 })
return true
} catch (error) {
console.error('Database connection check failed:', error)
return false
}
}
async function checkDiskSpace() {
try {
const { stdout } = await execAsync('df -P /')
const lines = stdout.split('
')
const stats = lines[1].split(/s+/)
const usedPercent = parseInt(stats[4], 10)
return {
usedPercent,
healthy: usedPercent < 90
}
} catch (error) {
console.error('Disk space check failed:', error)
return { usedPercent: 0, healthy: false }
}
}
实战案例:构建全功能API服务
案例:电子商务产品API
让我们构建一个完整的电子商务产品API,整合前面学到的各种概念:
import { z } from 'zod'
import { getValidatedQuery } from 'h3-zod'
const ProductQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
category: z.string().optional(),
sort: z.enum(['price_asc', 'price_desc', 'newest']).default('newest'),
minPrice: z.coerce.number().min(0).optional(),
maxPrice: z.coerce.number().min(0).optional()
})
export default defineEventHandler(async (event) => {
// 验证查询参数
const query = await getValidatedQuery(event, ProductQuerySchema.parse)
// 构建数据库查询
const dbQuery: Record = {}
// 添加分类筛选
if (query.category) {
dbQuery.category = query.category
}
// 添加价格范围筛选
if (query.minPrice !== undefined || query.maxPrice !== undefined) {
dbQuery.price = {}
if (query.minPrice !== undefined) dbQuery.price.$gte = query.minPrice
if (query.maxPrice !== undefined) dbQuery.price.$lte = query.maxPrice
}
// 构建排序选项
let sortOption: Record = { createdAt: -1 } // 默认按创建时间降序
switch (query.sort) {
case 'price_asc':
sortOption = { price: 1 }
break
case 'price_desc':
sortOption = { price: -1 }
break
case 'newest':
sortOption = { createdAt: -1 }
break
}
// 计算分页
const skip = (query.page - 1) * query.limit
// 查询数据库
const db = useNitroApp().db
const productsCollection = db.collection('products')
// 使用聚合管道同时获取结果和总数
const [result] = await productsCollection.aggregate([
{ $match: dbQuery },
{
$facet: {
data: [
{ $sort: sortOption },
{ $skip: skip },
{ $limit: query.limit },
// 排除敏感字段
{ $project: { description: 0, __v: 0 } }
],
pagination: [
{ $count: 'total' },
{
$addFields: {
page: query.page,
limit: query.limit,
pages: { $ceil: { $divide: ['$total', query.limit] } }
}
}
]
}
}
]).toArray()
// 格式化响应
return {
data: result.data,
pagination: result.pagination[0] || {
total: 0,
page: query.page,
limit: query.limit,
pages: 0
}
}
})
单个产品API:
import { z } from 'zod'
import { getValidatedRouterParams } from 'h3-zod'
const ParamsSchema = z.object({
id: z.string().regex(/^[0-9a-fA-F]{24}$/, 'Invalid product ID format')
})
export default defineEventHandler(async (event) => {
// 验证路由参数
const { id } = await getValidatedRouterParams(event, ParamsSchema.parse)
// 检查缓存
const cacheKey = `product:${id}`
const cache = useStorage('cache')
const cachedProduct = await cache.getItem(cacheKey)
if (cachedProduct) {
setHeader(event, 'X-Cache', 'HIT')
return JSON.parse(cachedProduct as string)
}
// 从数据库获取产品
const db = useNitroApp().db
const product = await db.collection('products').findOne({ _id: new ObjectId(id) })
if (!product) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found'
})
}
// 转换为API响应格式
const responseProduct = {
id: product._id.toString(),
name: product.name,
price: product.price,
description: product.description,
category: product.category,
images: product.images,
stock: product.stock,
attributes: product.attributes,
createdAt: product.createdAt,
updatedAt: product.updatedAt
}
// 存入缓存,有效期10分钟
await cache.setItem(cacheKey, JSON.stringify(responseProduct), {
ttl: 600
})
setHeader(event, 'X-Cache', 'MISS')
return responseProduct
})
总结与未来展望
Nuxt服务器目录为Vue开发者提供了构建全栈应用的强大能力,通过直观的文件系统路由、强大的中间件系统和丰富的工具函数,大大降低了全栈开发的复杂度。本文详细介绍了Nuxt服务器目录的核心概念、组件和最佳实践,包括:
- 服务器目录结构与工作原理
- 路由定义与请求处理
- 中间件与插件系统
- 数据验证与错误处理
- 性能优化与安全最佳实践
- 部署策略与配置管理
随着Web开发的不断发展,Nuxt服务器引擎Nitro也在持续进化,未来将支持更多部署目标和高级功能。作为开发者,掌握Nuxt全栈开发技能将使你能够构建更高效、更可靠的现代Web应用。
建议继续深入学习以下相关主题:
- Nitro引擎高级配置与自定义
- 全栈应用的测试策略
- 实时通信与WebSockets集成
- GraphQL API实现
- 微服务架构与Nuxt应用集成
通过不断实践和探索,你将能够充分利用Nuxt服务器目录的强大功能,构建出色的全栈Vue应用。
扩展资源与学习路径
-
官方文档
- Nuxt服务器目录文档
- Nitro引擎文档
- H3工具函数文档
-
推荐书籍
- 《Nuxt.js 3 Cookbook》
- 《Full Stack Vue 3 with Nuxt》
-
在线课程
- Nuxt.js全栈开发实战 (Vue School)
- 现代Nuxt 3开发完全指南 (Udemy)
-
社区资源
- Nuxt Discord社区
- Nuxt GitHub讨论区
- Stack Overflow Nuxt标签
-
示例项目
- Nuxt全栈电商示例
- Nuxt服务器API示例集
掌握Nuxt服务器开发是现代Vue开发者的重要技能,它使你能够构建从简单API到复杂全栈应用的各种解决方案。随着你的深入学习和实践,你将能够充分发挥Nuxt全栈开发的潜力,创建高性能、可维护的Web应用。
别忘了收藏本文,关注作者获取更多Nuxt高级教程,下次我们将探讨Nuxt服务器与WebSockets的集成方案!
【免费下载链接】nuxt The Intuitive Vue Framework. 项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt











