Nuxt Guide

Folder Structure

Project folder layout, nuxt.config.ts baseline, CSS setup, and app.vue structure.

Folder Structure

app/
├── app.vue                ← Root component — wraps <NuxtLayout> in <ClientOnly>
├── assets/
│   └── css/
│       └── main.css       ← @import 'tailwindcss'; @plugin 'daisyui';
├── components/            ← PascalCase.vue components; auto-imported by Nuxt
├── composables/           ← useXDomain() factory functions; auto-imported
├── layouts/
│   └── default.vue        ← Main app layout (sidebar drawer or navbar)
├── middleware/
│   ├── auth.ts            ← Named — redirects to /login if no token
│   ├── tenant.global.ts   ← Global — runs on every route
│   └── 01.org.ts          ← Ordered global — numeric prefix controls order
├── pages/                 ← File-based routing; <script setup> + definePageMeta
├── plugins/
│   ├── api.ts             ← $api: typed $fetch with auth headers + 401 redirect
│   ├── auth.client.ts     ← Bootstraps currentUser on app load
│   ├── socket.client.ts   ← Provides $socket with room helpers
│   └── phosphor-icons.ts  ← Registers icon components globally
├── types/
│   ├── index.ts           ← Barrel export
│   ├── auth.ts            ← IUser, IUserType, IUserStatus
│   ├── resource.ts        ← IResource, IResourceStatus
│   └── form.ts            ← IValidationRule, IApiActionOptions
├── public/
│   └── robots.txt
├── nuxt.config.ts
└── tsconfig.json

nuxt.config.ts Baseline

import tailwindcss from '@tailwindcss/vite'

export default defineNuxtConfig({
  ssr: false,                          // SPA mode
  compatibilityDate: '2025-05-15',

  nitro: {
    preset: 'cloudflare-pages',
    devProxy: { host: '0.0.0.0' },
  },

  vite: {
    plugins: [tailwindcss()],
  },

  css: ['~/assets/css/main.css'],

  modules: [
    'nuxt-phosphor-icons',
    ['@nuxtjs/google-fonts', {
      families: { Geist: true, Inter: [400, 700] },
    }],
  ],

  // All API calls are proxied through Nitro — browser never sees the real API URL
  routeRules: {
    '/api/auth/**':      { proxy: `${process.env.API_CORE}/api/auth/**` },
    '/api/resources/**': { proxy: `${process.env.API_CORE}/api/resources/**` },
    '/api/users/**':     { proxy: `${process.env.API_CORE}/api/users/**` },
  },

  runtimeConfig: {
    public: {
      cookieConfig: {
        maxAge:   60 * 60 * 24 * 7,
        secure:   true,
        sameSite: 'lax' as const,
      },
      socketUrl: '',
    },
  },
})

CSS Setup

DaisyUI v5 is loaded as a Tailwind CSS v4 plugin — no tailwind.config.js needed.

/* app/assets/css/main.css */
@import 'tailwindcss';
@plugin 'daisyui';

Themes are configured via the @plugin directive:

@import 'tailwindcss';
@plugin 'daisyui' {
  themes: light --default, dark --prefersdark;
}

app.vue Structure

Because SSR is disabled, all content is wrapped in <ClientOnly> to prevent hydration mismatches:

<!-- app/app.vue -->
<template>
  <div>
    <ClientOnly>
      <NuxtLoadingIndicator />
      <NuxtLayout>
        <NuxtPage />
      </NuxtLayout>
    </ClientOnly>
  </div>
</template>