This website requires JavaScript.
Nuxt 3 Full-Stack Template
assets/css/main.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

:root {
  /* Light theme */
  --color-background: #ffffff;
  --color-background-soft: #f8fafc;
  --color-background-mute: #f1f5f9;
  --color-border: #e2e8f0;
  --color-border-hover: #cbd5e1;
  --color-heading: #1e293b;
  --color-text: #475569;
  --color-text-soft: #64748b;
  --color-primary: #3b82f6;
  --color-primary-hover: #2563eb;
  --color-primary-soft: #dbeafe;
  --color-secondary: #6366f1;
  --color-success: #10b981;
  --color-warning: #f59e0b;
  --color-error: #ef4444;
}

.dark {
  --color-background: #0f172a;
  --color-background-soft: #1e293b;
  --color-background-mute: #334155;
  --color-border: #475569;
  --color-border-hover: #64748b;
  --color-heading: #f1f5f9;
  --color-text: #cbd5e1;
  --color-text-soft: #94a3b8;
  --color-primary: #60a5fa;
  --color-primary-hover: #3b82f6;
  --color-primary-soft: #1e3a8a;
  --color-secondary: #818cf8;
  --color-success: #34d399;
  --color-warning: #fbbf24;
  --color-error: #f87171;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  line-height: 1.6;
  color: var(--color-text);
  background: var(--color-background);
  transition: color 0.3s ease, background-color 0.3s ease;
}

/* Custom scrollbar */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: var(--color-background-soft);
}

::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--color-border-hover);
}

/* Smooth scrolling */
html {
  scroll-behavior: smooth;
}

/* Focus styles */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Loading spinner */
.spinner {
  @apply inline-block w-4 h-4 border-2 border-current border-r-transparent rounded-full animate-spin;
}
app.vue
<template>
  <Html :lang="locale" :class="{ dark: $colorMode.value === 'dark' }">
    <Head>
      <Title>{{ title }}</Title>
      <Meta name="description" :content="description" />
      <Meta name="viewport" content="width=device-width, initial-scale=1" />
      <Link rel="icon" type="image/x-icon" href="/favicon.ico" />
    </Head>
    <Body class="antialiased">
      <div id="app" class="min-h-screen bg-background text-text">
        <AppHeader />

        <main class="flex-1">
          <NuxtPage />
        </main>

        <AppFooter />

        <!-- Global modals and overlays -->
        <LazyUNotifications />
        <UModals />
      </div>
    </Body>
  </Html>
</template>

<script setup lang="ts">
const { locale } = useI18n()
const colorMode = useColorMode()

// SEO
const title = 'Nuxt 3 App'
const description = 'A modern, full-stack Nuxt 3 application with TypeScript and best practices'

// Global error handling
const { $router } = useNuxtApp()

onErrorCaptured((error) => {
  console.error('Global error captured:', error)
  // You can send error to logging service here
  return false
})

// PWA
if (process.client) {
  // Register service worker
  navigator.serviceWorker?.register('/sw.js')
}
</script>
layouts/default.vue
<template>
  <div class="min-h-screen flex flex-col">
    <AppHeader />

    <main class="flex-1 container mx-auto px-4 py-8">
      <slot />
    </main>

    <AppFooter />
  </div>
</template>

<script setup lang="ts">
// Layout-specific logic can go here
</script>
layouts/auth.vue
<template>
  <div class="min-h-screen flex items-center justify-center bg-background-soft">
    <div class="w-full max-w-md">
      <slot />
    </div>
  </div>
</template>

<script setup lang="ts">
// Auth layout for login/register pages
</script>
components/AppHeader.vue
<template>
  <header class="sticky top-0 z-50 bg-background/80 backdrop-blur-sm border-b border-border">
    <nav class="container mx-auto px-4 py-4">
      <div class="flex items-center justify-between">
        <!-- Logo -->
        <NuxtLink to="/" class="flex items-center space-x-2">
          <Icon name="lucide:zap" class="w-8 h-8 text-primary" />
          <span class="text-xl font-bold text-heading">Nuxt App</span>
        </NuxtLink>

        <!-- Desktop Navigation -->
        <div class="hidden md:flex items-center space-x-6">
          <NuxtLink
            v-for="item in navigation"
            :key="item.to"
            :to="item.to"
            class="text-text hover:text-primary transition-colors duration-200"
            active-class="text-primary font-semibold"
          >
            {{ item.label }}
          </NuxtLink>
        </div>

        <!-- Actions -->
        <div class="flex items-center space-x-3">
          <!-- Theme Toggle -->
          <UButton
            :icon="$colorMode.value === 'dark' ? 'lucide:sun' : 'lucide:moon'"
            color="gray"
            variant="ghost"
            size="sm"
            @click="$colorMode.preference = $colorMode.value === 'dark' ? 'light' : 'dark'"
          />

          <!-- Language Switcher -->
          <UDropdown
            :items="localeItems"
            :popper="{ placement: 'bottom-end' }"
          >
            <UButton
              :label="locale.toUpperCase()"
              trailing-icon="lucide:chevron-down"
              color="gray"
              variant="ghost"
              size="sm"
            />
          </UDropdown>

          <!-- User Menu -->
          <UDropdown
            v-if="user"
            :items="userMenuItems"
            :popper="{ placement: 'bottom-end' }"
          >
            <UAvatar
              :src="user.avatar"
              :alt="user.name"
              size="sm"
              class="cursor-pointer"
            />
          </UDropdown>

          <!-- Auth Buttons -->
          <div v-else class="hidden md:flex items-center space-x-2">
            <UButton to="/auth/login" variant="ghost" size="sm">
              Login
            </UButton>
            <UButton to="/auth/register" size="sm">
              Sign Up
            </UButton>
          </div>

          <!-- Mobile Menu Toggle -->
          <UButton
            :icon="isMenuOpen ? 'lucide:x' : 'lucide:menu'"
            color="gray"
            variant="ghost"
            size="sm"
            class="md:hidden"
            @click="isMenuOpen = !isMenuOpen"
          />
        </div>
      </div>

      <!-- Mobile Menu -->
      <div
        v-if="isMenuOpen"
        class="md:hidden mt-4 pb-4 border-t border-border pt-4 space-y-2"
      >
        <NuxtLink
          v-for="item in navigation"
          :key="item.to"
          :to="item.to"
          class="block py-2 text-text hover:text-primary transition-colors"
          @click="isMenuOpen = false"
        >
          {{ item.label }}
        </NuxtLink>

        <div v-if="!user" class="pt-4 space-y-2">
          <UButton to="/auth/login" variant="ghost" block>
            Login
          </UButton>
          <UButton to="/auth/register" block>
            Sign Up
          </UButton>
        </div>
      </div>
    </nav>
  </header>
</template>

<script setup lang="ts">
const { locale, locales, setLocale } = useI18n()
const colorMode = useColorMode()
const { user, logout } = useAuth()

const isMenuOpen = ref(false)

const navigation = [
  { label: 'Home', to: '/' },
  { label: 'About', to: '/about' },
  { label: 'Blog', to: '/blog' },
  { label: 'Contact', to: '/contact' }
]

const localeItems = computed(() => [
  locales.value.map((locale: any) => ({
    label: locale.name,
    click: () => setLocale(locale.code)
  }))
])

const userMenuItems = computed(() => [
  [{
    label: 'Profile',
    icon: 'lucide:user',
    to: '/profile'
  }, {
    label: 'Settings',
    icon: 'lucide:settings',
    to: '/settings'
  }],
  [{
    label: 'Logout',
    icon: 'lucide:log-out',
    click: logout
  }]
])

// Close mobile menu on route change
watch(() => useRoute().path, () => {
  isMenuOpen.value = false
})
</script>
components/AppFooter.vue
<template>
  <footer class="bg-background-soft border-t border-border">
    <div class="container mx-auto px-4 py-8">
      <div class="grid grid-cols-1 md:grid-cols-4 gap-8">
        <!-- Company Info -->
        <div class="space-y-4">
          <div class="flex items-center space-x-2">
            <Icon name="lucide:zap" class="w-6 h-6 text-primary" />
            <span class="text-lg font-bold text-heading">Nuxt App</span>
          </div>
          <p class="text-text-soft text-sm">
            Building modern web applications with Nuxt 3, TypeScript, and best practices.
          </p>
          <div class="flex space-x-4">
            <UButton
              to="https://github.com"
              target="_blank"
              icon="lucide:github"
              color="gray"
              variant="ghost"
              size="sm"
            />
            <UButton
              to="https://twitter.com"
              target="_blank"
              icon="lucide:twitter"
              color="gray"
              variant="ghost"
              size="sm"
            />
            <UButton
              to="https://linkedin.com"
              target="_blank"
              icon="lucide:linkedin"
              color="gray"
              variant="ghost"
              size="sm"
            />
          </div>
        </div>

        <!-- Quick Links -->
        <div class="space-y-4">
          <h3 class="font-semibold text-heading">Quick Links</h3>
          <ul class="space-y-2">
            <li v-for="link in quickLinks" :key="link.to">
              <NuxtLink
                :to="link.to"
                class="text-text-soft hover:text-primary transition-colors text-sm"
              >
                {{ link.label }}
              </NuxtLink>
            </li>
          </ul>
        </div>

        <!-- Resources -->
        <div class="space-y-4">
          <h3 class="font-semibold text-heading">Resources</h3>
          <ul class="space-y-2">
            <li v-for="resource in resources" :key="resource.to">
              <NuxtLink
                :to="resource.to"
                class="text-text-soft hover:text-primary transition-colors text-sm"
                target="_blank"
              >
                {{ resource.label }}
              </NuxtLink>
            </li>
          </ul>
        </div>

        <!-- Newsletter -->
        <div class="space-y-4">
          <h3 class="font-semibold text-heading">Stay Updated</h3>
          <p class="text-text-soft text-sm">
            Subscribe to our newsletter for the latest updates.
          </p>
          <UForm :state="newsletter" @submit="subscribeNewsletter">
            <div class="flex space-x-2">
              <UInput
                v-model="newsletter.email"
                type="email"
                placeholder="Enter your email"
                size="sm"
                class="flex-1"
              />
              <UButton type="submit" size="sm">
                Subscribe
              </UButton>
            </div>
          </UForm>
        </div>
      </div>

      <!-- Copyright -->
      <div class="mt-8 pt-8 border-t border-border">
        <div class="flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0">
          <p class="text-text-soft text-sm">
            © {{ currentYear }} Nuxt App. All rights reserved.
          </p>
          <div class="flex space-x-6">
            <NuxtLink to="/privacy" class="text-text-soft hover:text-primary text-sm">
              Privacy Policy
            </NuxtLink>
            <NuxtLink to="/terms" class="text-text-soft hover:text-primary text-sm">
              Terms of Service
            </NuxtLink>
          </div>
        </div>
      </div>
    </div>
  </footer>
</template>

<script setup lang="ts">
const currentYear = new Date().getFullYear()

const newsletter = reactive({
  email: ''
})

const quickLinks = [
  { label: 'Home', to: '/' },
  { label: 'About', to: '/about' },
  { label: 'Blog', to: '/blog' },
  { label: 'Contact', to: '/contact' }
]

const resources = [
  { label: 'Documentation', to: 'https://nuxt.com/docs' },
  { label: 'Examples', to: 'https://nuxt.com/examples' },
  { label: 'Community', to: 'https://nuxt.com/community' },
  { label: 'Support', to: 'https://nuxt.com/support' }
]

const subscribeNewsletter = async () => {
  try {
    // Handle newsletter subscription
    const { $fetch } = useNuxtApp()
    await $fetch('/api/newsletter/subscribe', {
      method: 'POST',
      body: { email: newsletter.email }
    })

    const toast = useToast()
    toast.add({
      title: 'Success!',
      description: 'Thank you for subscribing to our newsletter.',
      color: 'green'
    })

    newsletter.email = ''
  } catch (error) {
    const toast = useToast()
    toast.add({
      title: 'Error',
      description: 'Failed to subscribe. Please try again.',
      color: 'red'
    })
  }
}
</script>
pages/index.vue
<template>
  <div>
    <!-- Hero Section -->
    <section class="py-20 text-center">
      <div class="max-w-4xl mx-auto">
        <h1 class="text-5xl md:text-6xl font-bold text-heading mb-6">
          Welcome to
          <span class="text-primary">Nuxt 3</span>
        </h1>
        <p class="text-xl text-text-soft mb-8 max-w-2xl mx-auto">
          A modern, full-stack application template built with Nuxt 3, TypeScript, and Tailwind CSS.
          Perfect for building scalable web applications.
        </p>
        <div class="flex flex-col sm:flex-row gap-4 justify-center">
          <UButton size="lg" to="/about">
            Get Started
          </UButton>
          <UButton size="lg" variant="outline" to="https://nuxt.com" target="_blank">
            Learn Nuxt
          </UButton>
        </div>
      </div>
    </section>

    <!-- Features Section -->
    <section class="py-20 bg-background-soft">
      <div class="container mx-auto px-4">
        <div class="text-center mb-16">
          <h2 class="text-3xl font-bold text-heading mb-4">
            Modern Features
          </h2>
          <p class="text-text-soft max-w-2xl mx-auto">
            Built with the latest technologies and best practices for modern web development.
          </p>
        </div>

        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
          <FeatureCard
            v-for="feature in features"
            :key="feature.title"
            :icon="feature.icon"
            :title="feature.title"
            :description="feature.description"
          />
        </div>
      </div>
    </section>

    <!-- Stats Section -->
    <section class="py-20">
      <div class="container mx-auto px-4">
        <div class="grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
          <div v-for="stat in stats" :key="stat.label">
            <div class="text-3xl font-bold text-primary mb-2">
              {{ stat.value }}
            </div>
            <div class="text-text-soft">
              {{ stat.label }}
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- CTA Section -->
    <section class="py-20 bg-primary text-white">
      <div class="container mx-auto px-4 text-center">
        <h2 class="text-3xl font-bold mb-4">
          Ready to Get Started?
        </h2>
        <p class="text-xl mb-8 opacity-90">
          Start building your next application with our modern template.
        </p>
        <UButton size="lg" color="white" variant="solid" to="/contact">
          Contact Us
        </UButton>
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
// SEO
definePageMeta({
  title: 'Home - Nuxt 3 App',
  description: 'Welcome to our modern Nuxt 3 application template'
})

const features = [
  {
    icon: 'lucide:zap',
    title: 'Lightning Fast',
    description: 'Built with Nuxt 3 for optimal performance and developer experience.'
  },
  {
    icon: 'lucide:shield-check',
    title: 'Type Safe',
    description: 'Full TypeScript support with strict type checking and IntelliSense.'
  },
  {
    icon: 'lucide:palette',
    title: 'Beautiful UI',
    description: 'Styled with Tailwind CSS and Nuxt UI for modern, responsive design.'
  },
  {
    icon: 'lucide:server',
    title: 'Full-Stack',
    description: 'Server-side rendering, API routes, and database integration ready.'
  },
  {
    icon: 'lucide:smartphone',
    title: 'Mobile First',
    description: 'Responsive design that works perfectly on all devices and screen sizes.'
  },
  {
    icon: 'lucide:layers',
    title: 'Modular',
    description: 'Clean architecture with reusable components and composables.'
  }
]

const stats = [
  { value: '100+', label: 'Components' },
  { value: '99%', label: 'Uptime' },
  { value: '50+', label: 'Features' },
  { value: '24/7', label: 'Support' }
]
</script>
pages/about.vue
<template>
  <div class="max-w-4xl mx-auto">
    <div class="text-center mb-12">
      <h1 class="text-4xl font-bold text-heading mb-4">
        About Our Application
      </h1>
      <p class="text-xl text-text-soft">
        Learn more about our modern Nuxt 3 application and the technologies behind it.
      </p>
    </div>

    <div class="prose prose-lg mx-auto">
      <section class="mb-12">
        <h2 class="text-2xl font-semibold text-heading mb-4">Our Mission</h2>
        <p class="text-text leading-relaxed mb-6">
          We believe in building modern, scalable web applications that provide exceptional user experiences.
          Our Nuxt 3 template combines the latest technologies with best practices to help developers
          create production-ready applications quickly and efficiently.
        </p>
      </section>

      <section class="mb-12">
        <h2 class="text-2xl font-semibold text-heading mb-4">Technology Stack</h2>
        <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
          <TechCard
            v-for="tech in technologies"
            :key="tech.name"
            :name="tech.name"
            :description="tech.description"
            :icon="tech.icon"
            :link="tech.link"
          />
        </div>
      </section>

      <section class="mb-12">
        <h2 class="text-2xl font-semibold text-heading mb-4">Key Features</h2>
        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
          <div v-for="feature in keyFeatures" :key="feature" class="flex items-center space-x-2">
            <Icon name="lucide:check-circle" class="w-5 h-5 text-success" />
            <span class="text-text">{{ feature }}</span>
          </div>
        </div>
      </section>
    </div>
  </div>
</template>

<script setup lang="ts">
// SEO
definePageMeta({
  title: 'About - Nuxt 3 App',
  description: 'Learn about our modern Nuxt 3 application and technology stack'
})

const technologies = [
  {
    name: 'Nuxt 3',
    description: 'The intuitive Vue framework for building modern web applications',
    icon: 'simple-icons:nuxtdotjs',
    link: 'https://nuxt.com'
  },
  {
    name: 'TypeScript',
    description: 'Strongly typed programming language that builds on JavaScript',
    icon: 'simple-icons:typescript',
    link: 'https://typescriptlang.org'
  },
  {
    name: 'Tailwind CSS',
    description: 'Utility-first CSS framework for rapid UI development',
    icon: 'simple-icons:tailwindcss',
    link: 'https://tailwindcss.com'
  },
  {
    name: 'Nuxt UI',
    description: 'Beautiful and accessible components built for Nuxt',
    icon: 'simple-icons:nuxtdotjs',
    link: 'https://ui.nuxt.com'
  }
]

const keyFeatures = [
  'Server-Side Rendering (SSR)',
  'Static Site Generation (SSG)',
  'Auto-imports for components and composables',
  'File-based routing system',
  'Built-in SEO optimization',
  'TypeScript support out of the box',
  'Responsive design with Tailwind CSS',
  'Dark mode support',
  'Internationalization (i18n)',
  'Progressive Web App (PWA) ready',
  'API routes and middleware',
  'Database integration ready'
]
</script>
pages/blog/index.vue
<template>
  <div>
    <div class="text-center mb-12">
      <h1 class="text-4xl font-bold text-heading mb-4">
        Blog
      </h1>
      <p class="text-xl text-text-soft">
        Latest articles and insights about web development.
      </p>
    </div>

    <!-- Search and Filters -->
    <div class="mb-8 flex flex-col sm:flex-row gap-4">
      <UInput
        v-model="searchQuery"
        placeholder="Search articles..."
        icon="lucide:search"
        class="flex-1"
      />
      <USelectMenu
        v-model="selectedCategory"
        :options="categories"
        placeholder="All Categories"
        class="w-full sm:w-48"
      />
    </div>

    <!-- Articles Grid -->
    <div v-if="filteredArticles.length" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
      <ArticleCard
        v-for="article in filteredArticles"
        :key="article.slug"
        :article="article"
      />
    </div>

    <!-- Empty State -->
    <div v-else class="text-center py-12">
      <Icon name="lucide:file-text" class="w-16 h-16 text-text-soft mx-auto mb-4" />
      <h3 class="text-xl font-semibold text-heading mb-2">No articles found</h3>
      <p class="text-text-soft">Try adjusting your search or filter criteria.</p>
    </div>

    <!-- Pagination -->
    <div v-if="totalPages > 1" class="mt-12 flex justify-center">
      <UPagination
        v-model="currentPage"
        :page-count="itemsPerPage"
        :total="filteredArticles.length"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
// SEO
definePageMeta({
  title: 'Blog - Nuxt 3 App',
  description: 'Read our latest articles and insights about web development'
})

const searchQuery = ref('')
const selectedCategory = ref('')
const currentPage = ref(1)
const itemsPerPage = 9

// Mock articles data
const articles = ref([
  {
    slug: 'getting-started-nuxt3',
    title: 'Getting Started with Nuxt 3',
    description: 'Learn how to build modern web applications with Nuxt 3',
    category: 'Tutorial',
    author: 'John Doe',
    publishedAt: '2025-01-15',
    image: '/images/blog/nuxt3-intro.jpg',
    readTime: '5 min read'
  },
  {
    slug: 'typescript-best-practices',
    title: 'TypeScript Best Practices',
    description: 'Essential TypeScript patterns for better code quality',
    category: 'Development',
    author: 'Jane Smith',
    publishedAt: '2025-01-10',
    image: '/images/blog/typescript-tips.jpg',
    readTime: '8 min read'
  },
  {
    slug: 'tailwind-css-tips',
    title: 'Advanced Tailwind CSS Techniques',
    description: 'Pro tips for building beautiful UIs with Tailwind CSS',
    category: 'Design',
    author: 'Mike Johnson',
    publishedAt: '2025-01-05',
    image: '/images/blog/tailwind-advanced.jpg',
    readTime: '6 min read'
  }
])

const categories = computed(() => [
  { label: 'All Categories', value: '' },
  ...Array.from(new Set(articles.value.map(a => a.category)))
    .map(category => ({ label: category, value: category }))
])

const filteredArticles = computed(() => {
  let filtered = articles.value

  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    filtered = filtered.filter(article =>
      article.title.toLowerCase().includes(query) ||
      article.description.toLowerCase().includes(query)
    )
  }

  if (selectedCategory.value) {
    filtered = filtered.filter(article => article.category === selectedCategory.value)
  }

  return filtered
})

const totalPages = computed(() => Math.ceil(filteredArticles.value.length / itemsPerPage))
</script>
pages/contact.vue
<template>
  <div class="max-w-2xl mx-auto">
    <div class="text-center mb-12">
      <h1 class="text-4xl font-bold text-heading mb-4">
        Contact Us
      </h1>
      <p class="text-xl text-text-soft">
        Get in touch with our team. We'd love to hear from you.
      </p>
    </div>

    <div class="grid grid-cols-1 lg:grid-cols-2 gap-12">
      <!-- Contact Form -->
      <div>
        <UForm
          ref="form"
          :schema="schema"
          :state="formData"
          @submit="submitForm"
          class="space-y-6"
        >
          <UFormGroup label="Name" name="name" required>
            <UInput
              v-model="formData.name"
              placeholder="Your full name"
              icon="lucide:user"
            />
          </UFormGroup>

          <UFormGroup label="Email" name="email" required>
            <UInput
              v-model="formData.email"
              type="email"
              placeholder="your.email@example.com"
              icon="lucide:mail"
            />
          </UFormGroup>

          <UFormGroup label="Subject" name="subject" required>
            <UInput
              v-model="formData.subject"
              placeholder="What's this about?"
              icon="lucide:message-square"
            />
          </UFormGroup>

          <UFormGroup label="Message" name="message" required>
            <UTextarea
              v-model="formData.message"
              placeholder="Tell us more about your inquiry..."
              :rows="6"
            />
          </UFormGroup>

          <UButton
            type="submit"
            :loading="isSubmitting"
            block
            size="lg"
          >
            Send Message
          </UButton>
        </UForm>
      </div>

      <!-- Contact Information -->
      <div class="space-y-8">
        <div>
          <h3 class="text-xl font-semibold text-heading mb-4">
            Get in Touch
          </h3>
          <p class="text-text-soft mb-6">
            Have questions about our application or need support?
            We're here to help and would love to hear from you.
          </p>
        </div>

        <div class="space-y-4">
          <ContactInfo
            icon="lucide:mail"
            label="Email"
            value="hello@nuxtapp.com"
            link="mailto:hello@nuxtapp.com"
          />
          <ContactInfo
            icon="lucide:phone"
            label="Phone"
            value="+1 (555) 123-4567"
            link="tel:+15551234567"
          />
          <ContactInfo
            icon="lucide:map-pin"
            label="Address"
            value="123 Developer Street, Tech City, TC 12345"
          />
        </div>

        <div>
          <h4 class="font-semibold text-heading mb-3">Follow Us</h4>
          <div class="flex space-x-3">
            <UButton
              to="https://github.com"
              target="_blank"
              icon="lucide:github"
              color="gray"
              variant="ghost"
              size="sm"
            />
            <UButton
              to="https://twitter.com"
              target="_blank"
              icon="lucide:twitter"
              color="gray"
              variant="ghost"
              size="sm"
            />
            <UButton
              to="https://linkedin.com"
              target="_blank"
              icon="lucide:linkedin"
              color="gray"
              variant="ghost"
              size="sm"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { z } from 'zod'

// SEO
definePageMeta({
  title: 'Contact - Nuxt 3 App',
  description: 'Get in touch with our team. We\'d love to hear from you.'
})

const isSubmitting = ref(false)

const schema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Please enter a valid email address'),
  subject: z.string().min(5, 'Subject must be at least 5 characters'),
  message: z.string().min(10, 'Message must be at least 10 characters')
})

const formData = reactive({
  name: '',
  email: '',
  subject: '',
  message: ''
})

const submitForm = async () => {
  isSubmitting.value = true

  try {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))

    const toast = useToast()
    toast.add({
      title: 'Message Sent!',
      description: 'Thank you for your message. We\'ll get back to you soon.',
      color: 'green'
    })

    // Reset form
    Object.assign(formData, {
      name: '',
      email: '',
      subject: '',
      message: ''
    })
  } catch (error) {
    const toast = useToast()
    toast.add({
      title: 'Error',
      description: 'Failed to send message. Please try again.',
      color: 'red'
    })
  } finally {
    isSubmitting.value = false
  }
}
</script>
components/FeatureCard.vue
<template>
  <div class="p-6 bg-background rounded-lg border border-border hover:border-border-hover transition-colors duration-200">
    <div class="mb-4">
      <Icon :name="icon" class="w-8 h-8 text-primary" />
    </div>
    <h3 class="text-lg font-semibold text-heading mb-2">
      {{ title }}
    </h3>
    <p class="text-text-soft">
      {{ description }}
    </p>
  </div>
</template>

<script setup lang="ts">
interface Props {
  icon: string
  title: string
  description: string
}

defineProps<Props>()
</script>
components/TechCard.vue
<template>
  <NuxtLink
    :to="link"
    target="_blank"
    class="p-4 bg-background-soft rounded-lg border border-border hover:border-border-hover transition-colors duration-200 group"
  >
    <div class="flex items-center space-x-3 mb-2">
      <Icon :name="icon" class="w-6 h-6 text-primary" />
      <h3 class="font-semibold text-heading group-hover:text-primary transition-colors">
        {{ name }}
      </h3>
    </div>
    <p class="text-text-soft text-sm">
      {{ description }}
    </p>
  </NuxtLink>
</template>

<script setup lang="ts">
interface Props {
  name: string
  description: string
  icon: string
  link: string
}

defineProps<Props>()
</script>
components/ArticleCard.vue
<template>
  <NuxtLink :to="`/blog/${article.slug}`" class="group">
    <article class="bg-background rounded-lg border border-border overflow-hidden hover:border-border-hover transition-colors duration-200">
      <div class="aspect-video bg-background-mute overflow-hidden">
        <img
          v-if="article.image"
          :src="article.image"
          :alt="article.title"
          class="size-full object-cover group-hover:scale-105 transition-transform duration-200"
        >
        <div v-else class="size-full flex items-center justify-center">
          <Icon name="lucide:image" class="w-12 h-12 text-text-soft" />
        </div>
      </div>

      <div class="p-6">
        <div class="flex items-center justify-between text-sm text-text-soft mb-2">
          <span class="bg-primary/10 text-primary px-2 py-1 rounded-full">
            {{ article.category }}
          </span>
          <span>{{ article.readTime }}</span>
        </div>

        <h3 class="text-lg font-semibold text-heading mb-2 group-hover:text-primary transition-colors">
          {{ article.title }}
        </h3>

        <p class="text-text-soft mb-4 line-clamp-2">
          {{ article.description }}
        </p>

        <div class="flex items-center justify-between text-sm text-text-soft">
          <span>{{ article.author }}</span>
          <time :datetime="article.publishedAt">
            {{ formatDate(article.publishedAt) }}
          </time>
        </div>
      </div>
    </article>
  </NuxtLink>
</template>

<script setup lang="ts">
interface Article {
  slug: string
  title: string
  description: string
  category: string
  author: string
  publishedAt: string
  image?: string
  readTime: string
}

interface Props {
  article: Article
}

defineProps<Props>()

const formatDate = (dateString: string) => {
  return new Date(dateString).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  })
}
</script>
components/ContactInfo.vue
<template>
  <div class="flex items-start space-x-3">
    <Icon :name="icon" class="w-5 h-5 text-primary mt-0.5" />
    <div>
      <div class="font-medium text-heading">{{ label }}</div>
      <NuxtLink
        v-if="link"
        :to="link"
        class="text-text-soft hover:text-primary transition-colors"
      >
        {{ value }}
      </NuxtLink>
      <div v-else class="text-text-soft">
        {{ value }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  icon: string
  label: string
  value: string
  link?: string
}

defineProps<Props>()
</script>
components/ui/LoadingSpinner.vue
<template>
  <div class="flex items-center justify-center" :class="containerClass">
    <div
      class="animate-spin rounded-full border-2 border-current border-r-transparent"
      :class="spinnerClass"
    />
    <span v-if="text" class="ml-3 text-text-soft">{{ text }}</span>
  </div>
</template>

<script setup lang="ts">
interface Props {
  size?: 'sm' | 'md' | 'lg'
  text?: string
  containerClass?: string
}

const props = withDefaults(defineProps<Props>(), {
  size: 'md'
})

const spinnerClass = computed(() => {
  const sizes = {
    sm: 'w-4 h-4',
    md: 'w-6 h-6',
    lg: 'w-8 h-8'
  }
  return sizes[props.size]
})
</script>
composables/useAuth.ts
export const useAuth = () => {
  const user = ref(null)
  const isAuthenticated = computed(() => !!user.value)

  const login = async (credentials: { email: string; password: string }) => {
    try {
      // Simulate API call
      const { data } = await $fetch('/api/auth/login', {
        method: 'POST',
        body: credentials
      })

      user.value = data.user

      // Store token in httpOnly cookie
      const token = useCookie('auth-token', {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        maxAge: 60 * 60 * 24 * 7 // 7 days
      })
      token.value = data.token

      await navigateTo('/')
    } catch (error) {
      throw error
    }
  }

  const register = async (userData: {
    name: string
    email: string
    password: string
  }) => {
    try {
      const { data } = await $fetch('/api/auth/register', {
        method: 'POST',
        body: userData
      })

      user.value = data.user

      const token = useCookie('auth-token')
      token.value = data.token

      await navigateTo('/')
    } catch (error) {
      throw error
    }
  }

  const logout = async () => {
    try {
      await $fetch('/api/auth/logout', { method: 'POST' })
    } catch (error) {
      // Handle error silently
    } finally {
      user.value = null

      const token = useCookie('auth-token')
      token.value = null

      await navigateTo('/auth/login')
    }
  }

  const fetchUser = async () => {
    try {
      const { data } = await $fetch('/api/auth/me')
      user.value = data.user
    } catch (error) {
      // User not authenticated
      user.value = null
    }
  }

  return {
    user: readonly(user),
    isAuthenticated,
    login,
    register,
    logout,
    fetchUser
  }
}
composables/useApi.ts
import type { UseFetchOptions } from 'nuxt/app'

export const useApi = <T>(
  url: string | (() => string),
  options: UseFetchOptions<T> = {}
) => {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$fetch
  })
}

export const useApiLazy = <T>(
  url: string | (() => string),
  options: UseFetchOptions<T> = {}
) => {
  return useLazyFetch(url, {
    ...options,
    $fetch: useNuxtApp().$fetch
  })
}
middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()

  if (!isAuthenticated.value) {
    return navigateTo('/auth/login')
  }
})
middleware/guest.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()

  if (isAuthenticated.value) {
    return navigateTo('/')
  }
})
plugins/auth.client.ts
export default defineNuxtPlugin(async () => {
  const { fetchUser } = useAuth()

  // Fetch user on app initialization
  await fetchUser()
})
server/api/auth/login.post.ts
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event)

  // Validate input
  if (!email || !password) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Email and password are required'
    })
  }

  // In a real app, fetch user from database
  const users = [
    {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com',
      password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi' // password
    }
  ]

  const user = users.find(u => u.email === email)

  if (!user || !await bcrypt.compare(password, user.password)) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid credentials'
    })
  }

  // Generate JWT token
  const token = jwt.sign(
    { userId: user.id },
    process.env.JWT_SECRET || 'your-secret-key',
    { expiresIn: '7d' }
  )

  // Set httpOnly cookie
  setCookie(event, 'auth-token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 24 * 7
  })

  return {
    data: {
      user: {
        id: user.id,
        name: user.name,
        email: user.email
      },
      token
    }
  }
})
server/api/auth/register.post.ts
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

export default defineEventHandler(async (event) => {
  const { name, email, password } = await readBody(event)

  // Validate input
  if (!name || !email || !password) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Name, email and password are required'
    })
  }

  if (password.length < 6) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Password must be at least 6 characters'
    })
  }

  // In a real app, check if user exists and save to database
  const hashedPassword = await bcrypt.hash(password, 10)

  const user = {
    id: Math.floor(Math.random() * 1000) + 1,
    name,
    email,
    password: hashedPassword
  }

  // Generate JWT token
  const token = jwt.sign(
    { userId: user.id },
    process.env.JWT_SECRET || 'your-secret-key',
    { expiresIn: '7d' }
  )

  // Set httpOnly cookie
  setCookie(event, 'auth-token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 24 * 7
  })

  return {
    data: {
      user: {
        id: user.id,
        name: user.name,
        email: user.email
      },
      token
    }
  }
})
server/api/auth/logout.post.ts
export default defineEventHandler(async (event) => {
  // Clear the auth cookie
  setCookie(event, 'auth-token', '', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 0
  })

  return {
    data: {
      message: 'Logged out successfully'
    }
  }
})
server/api/auth/me.get.ts
import jwt from 'jsonwebtoken'

export default defineEventHandler(async (event) => {
  const token = getCookie(event, 'auth-token')

  if (!token) {
    throw createError({
      statusCode: 401,
      statusMessage: 'No token provided'
    })
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') as { userId: number }

    // In a real app, fetch user from database
    const user = {
      id: decoded.userId,
      name: 'John Doe',
      email: 'john@example.com'
    }

    return {
      data: {
        user
      }
    }
  } catch (error) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid token'
    })
  }
})
tsconfig.json
{
  "extends": "./.nuxt/tsconfig.json"
}
````md [README.md]
# Nuxt 3 Minimal Starter

Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.

## Setup

Make sure to install the dependencies:

```bash
# npm
npm install

# pnpm
pnpm install

# yarn
yarn install

# bun
bun install

Start the development server on http://localhost:3000:

# npm
npm run dev

# pnpm
pnpm run dev

# yarn
yarn dev

# bun
bun run dev

Build the application for production:

# npm
npm run build

# pnpm
pnpm run build

# yarn
yarn build

# bun
bun run build

Locally preview production build:

# npm
npm run preview

# pnpm
pnpm run preview

# yarn
yarn preview

# bun
bun run preview

Check out the deployment documentation for more information.

  1. Clone and Install:
git clone <your-repo-url>
cd nuxt3-full-stack-app
npm install
cp .env.example .env
  1. Configure Environment: Edit .env file with your values:
JWT_SECRET=your-super-secret-jwt-key
DATABASE_URL=postgresql://username:password@localhost:5432/database_name
  1. Start Development Server:
npm run dev
# Docker deployment
docker-compose up -d

# Traditional deployment
npm run build
npm start
  • Full-Stack: Complete Nuxt 3 application with server-side API
  • Authentication: JWT-based auth with login/register/logout
  • Database Ready: PostgreSQL integration with user management
  • Modern UI: Tailwind CSS with dark mode support
  • Internationalization: Multi-language support (EN, ES)
  • TypeScript: Full type safety throughout the application
  • Testing: Vitest setup with component and API tests
  • Production Ready: Docker containers and deployment configs
  • SEO Optimized: Meta tags, structured data, and performance
  • Responsive: Mobile-first design with modern UI components

This Nuxt 3 template provides a comprehensive foundation for building modern, full-stack web applications with authentication, internationalization, and production-ready deployment configurations.


```json [package.json]
{
  "name": "nuxt3-full-stack-app",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "typecheck": "nuxt typecheck",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage"
  },
  "dependencies": {
    "@nuxt/ui": "^2.18.7",
    "@nuxtjs/i18n": "^8.8.0",
    "@nuxtjs/color-mode": "^3.5.1",
    "@pinia/nuxt": "^0.5.5",
    "@vueuse/nuxt": "^11.3.0",
    "bcryptjs": "^2.4.3",
    "jsonwebtoken": "^9.0.2",
    "nuxt": "^3.15.1",
    "pinia": "^2.2.8",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@iconify-json/lucide": "^1.2.18",
    "@iconify-json/simple-icons": "^1.2.18",
    "@nuxt/devtools": "^1.7.4",
    "@nuxt/eslint": "^0.8.0",
    "@nuxt/test-utils": "^3.15.1",
    "@types/bcryptjs": "^2.4.6",
    "@types/jsonwebtoken": "^9.0.7",
    "@vue/test-utils": "^2.4.6",
    "eslint": "^9.17.0",
    "happy-dom": "^15.11.6",
    "playwright-core": "^1.49.0",
    "sass": "^1.82.0",
    "typescript": "^5.7.2",
    "vitest": "^2.1.8",
    "vue-tsc": "^2.2.0"
  }
}
```
```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: [
    '@nuxt/ui',
    '@nuxtjs/i18n',
    '@nuxtjs/color-mode',
    '@pinia/nuxt',
    '@vueuse/nuxt',
    '@nuxt/devtools',
    '@nuxt/eslint'
  ],
  devtools: { enabled: true },
  css: ['~/assets/css/main.css'],
  typescript: {
    strict: true,
    typeCheck: true
  },
  runtimeConfig: {
    jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
    databaseUrl: process.env.DATABASE_URL,
    public: {
      apiBase: process.env.API_BASE_URL || '/api',
      appName: 'Nuxt 3 App',
      appVersion: '1.0.0'
    }
  },
  app: {
    head: {
      charset: 'utf-8',
      viewport: 'width=device-width, initial-scale=1',
      title: 'Nuxt 3 Full-Stack App',
      meta: [
        { name: 'description', content: 'A modern, full-stack Nuxt 3 application' },
        { name: 'format-detection', content: 'telephone=no' }
      ]
    }
  },
  ui: {
    global: true,
    icons: ['lucide', 'simple-icons']
  },
  i18n: {
    locales: [
      { code: 'en', name: 'English', file: 'en.json' },
      { code: 'es', name: 'Español', file: 'es.json' }
    ],
    defaultLocale: 'en',
    langDir: 'locales/',
    strategy: 'prefix_except_default'
  }
})
```
```bash [Dockerfile]
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxtjs
COPY --from=builder --chown=nuxtjs:nodejs /app/.output /app/.output
USER nuxtjs
EXPOSE 3000
ENV NUXT_HOST=0.0.0.0
ENV NUXT_PORT=3000
ENV NODE_ENV=production
CMD ["node", ".output/server/index.mjs"]
```
```yaml [docker-compose.yml]
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - JWT_SECRET=your-super-secret-jwt-key
      - DATABASE_URL=postgresql://nuxt_user:nuxt_pass@postgres:5432/nuxt_db
    depends_on:
      - postgres
    networks:
      - app-network

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=nuxt_user
      - POSTGRES_PASSWORD=nuxt_pass
      - POSTGRES_DB=nuxt_db
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:
```
::
Next
React