Files
thrillwiki_django_no_react/.agent/rules/nuxt-standards.md
pacnpal 1adba1b804 lol
2026-01-02 07:58:58 -05:00

4.7 KiB

ThrillWiki Nuxt 4 Frontend Standards

Rules for developing the ThrillWiki frontend with Nuxt 4.

Project Structure

frontend/
├── app.vue                  # Root component
├── nuxt.config.ts          # Nuxt configuration
├── pages/                   # File-based routing
│   ├── index.vue           # Homepage (/)
│   ├── parks/
│   │   ├── index.vue       # /parks
│   │   ├── nearby.vue      # /parks/nearby
│   │   └── [slug].vue      # /parks/:slug
│   └── ...
├── components/
│   ├── layout/             # Header, Footer, Sidebar
│   ├── ui/                 # Base components (Button, Card, Input)
│   ├── entity/             # ParkCard, RideCard, ReviewCard
│   └── forms/              # Form components
├── composables/            # Shared logic (useAuth, useApi, useUnits)
├── stores/                 # Pinia stores
├── types/                  # TypeScript interfaces
└── assets/
    └── css/                # Global styles, Tailwind config

Component Conventions

Naming

  • Use PascalCase for component files: ParkCard.vue, SearchAutocomplete.vue
  • Use kebab-case in templates: <park-card>, <search-autocomplete>
  • Prefix base components with Base: BaseButton.vue, BaseInput.vue

Component Structure

<script setup lang="ts">
// 1. Type imports
import type { Park } from '~/types'

// 2. Component imports (auto-imported usually)

// 3. Props and emits
const props = defineProps<{
  park: Park
  variant?: 'default' | 'compact'
}>()

const emit = defineEmits<{
  (e: 'select', park: Park): void
}>()

// 4. Composables
const { formatDistance } = useUnits()

// 5. Refs and reactive state
const isExpanded = ref(false)

// 6. Computed properties
const displayLocation = computed(() => 
  `${props.park.city}, ${props.park.country}`
)

// 7. Functions
function handleClick() {
  emit('select', props.park)
}

// 8. Lifecycle hooks (if needed)
</script>

<template>
  <!-- Template here -->
</template>

<style scoped>
/* Scoped styles, prefer Tailwind classes in template */
</style>

TypeScript

  • Enable strict mode
  • Define interfaces for all data structures in types/
  • Use defineProps<T>() with TypeScript generics
  • No any types without explicit justification

Routing

File-Based Routes

Follow Nuxt 4 file-based routing conventions:

  • pages/index.vue/
  • pages/parks/index.vue/parks
  • pages/parks/[slug].vue/parks/:slug
  • pages/parks/[park]/rides/[ride].vue/parks/:park/rides/:ride

Navigation

// Use navigateTo for programmatic navigation
await navigateTo('/parks/cedar-point')

// Use NuxtLink for declarative navigation
<NuxtLink to="/parks">All Parks</NuxtLink>

Data Fetching

Use Composables

// composables/useParks.ts
export function useParks() {
  const { data, pending, error, refresh } = useFetch('/api/parks/')
  
  return {
    parks: data,
    loading: pending,
    error,
    refresh
  }
}

In Components

// Use useAsyncData for page-level data
const { data: park } = await useAsyncData(
  `park-${route.params.slug}`,
  () => $fetch(`/api/parks/${route.params.slug}/`)
)

State Management (Pinia)

Store Structure

// stores/auth.ts
export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null)
  const isAuthenticated = computed(() => !!user.value)
  
  async function login(credentials: LoginCredentials) {
    // Implementation
  }
  
  function logout() {
    user.value = null
  }
  
  return { user, isAuthenticated, login, logout }
})

Using Stores

const authStore = useAuthStore()
const { user, isAuthenticated } = storeToRefs(authStore)

API Integration

Base API Composable

// composables/useApi.ts
export function useApi() {
  const config = useRuntimeConfig()
  const authStore = useAuthStore()
  
  return $fetch.create({
    baseURL: config.public.apiBase,
    headers: {
      ...(authStore.token && { Authorization: `Bearer ${authStore.token}` })
    }
  })
}

Error Handling

Page Errors

// In page components
const { data, error } = await useAsyncData(...)

if (error.value) {
  throw createError({
    statusCode: error.value.statusCode || 500,
    message: error.value.message
  })
}

Form Errors

  • Display validation errors inline with form fields
  • Use toast notifications for API errors
  • Provide clear user feedback

Accessibility Requirements

  • All interactive elements must be keyboard accessible
  • Provide proper ARIA labels
  • Ensure color contrast meets WCAG AA standards
  • Support prefers-reduced-motion
  • Use semantic HTML elements