scaffold-route
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseScaffold Route
路由脚手架搭建
Create a complete vertical slice for a new feature: page, API, and validation.
为新功能创建完整的垂直切片:页面、API和验证。
What gets created
生成的文件
app/[feature]/
├── page.tsx # Server component
├── loading.tsx # Skeleton loader
├── error.tsx # Error boundary
└── components/
└── index.ts # Barrel export
server/api/routers/[feature]/
└── index.ts # tRPC router with CRUD procedures
validations/
└── [feature].ts # Zod schemas + inferred typesapp/[feature]/
├── page.tsx # Server component
├── loading.tsx # Skeleton loader
├── error.tsx # Error boundary
└── components/
└── index.ts # Barrel export
server/api/routers/[feature]/
└── index.ts # tRPC router with CRUD procedures
validations/
└── [feature].ts # Zod schemas + inferred typesWhat gets updated
更新的内容
- - import and add router to appRouter
server/api/root.ts - - add barrel export
validations/index.ts
- - 导入路由并添加至appRouter
server/api/root.ts - - 添加桶导出
validations/index.ts
Instructions
操作步骤
- Ask for the feature name (singular, lowercase, e.g., "project", "booking", "invoice")
- Ask which CRUD operations are needed: list, get, create, update, delete
- Generate files following the patterns below
- Update barrel exports and root router
- 确认功能名称(单数、小写,例如:"project"、"booking"、"invoice")
- 确认所需的CRUD操作:列表查询、详情查询、创建、更新、删除
- 按照以下模板生成文件
- 更新桶导出和根路由
Patterns
模板示例
page.tsx (Server Component)
page.tsx(服务端组件)
tsx
import { Suspense } from 'react'
import { FeatureList } from './components'
import { FeatureSkeleton } from '@/components/skeletons'
export default function FeaturePage() {
return (
<main className="container py-8">
<h1 className="text-2xl font-bold mb-6">Features</h1>
<Suspense fallback={<FeatureSkeleton />}>
<FeatureList />
</Suspense>
</main>
)
}tsx
import { Suspense } from 'react'
import { FeatureList } from './components'
import { FeatureSkeleton } from '@/components/skeletons'
export default function FeaturePage() {
return (
<main className="container py-8">
<h1 className="text-2xl font-bold mb-6">Features</h1>
<Suspense fallback={<FeatureSkeleton />}>
<FeatureList />
</Suspense>
</main>
)
}loading.tsx
loading.tsx
tsx
import { FeatureSkeleton } from '@/components/skeletons'
export default function Loading() {
return <FeatureSkeleton />
}tsx
import { FeatureSkeleton } from '@/components/skeletons'
export default function Loading() {
return <FeatureSkeleton />
}error.tsx
error.tsx
tsx
'use client'
interface Props {
error: Error & { digest?: string }
reset: () => void
}
export default function Error({ error, reset }: Props) {
return (
<main className="container py-8">
<h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
<p className="text-muted-foreground mb-4">{error.message}</p>
<button onClick={reset} className="text-primary underline">
Try again
</button>
</main>
)
}tsx
'use client'
interface Props {
error: Error & { digest?: string }
reset: () => void
}
export default function Error({ error, reset }: Props) {
return (
<main className="container py-8">
<h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
<p className="text-muted-foreground mb-4">{error.message}</p>
<button onClick={reset} className="text-primary underline">
Try again
</button>
</main>
)
}validations/[feature].ts
validations/[feature].ts
tsx
import { z } from 'zod'
export const createFeatureSchema = z.object({
name: z.string().min(1, 'Name is required'),
})
export const updateFeatureSchema = createFeatureSchema.partial()
export type CreateFeatureInput = z.infer<typeof createFeatureSchema>
export type UpdateFeatureInput = z.infer<typeof updateFeatureSchema>tsx
import { z } from 'zod'
export const createFeatureSchema = z.object({
name: z.string().min(1, 'Name is required'),
})
export const updateFeatureSchema = createFeatureSchema.partial()
export type CreateFeatureInput = z.infer<typeof createFeatureSchema>
export type UpdateFeatureInput = z.infer<typeof updateFeatureSchema>server/api/routers/[feature]/index.ts
server/api/routers/[feature]/index.ts
tsx
import { z } from 'zod'
import { router, publicProcedure } from '@/server/api/trpc'
import { createFeatureSchema, updateFeatureSchema } from '@/validations'
import { prisma } from '@/prisma/prisma'
export const featureRouter = router({
list: publicProcedure.query(async () => {
return prisma.feature.findMany({
select: { id: true, name: true, createdAt: true },
orderBy: { createdAt: 'desc' },
})
}),
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return prisma.feature.findUniqueOrThrow({
where: { id: input.id },
select: { id: true, name: true, createdAt: true },
})
}),
create: publicProcedure
.input(createFeatureSchema)
.mutation(async ({ input }) => {
return prisma.feature.create({
data: input,
select: { id: true },
})
}),
update: publicProcedure
.input(z.object({ id: z.string(), data: updateFeatureSchema }))
.mutation(async ({ input }) => {
return prisma.feature.update({
where: { id: input.id },
data: input.data,
select: { id: true },
})
}),
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
return prisma.feature.delete({
where: { id: input.id },
select: { id: true },
})
}),
})tsx
import { z } from 'zod'
import { router, publicProcedure } from '@/server/api/trpc'
import { createFeatureSchema, updateFeatureSchema } from '@/validations'
import { prisma } from '@/prisma/prisma'
export const featureRouter = router({
list: publicProcedure.query(async () => {
return prisma.feature.findMany({
select: { id: true, name: true, createdAt: true },
orderBy: { createdAt: 'desc' },
})
}),
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return prisma.feature.findUniqueOrThrow({
where: { id: input.id },
select: { id: true, name: true, createdAt: true },
})
}),
create: publicProcedure
.input(createFeatureSchema)
.mutation(async ({ input }) => {
return prisma.feature.create({
data: input,
select: { id: true },
})
}),
update: publicProcedure
.input(z.object({ id: z.string(), data: updateFeatureSchema }))
.mutation(async ({ input }) => {
return prisma.feature.update({
where: { id: input.id },
data: input.data,
select: { id: true },
})
}),
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input }) => {
return prisma.feature.delete({
where: { id: input.id },
select: { id: true },
})
}),
})Updating root.ts
更新root.ts
tsx
import { featureRouter } from './routers/feature'
export const appRouter = router({
// existing routers...
feature: featureRouter,
})Note: Prisma model scaffolding is currently under review. For now, create models manually inand run migrations before using this skill.prisma/schema.prisma
tsx
import { featureRouter } from './routers/feature'
export const appRouter = router({
// existing routers...
feature: featureRouter,
})注意: Prisma模型脚手架目前正在审核中。目前请在中手动创建模型,并在使用此技能前运行迁移。prisma/schema.prisma
Checklist
检查清单
- Feature name is singular and lowercase
- All files use imports
@/ - Barrel exports updated
- Root router updated
- Skeleton component exists or created
- No types
any - No semicolons
- 功能名称为单数且小写
- 所有文件使用路径导入
@/ - 已更新桶导出
- 已更新根路由
- 骨架屏组件已存在或已创建
- 未使用类型
any - 未使用分号