Monorepo using pnpm workspaces with Turbo. Contains multiple Next.js apps:
apps/web- Main web app (port 3000)apps/admin- Admin dashboard (port 3001)packages/ui- Shared UI components
pnpm build # Build all
pnpm dev # Dev all
pnpm lint # Lint all
pnpm check-types # Type check all
pnpm format # Format code
pnpm test # Test allcd apps/web && pnpm vitest run path/to/test.spec.ts
pnpm vitest run --grep "test name"
pnpm vitest --watch path/to/test.spec.ts- Package Manager: pnpm | Framework: Next.js 15+ (App Router)
- Language: TypeScript (strict mode) | Testing: Vitest + React Testing Library
{
"semi": false,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2
}Run pnpm format before committing. Uses prettier-plugin-tailwindcss.
- Strict mode +
noUncheckedIndexedAccessenabled - use optional chaining - moduleResolution: Bundler (Next.js), NodeNext (packages)
- Return types required:
const sum = (a: number, b: number): number => a + b - Types over interfaces for simple types
- Node/built-in 2. External packages (react, next, @tanstack/react-query)
- Absolute imports (@/, @repo/ui/*) 4. Relative imports (./, ../)
import path from 'path'
import { useState } from 'react'
import { Icon } from '@repo/ui/components/Icon'
import { CLIENT_PATH } from '@/_constants/path'
import { BottomNavigation } from './_components/BottomNavigation'Aliases: @/* β apps/web/app/*, @repo/ui/components/* β packages/ui/src/components/*
- Components: PascalCase (
BottomNavigation) | Files: PascalCase (components), kebab-case (utilities) - Functions/variables: camelCase | Constants: SCREAMING_SNAKE_CASE | React Query Keys: PascalCase
import { cn } from '@repo/ui/utils/cn'
interface Props { className?: string; children?: React.ReactNode }
export const ComponentName = ({ className, children }: Props) => {
return <div className={cn('base-classes', className)}>{children}</div>
}export const CategoryQueryKeys = {
all: () => ['category'] as const,
list: () => [...CategoryQueryKeys.all(), 'list'] as const,
}
export const useCategoryQueries = {
list: () =>
queryOptions({
queryKey: CategoryQueryKeys.list(),
queryFn: getCategories,
}),
}onError: (error) => {
console.error(error) /* Handle error */
}<Flex className={'gap-4 px-5 py-2.5'}><type>: <subject>
Types: feat, fix, bug, refactor, design, style, docs, test, chore, rename, remove, build
- Test files:
*.spec.tsor*.test.ts- Vitest + @testing-library/react
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
test('renders correctly', () => {
render(<Component />)
expect(screen.getByText('Hello')).toBeInTheDocument()
})pnpm lint # All apps | cd apps/web && pnpm lint # Single appapps/web/app/
βββ (home)/ # Route group
βββ _apis/ # queries, mutations, services
βββ _components/ # Shared components
βββ _constants/ # Constants
βββ _hooks/ # Custom hooks
βββ _providers/ # React providers
βββ _utils/ # Utilities
- Use turbopack:
next dev --turbopack| MSW for API mocking - Use
HydrationBoundaryPagefor SSR prefetch - HeroUI, Naver Maps, Zod, react-hook-form + zod