Frontend Development

Project Structure

How the Nuda Kit frontend is organized.

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.

Directory Overview

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

Key Directories

components/

Components are organized into three categories:

DirectoryPurpose
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:

ComposablePurpose
useUserQueryUser auth, profile, password
useTeamQueryTeams, members, invitations
useBillingQueryCheckout, portal, invoices
usePlanQuerySubscription plans
useInvitationQueryInvitation actions

types/

TypeScript type definitions for API responses:

  • user.ts — User profile types
  • team.ts — Team types
  • team-member.ts — Team member with roles
  • invitation.ts — Invitation types
  • plan.ts — Subscription plan types
  • subscription.ts — Subscription status types
  • invoice.ts — Stripe invoice types

Layouts

Three layouts handle different contexts:

LayoutUsed For
defaultPublic pages (landing, terms, privacy)
authAuthentication pages (login, signup, etc.)
appAuthenticated dashboard pages
settingsSettings pages (extends app layout)

Set a layout in your page:

<script setup lang="ts">
definePageMeta({
  layout: 'app',
})
</script>

Middleware

Two route middlewares control access:

MiddlewarePurpose
authRedirects unauthenticated users to login
guestRedirects authenticated users to dashboard

Apply middleware in your page:

<script setup lang="ts">
definePageMeta({
  layout: 'app',
  middleware: ['auth'],
})
</script>

Pages

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

Stores

Pinia stores for client-side state:

StorePurpose
authCurrent user state
teamActive team selection
Server state (API data) is managed by TanStack Query, not Pinia. Stores are only for client-side UI state like the currently selected team.

Plugins

PluginPurpose
tanstack-queryInitializes TanStack Query client
ssr-widthHandles responsive breakpoints during SSR

Conventions

File Naming

  • Components — PascalCase (UserAvatar.vue)
  • Composables — camelCase with use prefix (useUserQuery.ts)
  • API Services — kebab-case (login-with-email-password.api.ts)
  • Types — kebab-case (team-member.ts)

Import Aliases

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'