🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos Nuxt
Exemplos do framework full-stack Vue Nuxt.js incluindo SSR, SSG, rotas API e padrões modernos de desenvolvimento Vue
💻 Aplicação Básica Nuxt typescript
🟢 simple
⭐⭐
Estrutura essencial de aplicação Nuxt com páginas, componentes, layouts e configuração
⏱️ 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)
export default 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)
export default 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)
export default 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)
export default 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)
export default 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
}
💻 Recursos Avançados do Nuxt typescript
🟡 intermediate
⭐⭐⭐⭐
Padrões complexos do Nuxt incluindo autenticação, cache, otimização SEO e implantação
⏱️ 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
export default 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
export default 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
export default 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
export default 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
export default 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
export default 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()
})