The frontend follows standard Nuxt 4 conventions with some additional organization patterns specific to Nuda Kit. For general Nuxt directory structure, see the official Nuxt documentation.
This page focuses on Nuda Kit-specific patterns you should understand.
frontend/app/
├── assets/ # CSS and images
├── components/ # Vue components
│ ├── app/ # Application components
│ ├── landing/ # Landing page sections
│ └── ui/ # shadcn/vue primitives
├── composables/ # Vue composables
│ └── queries/ # TanStack Query hooks
├── layouts/ # Page layouts
├── lib/ # Utility functions
├── middleware/ # Route middleware
├── pages/ # File-based routing
├── plugins/ # Nuxt plugins
├── services/api/ # API service functions
├── stores/ # Pinia stores
└── types/ # TypeScript types
components/Components are organized into three categories:
| Directory | Purpose |
|---|---|
components/ui/ | shadcn/vue primitives (Button, Card, Dialog, etc.) |
components/app/ | Application-specific components (forms, headers, sidebars) |
components/landing/ | Landing page sections (Hero, Pricing, FAQ, etc.) |
services/api/API calls are organized by domain, matching the backend structure:
services/api/
├── billing/ # Stripe checkout, portal, invoices
├── invitation/ # Team invitation actions
├── plan/ # Subscription plans
├── team/ # Team CRUD, members, avatars
└── user/ # Auth, profile, settings
Each file exports a single API function, making imports explicit and tree-shakeable.
composables/queries/TanStack Query hooks wrap the API services for data fetching:
| Composable | Purpose |
|---|---|
useUserQuery | User auth, profile, password |
useTeamQuery | Teams, members, invitations |
useBillingQuery | Checkout, portal, invoices |
usePlanQuery | Subscription plans |
useInvitationQuery | Invitation actions |
types/TypeScript type definitions for API responses:
user.ts — User profile typesteam.ts — Team typesteam-member.ts — Team member with rolesinvitation.ts — Invitation typesplan.ts — Subscription plan typessubscription.ts — Subscription status typesinvoice.ts — Stripe invoice typesThree layouts handle different contexts:
| Layout | Used For |
|---|---|
default | Public pages (landing, terms, privacy) |
auth | Authentication pages (login, signup, etc.) |
app | Authenticated dashboard pages |
settings | Settings pages (extends app layout) |
Set a layout in your page:
<script setup lang="ts">
definePageMeta({
layout: 'app',
})
</script>
Two route middlewares control access:
| Middleware | Purpose |
|---|---|
auth | Redirects unauthenticated users to login |
guest | Redirects authenticated users to dashboard |
Apply middleware in your page:
<script setup lang="ts">
definePageMeta({
layout: 'app',
middleware: ['auth'],
})
</script>
Pages are split between public and authenticated:
pages/
├── index.vue # Landing page
├── privacy-policy.vue # Privacy policy
├── terms-of-service.vue # Terms of service
└── app/ # Authenticated area
├── index.vue # Dashboard
├── ai-chat.vue # AI chat
├── auth/ # Auth flows
│ ├── login.vue
│ ├── signup.vue
│ └── ...
├── invitations/
│ └── [token].vue # Accept invitation
└── settings/
├── index.vue # General settings
├── profile.vue # Profile settings
├── security.vue # Password settings
├── billing.vue # Subscription
└── teams/ # Team management
Pinia stores for client-side state:
| Store | Purpose |
|---|---|
auth | Current user state |
team | Active team selection |
| Plugin | Purpose |
|---|---|
tanstack-query | Initializes TanStack Query client |
ssr-width | Handles responsive breakpoints during SSR |
UserAvatar.vue)use prefix (useUserQuery.ts)login-with-email-password.api.ts)team-member.ts)Nuda Kit uses the @/ alias for clean imports:
import { Button } from '@/components/ui/button'
import { useUserQuery } from '@/composables/queries/useUserQuery'
import type { User } from '@/types/user'