Skip to content

Collage Admin (Nuxt 4) ​

Nuxt 4 SSR rewrite of the Admin-Frontend. Same domain features as the Nuxt 2 app — DAM, workspaces, portals, search, analytics — rebuilt with the Composition API, Pinia, TanStack Query, and strict TypeScript.

Dev server runs on port 5000 (nuxt.config.ts devServer.host: '0.0.0.0').

Tech Stack ​

LayerChoice
FrameworkNuxt 4 (compatibilityDate: '2025-06-30') + Vue 3
UI componentsVuetify 3 (auto-import via vite-plugin-vuetify)
StatePinia 3 (Options Store syntax)
Data-fetchingTanStack Vue Query 5 (useQuery / useMutation)
HTTP$fetch → $api (configured in plugins/fetch.ts)
SearchTypesense 3 (server-proxy Nitro routes hide the API key)
RealtimeLaravel Echo 2 + Pusher 8 (echo.client.ts)
AnalyticsAmplitude browser 2 + Session Replay 1
AuthCustom JWT — cookie (SSR) + localStorage + useState
Passkeys@simplewebauthn/browser
StorageAWS S3 multipart (server-proxy Nitro routes)
ValidationZod 4
Image editingCropper.js + Jimp + html-to-image
ChartsChart.js 4 + vue-chartjs
PaymentsStripe (keys in runtime config, never in client bundle)
StylingSCSS (sass-embedded), MDI fonts, custom collage icon collection
LanguageTypeScript 5 strict mode
Package managerpnpm 9 only
Node^22.12.0 / ^24.11.0 / ≥26.0.0

Project Structure ​

collage-admin/
├── app/                    # All application source (Nuxt 4 convention)
│   ├── pages/              # File-based routing
│   ├── components/         # Vue SFCs (PascalCase, pathPrefix: false)
│   ├── composables/        # Auto-imported composables (api/, auth/, core/, queries/)
│   ├── stores/             # Pinia stores (Options API syntax)
│   ├── layouts/            # Layout wrappers
│   ├── middleware/         # Route guards
│   ├── plugins/            # Nuxt plugins
│   ├── utils/              # Auto-imported utilities
│   ├── constants/          # Auto-imported constants
│   ├── types/              # TypeScript types (NOT auto-imported)
│   └── assets/scss/        # Global SCSS
├── server/                 # Nitro server routes and API handlers
│   ├── api/                # Proxied API routes (hides secrets)
│   ├── routes/             # Non-API server routes (manifest, health)
│   └── utils/              # Server utilities (CSRF, rate-limit, S3 helpers)
├── public/                 # Static files
├── scripts/                # Build/maintenance scripts
├── docs/                   # Internal design docs and tickets
└── nuxt.config.ts          # Framework configuration

Auto-Import Configuration ​

Nuxt auto-imports from these directories — you do not need explicit import statements:

  • composables/** — all composables
  • utils/** — all utilities
  • stores/** — all Pinia stores
  • constants/** — all constants

Not auto-imported: app/types/ — always import type { X } from '~/types/x' explicitly.

Data Fetching Pattern ​

All server communication uses a strict two-layer architecture built on TanStack Vue Query 5.

Layer 1 — API Composables (composables/api/) ​

Raw fetch functions that wrap $api (the configured $fetch instance). They handle request/response shape and nothing else — no caching, no reactivity.

ts
// composables/api/useAssetsApi.ts
export function useAssetsApi() {
  const { $api } = useNuxtApp()
  return {
    listAssets: (params: AssetListParams) =>
      $api<AssetListResponse>('digital-assets/list', { params }),
    deleteAsset: (id: number) =>
      $api(`digital-assets/${id}`, { method: 'DELETE' }),
  }
}

Layer 2 — Query Composables (composables/queries/) ​

TanStack Query wrappers that consume Layer 1 functions. They own caching, background refetch, invalidation, and optimistic updates.

ts
// composables/queries/useAssetsQueries.ts
export function useAssetsQueries(params: MaybeRef<AssetListParams>) {
  const { listAssets } = useAssetsApi()
  return useQuery({
    queryKey: ['assets', params],
    queryFn: () => listAssets(toValue(params)),
    staleTime: useRuntimeConfig().public.queryStaleTime,
  })
}

Rules ​

  • Components only call Layer 2 (query composables) — never $api directly.
  • Mutations call queryClient.invalidateQueries after success to keep caches in sync.
  • queryStaleTime is controlled by the NUXT_PUBLIC_QUERY_STALE_TIME env var (default 300000 ms / 5 min).

Key Commands ​

bash
pnpm dev          # Dev server (port 5000)
pnpm build        # Production build
pnpm preview      # Preview production build
pnpm lint         # ESLint
pnpm typecheck    # Nuxt typecheck

Coding Conventions ​

  • <script setup lang="ts"> exclusively — no Options API, no defineComponent
  • defineProps<T>() and defineEmits<T>() — always typed generics
  • No any without an eslint-disable comment and justification
  • interface for object shapes; type for unions/aliases
  • Composable naming: useFeatureName.ts; API composables: useFeatureNameApi.ts
  • Composables always return named properties, never positional arrays
  • Pinia: Options Store syntax (defineStore('name', { state, getters, actions }))
  • Actions are the only state mutators — never store.prop = from components
  • pnpm only — do not use npm or yarn
  • No console.log (esbuild strips them in production anyway)
  • No code comments — self-documenting names and small functions; explanations go in commit messages and docs
  • No AI co-author attribution in commits