Nuxt Samples

Nuxt.js full-stack Vue framework examples including SSR, SSG, API routes, and modern Vue development patterns

Key Facts

Category
Web Frameworks
Items
2
Format Families
sample

Sample Overview

Nuxt.js full-stack Vue framework examples including SSR, SSG, API routes, and modern Vue development patterns This sample set belongs to Web Frameworks and can be used to test related workflows inside Elysia Tools.

💻 Basic Nuxt Application typescript

🟢 simple ⭐⭐

Essential Nuxt app structure with pages, components, layouts, and configuration

⏱️ 30 min 🏷️ nuxt, vue, full-stack, ssr
Prerequisites: Vue.js basics, JavaScript/TypeScript, Node.js
// Nuxt 3 Basic Application Examples
// Nuxt is an intuitive full-stack web framework built on top of Vue.js

// 1. Nuxt Configuration (nuxt.config.ts)
const nuxtConfigBasic = defineNuxtConfig({
  devtools: { enabled: true },
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    '@vueuse/nuxt'
  ],
  css: ['~/assets/css/main.css'],
  runtimeConfig: {
    // Private keys (only available on server-side)
    apiSecret: process.env.API_SECRET,
    // Public keys (exposed to client-side)
    public: {
      apiBase: process.env.API_BASE || '/api'
    }
  },
  nitro: {
    experimental: {
      wasm: true
    }
  }
});

// 2. App Entry Point (app.vue)
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

// 3. Default Layout (layouts/default.vue)
<template>
  <div class="min-h-screen bg-gray-50">
    <nav class="bg-white shadow-sm border-b">
      <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex justify-between h-16">
          <div class="flex items-center">
            <NuxtLink to="/" class="text-xl font-bold text-gray-900">
              My App
            </NuxtLink>
          </div>
          <div class="flex items-center space-x-4">
            <NuxtLink to="/posts" class="text-gray-600 hover:text-gray-900">
              Posts
            </NuxtLink>
            <NuxtLink to="/about" class="text-gray-600 hover:text-gray-900">
              About
            </NuxtLink>
          </div>
        </div>
      </div>
    </nav>

    <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
      <slot />
    </main>

    <footer class="bg-white border-t mt-12">
      <div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
        <p class="text-center text-gray-500">
          © 2024 My App. Built with Nuxt 3.
        </p>
      </div>
    </footer>
  </div>
</template>

// 4. Index Page (pages/index.vue)
<template>
  <div class="space-y-6">
    <div class="text-center">
      <h1 class="text-4xl font-bold text-gray-900 mb-4">
        Welcome to Nuxt 3
      </h1>
      <p class="text-xl text-gray-600">
        The Intuitive Vue Framework
      </p>
    </div>

    <div class="grid md:grid-cols-3 gap-6">
      <UCard>
        <template #header>
          <h3 class="text-lg font-semibold">Server-Side Rendering</h3>
        </template>
        <p class="text-gray-600">
          Nuxt provides server-side rendering out of the box for better SEO and performance.
        </p>
      </UCard>

      <UCard>
        <template #header>
          <h3 class="text-lg font-semibold">Auto-imports</h3>
        </template>
        <p class="text-gray-600">
          Vue components, composables, and utilities are automatically imported.
        </p>
      </UCard>

      <UCard>
        <template #header>
          <h3 class="text-lg font-semibold">File-based Routing</h3>
        </template>
        <p class="text-gray-600">
          Create routes automatically based on your file structure.
        </p>
      </UCard>
    </div>

    <div class="text-center">
      <UButton to="/posts" size="xl">
        Get Started
      </UButton>
    </div>
  </div>
</template>

<script setup>
// SEO Meta
useSeoMeta({
  title: 'Welcome to Nuxt 3',
  description: 'Get started with Nuxt 3, the intuitive Vue framework',
  ogTitle: 'Nuxt 3 Starter',
  ogDescription: 'A modern Nuxt 3 application template',
  ogImage: '/og-image.jpg'
})

// Page meta
definePageMeta({
  title: 'Home',
  description: 'Welcome page'
})
</script>

// 5. Dynamic Route Page (pages/posts/[id].vue)
<template>
  <div class="max-w-4xl mx-auto">
    <div v-if="pending" class="text-center py-8">
      <USpinner />
      <p class="mt-2 text-gray-600">Loading post...</p>
    </div>

    <div v-else-if="error" class="text-center py-8">
      <UAlert color="red" icon="i-heroicons-exclamation-triangle">
        <template #title>Error loading post</template>
        <template #description>{{ error.message }}</template>
      </UAlert>
    </div>

    <article v-else-if="data" class="prose lg:prose-xl">
      <header class="mb-8">
        <h1 class="text-4xl font-bold mb-4">{{ data.title }}</h1>
        <div class="flex items-center space-x-4 text-gray-600">
          <span>{{ formatDate(data.createdAt) }}</span>
          <span>•</span>
          <span>{{ data.author }}</span>
        </div>
      </header>

      <div class="prose-content" v-html="data.content"></div>

      <footer class="mt-12 pt-8 border-t">
        <div class="flex items-center justify-between">
          <div class="flex items-center space-x-2">
            <UAvatar :src="data.authorAvatar" :alt="data.author" />
            <span class="font-medium">{{ data.author }}</span>
          </div>
          <div class="flex items-center space-x-4">
            <UButton to="/posts" variant="outline" size="sm">
              ← Back to Posts
            </UButton>
          </div>
        </div>
      </footer>
    </article>
  </div>
</template>

<script setup>
// Route params
const route = useRoute()
const postId = route.params.id

// Fetch post data
const { data, pending, error } = await useFetch(`/api/posts/${postId}`)

// Format date utility
function formatDate(dateString: string) {
  return new Date(dateString).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  })
}

// SEO
if (data.value) {
  useSeoMeta({
    title: data.value.title,
    description: data.value.excerpt,
    ogTitle: data.value.title,
    ogDescription: data.value.excerpt,
    ogImage: data.value.featuredImage
  })
}

// Page meta
definePageMeta({
  title: 'Post Detail',
  description: 'Read our blog post'
})
</script>

// 6. API Route (server/api/posts/[id].get.ts)
const getPostByIdHandler = defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')

  // Mock database - in real app, use Prisma, Drizzle, etc.
  const posts = [
    {
      id: '1',
      title: 'Getting Started with Nuxt 3',
      excerpt: 'Learn the basics of Nuxt 3 and build your first application',
      content: `
        <h2>What is Nuxt 3?</h2>
        <p>Nuxt 3 is a full-stack web framework built on top of Vue.js...</p>
        <h2>Key Features</h2>
        <ul>
          <li>Server-Side Rendering (SSR)</li>
          <li>Static Site Generation (SSG)</li>
          <li>Auto-imports</li>
          <li>File-based routing</li>
        </ul>
      `,
      author: 'John Doe',
      authorAvatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e',
      createdAt: '2024-01-15',
      featuredImage: '/posts/getting-started-nuxt3.jpg'
    },
    {
      id: '2',
      title: 'Vue 3 Composition API Deep Dive',
      excerpt: 'Master the Composition API and build reactive applications',
      content: `
        <h2>Understanding Composition API</h2>
        <p>The Composition API provides a more flexible way to organize logic...</p>
      `,
      author: 'Jane Smith',
      authorAvatar: 'https://images.unsplash.com/photo-1494790108755-2616b332c1ca',
      createdAt: '2024-01-20',
      featuredImage: '/posts/vue3-composition-api.jpg'
    }
  ]

  const post = posts.find(p => p.id === id)

  if (!post) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Post not found'
    })
  }

  return post
})

// 7. Form Handling with Validation (pages/posts/create.vue)
<template>
  <div class="max-w-2xl mx-auto">
    <h1 class="text-3xl font-bold mb-8">Create New Post</h1>

    <UForm :state="form" :validate="validate" @submit="onSubmit" class="space-y-6">
      <UFormGroup label="Title" name="title" required>
        <UInput v-model="form.title" placeholder="Enter post title" />
      </UFormGroup>

      <UFormGroup label="Excerpt" name="excerpt">
        <UTextarea v-model="form.excerpt" :rows="3" placeholder="Brief description" />
      </UFormGroup>

      <UFormGroup label="Content" name="content" required>
        <ClientOnly>
          <TiptapEditor v-model="form.content" />
        </ClientOnly>
      </UFormGroup>

      <UFormGroup label="Author" name="author" required>
        <UInput v-model="form.author" placeholder="Author name" />
      </UFormGroup>

      <UFormGroup label="Featured Image" name="featuredImage">
        <UInput v-model="form.featuredImage" placeholder="Image URL" />
      </UFormGroup>

      <div class="flex justify-end space-x-4">
        <UButton to="/posts" variant="outline">
          Cancel
        </UButton>
        <UButton type="submit" :loading="pending">
          Create Post
        </UButton>
      </div>
    </UForm>
  </div>
</template>

<script setup>
const router = useRouter()
const toast = useToast()

// Form state
const form = reactive({
  title: '',
  excerpt: '',
  content: '',
  author: '',
  featuredImage: ''
})

const pending = ref(false)

// Validation
const validate = (state: typeof form) => {
  const errors = []

  if (!state.title) errors.push({ path: 'title', message: 'Title is required' })
  if (!state.content) errors.push({ path: 'content', message: 'Content is required' })
  if (!state.author) errors.push({ path: 'author', message: 'Author is required' })

  return errors
}

// Submit handler
const onSubmit = async (event: FormSubmitEvent<typeof form>) => {
  pending.value = true

  try {
    await $fetch('/api/posts', {
      method: 'POST',
      body: event.data
    })

    toast.add({
      title: 'Success',
      description: 'Post created successfully',
      color: 'green'
    })

    await router.push('/posts')
  } catch (error) {
    toast.add({
      title: 'Error',
      description: 'Failed to create post',
      color: 'red'
    })
  } finally {
    pending.value = false
  }
}

definePageMeta({
  title: 'Create Post',
  description: 'Create a new blog post'
})
</script>

// 8. Composables (composables/usePosts.ts)
export const usePosts = () => {
  const posts = ref<Post[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  const fetchPosts = async () => {
    loading.value = true
    error.value = null

    try {
      const data = await $fetch<Post[]>('/api/posts')
      posts.value = data
    } catch (err) {
      error.value = 'Failed to fetch posts'
      console.error(err)
    } finally {
      loading.value = false
    }
  }

  const createPost = async (postData: CreatePostData) => {
    try {
      const newPost = await $fetch<Post>('/api/posts', {
        method: 'POST',
        body: postData
      })
      posts.value.unshift(newPost)
      return newPost
    } catch (err) {
      error.value = 'Failed to create post'
      throw err
    }
  }

  return {
    posts: readonly(posts),
    loading: readonly(loading),
    error: readonly(error),
    fetchPosts,
    createPost
  }
}

// 9. Server API (server/api/posts.post.ts)
const createPostHandler = defineEventHandler(async (event) => {
  const body = await readBody(event)

  // Validate input
  const postSchema = z.object({
    title: z.string().min(1),
    excerpt: z.string().optional(),
    content: z.string().min(1),
    author: z.string().min(1),
    featuredImage: z.string().url().optional()
  })

  try {
    const validatedData = postSchema.parse(body)

    // Create post (in real app, save to database)
    const newPost = {
      id: Date.now().toString(),
      ...validatedData,
      createdAt: new Date().toISOString().split('T')[0]
    }

    return newPost
  } catch (err) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Invalid post data'
    })
  }
})

// 10. Plugin (plugins/api.client.ts)
const apiClientPlugin = defineNuxtPlugin(() => {
  const api = $fetch.create({
    baseURL: '/api',
    onRequestError({ error }) {
      console.error('API request error:', error)
    },
    onResponseError({ response }) {
      console.error('API response error:', response.status)
    }
  })

  return {
    provide: {
      api
    }
  }
})

// 11. Middleware (middleware/auth.ts)
const authRouteMiddleware = defineNuxtRouteMiddleware((to, from) => {
  const { $auth } = useNuxtApp()

  if (!$auth.isLoggedIn) {
    return navigateTo('/login')
  }
})

// 12. Types (types/index.ts)
export interface Post {
  id: string
  title: string
  excerpt?: string
  content: string
  author: string
  authorAvatar?: string
  createdAt: string
  featuredImage?: string
}

export interface CreatePostData {
  title: string
  excerpt?: string
  content: string
  author: string
  featuredImage?: string
}

💻 Advanced Nuxt Features typescript

🟡 intermediate ⭐⭐⭐⭐

Complex Nuxt patterns including authentication, caching, SEO optimization, and deployment

⏱️ 50 min 🏷️ nuxt, advanced, full-stack, production
Prerequisites: Nuxt basics, Vue.js, TypeScript, Node.js, Database concepts
// Advanced Nuxt 3 Features

// 1. Authentication with Supabase
// server/api/auth/login.post.ts
const loginHandler = defineEventHandler(async (event) => {
  const { $supabase } = useNuxtApp(event)
  const body = await readBody(event)

  const { data, error } = await $supabase.auth.signInWithPassword({
    email: body.email,
    password: body.password
  })

  if (error) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid credentials'
    })
  }

  // Set session cookie
  setCookie(event, 'auth-token', data.session.access_token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7 // 7 days
  })

  return { user: data.user }
})

// 2. Advanced SEO and Meta Management
// pages/blog/[slug].vue
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/blog/${route.params.slug}`)

// Dynamic meta tags
useSeoMeta({
  title: () => post.value?.title || 'Blog Post',
  description: () => post.value?.excerpt || 'Read our latest blog post',
  ogTitle: () => post.value?.title,
  ogDescription: () => post.value?.excerpt,
  ogImage: () => post.value?.featuredImage || '/og-default.jpg',
  twitterCard: 'summary_large_image',
  twitterTitle: () => post.value?.title,
  twitterDescription: () => post.value?.excerpt,
  twitterImage: () => post.value?.featuredImage || '/og-default.jpg',
  author: () => post.value?.author,
  publishedTime: () => post.value?.createdAt,
  articleSection: 'Technology'
})

// Structured data (JSON-LD)
useHead({
  script: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'BlogPosting',
        headline: post.value?.title,
        description: post.value?.excerpt,
        image: post.value?.featuredImage,
        author: {
          '@type': 'Person',
          name: post.value?.author
        },
        datePublished: post.value?.createdAt,
        dateModified: post.value?.updatedAt
      })
    }
  ]
})
</script>

// 3. Caching Strategies
// server/api/posts/[id].ts
const cachedPostHandler = defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')

  // Try to get from cache first
  const cached = await useStorage('redis').getItem(`post:${id}`)
  if (cached) {
    return cached
  }

  // Fetch from database
  const post = await getPostFromDatabase(id)

  // Cache for 1 hour
  await useStorage('redis').setItem(`post:${id}`, post, {
    ttl: 60 * 60 // 1 hour in seconds
  })

  // Set cache headers
  setHeader(event, 'Cache-Control', 'public, max-age=3600, s-maxage=3600')

  return post
})

// 4. Real-time Updates with Server-Sent Events
// server/api/sse/posts.ts
const postSseHandler = defineEventHandler(async (event) => {
  const stream = new ReadableStream({
    start(controller) {
      const interval = setInterval(async () => {
        const posts = await getLatestPosts()
        controller.enqueue(`data: ${JSON.stringify(posts)}\n\n`)
      }, 1000)

      // Clean up on disconnect
      event.node.req.on('close', () => {
        clearInterval(interval)
        controller.close()
      })
    }
  })

  return stream
})

// 5. Image Optimization and CDN
// composables/useImages.ts
export const useImages = () => {
  const config = useRuntimeConfig()

  const optimizeImage = (src: string, options?: {
    width?: number
    height?: number
    quality?: number
    format?: 'webp' | 'avif' | 'jpg' | 'png'
  }) => {
    const params = new URLSearchParams()

    if (options?.width) params.set('w', options.width.toString())
    if (options?.height) params.set('h', options.height.toString())
    if (options?.quality) params.set('q', options.quality.toString())
    if (options?.format) params.set('f', options.format)

    const queryString = params.toString()
    const baseUrl = config.public.imageCdn || '/images'

    return queryString ? `${baseUrl}/${src}?${queryString}` : `${baseUrl}/${src}`
  }

  const generateSrcSet = (src: string, widths: number[]) => {
    return widths.map(width =>
      `${optimizeImage(src, { width })} ${width}w`
    ).join(', ')
  }

  return {
    optimizeImage,
    generateSrcSet
  }
}

// 6. Advanced State Management with Pinia
// stores/user.ts
export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const loading = ref(false)

  const login = async (credentials: LoginCredentials) => {
    loading.value = true
    try {
      const response = await $fetch('/api/auth/login', {
        method: 'POST',
        body: credentials
      })
      user.value = response.user
    } finally {
      loading.value = false
    }
  }

  const logout = async () => {
    await $fetch('/api/auth/logout', { method: 'POST' })
    user.value = null
    await navigateTo('/login')
  }

  const fetchUser = async () => {
    try {
      const response = await $fetch('/api/auth/me')
      user.value = response.user
    } catch {
      user.value = null
    }
  }

  return {
    user: readonly(user),
    loading: readonly(loading),
    login,
    logout,
    fetchUser
  }
})

// 7. Progressive Web App (PWA) Configuration
// nuxt.config.ts
const nuxtPwaConfig = defineNuxtConfig({
  modules: [
    '@vite-pwa/nuxt',
    '@nuxt/image'
  ],
  pwa: {
    registerType: 'autoUpdate',
    workbox: {
      navigateFallback: '/',
      globPatterns: ['**/*.{js,css,html,png,svg,ico}']
    },
    client: {
      installPrompt: true
    },
    manifest: {
      name: 'My Nuxt App',
      short_name: 'NuxtApp',
      description: 'A modern Nuxt 3 application',
      theme_color: '#ffffff',
      background_color: '#ffffff',
      display: 'standalone',
      orientation: 'portrait',
      scope: '/',
      start_url: '/',
      icons: [
        {
          src: 'pwa-192x192.png',
          sizes: '192x192',
          type: 'image/png'
        },
        {
          src: 'pwa-512x512.png',
          sizes: '512x512',
          type: 'image/png'
        }
      ]
    }
  }
})

// 8. Performance Monitoring
// plugins/analytics.client.ts
const analyticsPlugin = defineNuxtPlugin((nuxtApp) => {
  if (process.env.NODE_ENV === 'production') {
    // Google Analytics
    useHead({
      script: [
        {
          async: true,
          src: `https://www.googletagmanager.com/gtag/js?id=${process.env.GA_ID}`
        },
        {
          innerHTML: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${process.env.GA_ID}');
          `
        }
      ]
    })

    // Sentry for error tracking
    const { $sentry } = nuxtApp.vueApp.config.globalProperties
    if ($sentry) {
      $sentry.init({
        dsn: process.env.SENTRY_DSN,
        environment: process.env.NODE_ENV
      })
    }
  }
})

// 9. Multi-language Support (i18n)
// nuxt.config.ts
const nuxtI18nConfig = defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    locales: [
      {
        code: 'en',
        name: 'English',
        file: 'en.json'
      },
      {
        code: 'es',
        name: 'Español',
        file: 'es.json'
      },
      {
        code: 'fr',
        name: 'Français',
        file: 'fr.json'
      }
    ],
    defaultLocale: 'en',
    langDir: 'locales',
    lazy: true,
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected',
      redirectOn: 'root'
    }
  }
})

// 10. Deployment Configuration
// .env.production
NUXT_PUBLIC_API_BASE=https://api.myapp.com
NUXT_PUBLIC_SITE_URL=https://myapp.com
NUXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NUXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx

// Database
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp

// Authentication
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=xxx

// Redis
REDIS_URL=redis://localhost:6379

// 11. Docker Configuration
// Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:18-alpine AS runner

WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxt

COPY --from=builder /app/.output ./.output
COPY --from=builder /app/package.json ./package.json

USER nuxt

EXPOSE 3000

ENV NUXT_HOST=0.0.0.0
ENV PORT=3000
ENV NODE_ENV=production

CMD ["node", ".output/server/index.mjs"]

// docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

// 12. Testing with Vitest and Playwright
// tests/components/Button.test.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Button from '~/components/Button.vue'

describe('Button', () => {
  it('renders correctly', () => {
    const wrapper = mount(Button, {
      props: { text: 'Click me' }
    })

    expect(wrapper.text()).toContain('Click me')
    expect(wrapper.find('button').exists()).toBe(true)
  })

  it('emits click event', async () => {
    const wrapper = mount(Button, {
      props: { text: 'Click me' }
    })

    await wrapper.find('button').trigger('click')

    expect(wrapper.emitted('click')).toBeTruthy()
  })
})

// tests/e2e/blog.spec.ts
import { test, expect } from '@playwright/test'

test('blog page loads correctly', async ({ page }) => {
  await page.goto('/blog')

  await expect(page.locator('h1')).toContainText('Blog')
  await expect(page.locator('[data-testid="post-list"]').isVisible()).toBe(true()
})

test('can navigate to post detail', async ({ page }) => {
  await page.goto('/blog')

  await page.locator('[data-testid="post-link"]').first().click()

  await expect(page.locator('h1')).toBeVisible()
  await expect(page.locator('[data-testid="post-content"]')).toBeVisible()
})