Nuxt.js Cheatsheet
- El marco intuitivo de la Vue ajustado/h1 "Clase de inscripción" Nuxt.js es un marco libre y de código abierto con una manera intuitiva y extensible de crear aplicaciones web y sitios web de calidad tipo seguro, performante y de producción con Vue.js. ▪/p] ■/div titulada
########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button
■/div titulada ■/div titulada
Cuadro de contenidos
- Instalación
- Creación del proyecto
- Estructura del proyecto
- Routing
- Pagos y diseños
- Components
- Data Fetching
- Administración del Estado
- Styling
- Plugins
- Modules
- Server-Side Rendering
- API Routes
- Deployment
- Testing
- Perfeccionamiento Optimización
- Solucionando
- Las mejores prácticas
Instalación
Prerrequisitos
# Node.js version requirements
node --version # Should be 16.10 or later
# Check npm version
npm --version
# Update npm if needed
npm install -g npm@latest
Crear Nuxt.js App
# Create new Nuxt.js app
npx nuxi@latest init my-app
# Navigate to project
cd my-app
# Install dependencies
npm install
# Start development server
npm run dev
Instalación alternativa Métodos
# Using yarn
yarn create nuxt-app my-app
# Using pnpm
pnpm dlx nuxi@latest init my-app
# Using specific template
npx nuxi@latest init my-app --template ui
npx nuxi@latest init my-app --template content
npx nuxi@latest init my-app --template module
Instalación manual
# Create project directory
mkdir my-nuxt-app
cd my-nuxt-app
# Initialize package.json
npm init -y
# Install Nuxt.js
npm install nuxt
# Install Vue dependencies
npm install vue@latest
# Add scripts to package.json
Paquete.json Scripts
{
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
}
}
Creación de proyectos
Plantillas de arranque
# Official templates
npx nuxi@latest init my-app --template ui # Nuxt UI
npx nuxi@latest init my-app --template content # Nuxt Content
npx nuxi@latest init my-app --template module # Nuxt Module
npx nuxi@latest init my-app --template layer # Nuxt Layer
# Community templates
npx nuxi@latest init my-app --template docus # Documentation
npx nuxi@latest init my-app --template minimal # Minimal setup
Comandos de Desarrollo
# Start development server
npm run dev
# Start with specific port
npm run dev -- --port 3001
# Start with specific host
npm run dev -- --host 0.0.0.0
# Start with HTTPS
npm run dev -- --https
# Start with tunnel
npm run dev -- --tunnel
Construir comandos
# Build for production
npm run build
# Generate static site
npm run generate
# Preview production build
npm run preview
# Analyze bundle
npm run build -- --analyze
Estructura del proyecto
Nuxt 3 Estructura
my-nuxt-app/
├── .nuxt/ # Auto-generated files
├── .output/ # Build output
├── assets/ # Uncompiled assets
├── components/ # Vue components
├── composables/ # Vue composables
├── content/ # Content files (if using @nuxt/content)
├── layouts/ # Layout components
├── middleware/ # Route middleware
├── pages/ # File-based routing
├── plugins/ # Plugins
├── public/ # Static files
├── server/ # Server-side code
│ ├── api/ # API routes
│ └── middleware/ # Server middleware
├── stores/ # Pinia stores
├── utils/ # Utility functions
├── app.vue # Main app component
├── nuxt.config.ts # Nuxt configuration
├── package.json
└── tsconfig.json
Archivos de configuración
// nuxt.config.ts
export default defineNuxtConfig({
// Basic configuration
devtools: { enabled: true },
// CSS framework
css: ['~/assets/css/main.css'],
// Modules
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'@nuxt/content'
],
// Runtime config
runtimeConfig: {
// Private keys (only available on server-side)
apiSecret: '123',
// Public keys (exposed to client-side)
public: {
apiBase: '/api'
}
},
// App configuration
app: {
head: {
title: 'My Nuxt App',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
}
}
});
Routing
Routing basado en archivos
# Route structure
pages/
├── index.vue # / (root)
├── about.vue # /about
├── blog/
│ ├── index.vue # /blog
│ └── [slug].vue # /blog/[slug]
├── users/
│ ├── index.vue # /users
│ ├── [id].vue # /users/[id]
│ └── [id]/
│ └── edit.vue # /users/[id]/edit
└── [...slug].vue # Catch-all route
Rutas dinámicas
<!-- pages/blog/[slug].vue -->
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>
<script setup>
// Get route parameters
const route = useRoute();
const slug = route.params.slug;
// Fetch data based on slug
const { data: post } = await $fetch(`/api/posts/${slug}`);
// Define page meta
definePageMeta({
title: 'Blog Post',
description: 'Read our latest blog post'
});
</script>
Rutas anidadas
<!-- pages/users/[id].vue -->
<template>
<div>
<h1>User Profile</h1>
<NuxtPage />
</div>
</template>
<!-- pages/users/[id]/index.vue -->
<template>
<div>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</template>
<!-- pages/users/[id]/edit.vue -->
<template>
<div>
<h2>Edit User</h2>
<form @submit="updateUser">
<!-- Form fields -->
</form>
</div>
</template>
Navegación
<template>
<nav>
<!-- Static navigation -->
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
<!-- Dynamic navigation -->
<NuxtLink :to="`/blog/${post.slug}`">{{ post.title }}</NuxtLink>
<!-- External links -->
<NuxtLink to="https://nuxtjs.org" external>Nuxt.js</NuxtLink>
<!-- Programmatic navigation -->
<button @click="navigateTo('/dashboard')">Dashboard</button>
<button @click="navigateTo('/login', { replace: true })">Login</button>
</nav>
</template>
<script setup>
// Programmatic navigation
const router = useRouter();
function goToProfile() {
router.push('/profile');
}
function goBack() {
router.back();
}
// Navigation guards
definePageMeta({
middleware: 'auth'
});
</script>
Route Middleware
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useAuthUser();
if (!user.value) {
return navigateTo('/login');
}
});
// middleware/admin.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
// Global middleware runs on every route change
console.log('Navigating to:', to.path);
});
Páginas y diseños
Default Layout
<!-- layouts/default.vue -->
<template>
<div>
<header>
<nav>
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
<NuxtLink to="/blog">Blog</NuxtLink>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2024 My Nuxt App</p>
</footer>
</div>
</template>
<style scoped>
header {
background: #f8f9fa;
padding: 1rem;
}
nav a {
margin-right: 1rem;
text-decoration: none;
}
main {
min-height: 80vh;
padding: 2rem;
}
footer {
background: #343a40;
color: white;
text-align: center;
padding: 1rem;
}
</style>
Diseños personalizados
<!-- layouts/admin.vue -->
<template>
<div class="admin-layout">
<aside class="sidebar">
<nav>
<NuxtLink to="/admin">Dashboard</NuxtLink>
<NuxtLink to="/admin/users">Users</NuxtLink>
<NuxtLink to="/admin/posts">Posts</NuxtLink>
</nav>
</aside>
<main class="content">
<slot />
</main>
</div>
</template>
<style scoped>
.admin-layout {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 250px;
background: #2c3e50;
color: white;
padding: 1rem;
}
.content {
flex: 1;
padding: 2rem;
}
</style>
Usando diseños en páginas
<!-- pages/admin/index.vue -->
<template>
<div>
<h1>Admin Dashboard</h1>
<div class="stats">
<div class="stat">
<h3>Users</h3>
<p>{{ stats.users }}</p>
</div>
<div class="stat">
<h3>Posts</h3>
<p>{{ stats.posts }}</p>
</div>
</div>
</div>
</template>
<script setup>
// Use custom layout
definePageMeta({
layout: 'admin',
middleware: 'admin-auth'
});
// Fetch dashboard stats
const { data: stats } = await $fetch('/api/admin/stats');
</script>
Página Meta y SEO
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
</div>
</template>
<script setup>
const route = useRoute();
const { data: post } = await $fetch(`/api/posts/${route.params.slug}`);
// Dynamic page meta
useHead({
title: post.title,
meta: [
{ name: 'description', content: post.excerpt },
{ name: 'keywords', content: post.tags.join(', ') },
{ property: 'og:title', content: post.title },
{ property: 'og:description', content: post.excerpt },
{ property: 'og:image', content: post.image },
{ property: 'og:url', content: `https://example.com/blog/${post.slug}` }
]
});
// Structured data
useJsonld({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
author: {
'@type': 'Person',
name: post.author.name
},
datePublished: post.publishedAt
});
</script>
Componentes
Componentes autoimportados
<!-- components/TheHeader.vue -->
<template>
<header class="header">
<div class="container">
<NuxtLink to="/" class="logo">
<img src="/logo.png" alt="Logo" />
</NuxtLink>
<nav class="nav">
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
<NuxtLink to="/blog">Blog</NuxtLink>
</nav>
</div>
</header>
</template>
<script setup>
// Component is auto-imported and can be used as <TheHeader />
</script>
Componentes anidados
<!-- components/blog/PostCard.vue -->
<template>
<article class="post-card">
<img :src="post.image" :alt="post.title" />
<div class="content">
<h3>{{ post.title }}</h3>
<p>{{ post.excerpt }}</p>
<NuxtLink :to="`/blog/${post.slug}`" class="read-more">
Read More
</NuxtLink>
</div>
</article>
</template>
<script setup>
interface Post {
id: string;
title: string;
excerpt: string;
image: string;
slug: string;
}
defineProps<{
post: Post;
}>();
</script>
<!-- Usage: <BlogPostCard :post="post" /> -->
Composables
// composables/useAuth.ts
export const useAuth = () => {
const user = useState<User | null>('auth.user', () => null);
const login = async (credentials: LoginCredentials) => {
const { data } = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials
});
user.value = data.user;
await navigateTo('/dashboard');
};
const logout = async () => {
await $fetch('/api/auth/logout', { method: 'POST' });
user.value = null;
await navigateTo('/');
};
const isAuthenticated = computed(() => !!user.value);
return {
user: readonly(user),
login,
logout,
isAuthenticated
};
};
// Usage in components
const { user, login, logout, isAuthenticated } = useAuth();
Global State with useState
// composables/useCounter.ts
export const useCounter = () => {
const count = useState('counter', () => 0);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = 0;
return {
count: readonly(count),
increment,
decrement,
reset
};
};
Datos obtenidos
Datos del lado del servidor
<template>
<div>
<h1>Posts</h1>
<div v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</div>
</div>
</template>
<script setup>
// Fetch data on server-side
const { data: posts } = await $fetch('/api/posts');
// Alternative with useFetch
const { data: posts, pending, error } = await useFetch('/api/posts');
// With reactive parameters
const page = ref(1);
const { data: posts } = await useFetch('/api/posts', {
query: { page }
});
</script>
Datos del lado del cliente
<template>
<div>
<button @click="loadPosts">Load Posts</button>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
<div v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
</div>
</div>
</div>
</template>
<script setup>
const posts = ref([]);
const pending = ref(false);
const error = ref(null);
const loadPosts = async () => {
pending.value = true;
error.value = null;
try {
posts.value = await $fetch('/api/posts');
} catch (err) {
error.value = err.message;
} finally {
pending.value = false;
}
};
</script>
Obtención de datos avanzados
<script setup>
// Lazy loading
const { data: posts, pending } = await useLazyFetch('/api/posts');
// With caching
const { data: user } = await useFetch(`/api/users/${userId}`, {
key: `user-${userId}`,
server: true
});
// With transformation
const { data: posts } = await useFetch('/api/posts', {
transform: (data: any[]) => data.map(post => ({
...post,
formattedDate: new Date(post.createdAt).toLocaleDateString()
}))
});
// With error handling
const { data: posts, error } = await useFetch('/api/posts', {
onResponseError({ request, response, options }) {
console.error('Response error:', response.status);
},
onRequestError({ request, options, error }) {
console.error('Request error:', error);
}
});
// Refresh data
const { data: posts, refresh } = await useFetch('/api/posts');
// Refresh manually
await refresh();
</script>
Data Fetching with Pinia
// stores/posts.ts
export const usePostsStore = defineStore('posts', () => {
const posts = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchPosts = async () => {
loading.value = true;
error.value = null;
try {
const data = await $fetch('/api/posts');
posts.value = data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
const addPost = async (post: CreatePostData) => {
const newPost = await $fetch('/api/posts', {
method: 'POST',
body: post
});
posts.value.unshift(newPost);
};
return {
posts: readonly(posts),
loading: readonly(loading),
error: readonly(error),
fetchPosts,
addPost
};
});
State Management
Pinia Setup
# Install Pinia
npm install pinia @pinia/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
});
Pinia Store
// stores/auth.ts
export const useAuthStore = defineStore('auth', () => {
// State
const user = ref<User | null>(null);
const token = ref<string | null>(null);
// Getters
const isAuthenticated = computed(() => !!user.value);
const isAdmin = computed(() => user.value?.role === 'admin');
// Actions
const login = async (credentials: LoginCredentials) => {
try {
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials
});
user.value = response.user;
token.value = response.token;
// Store in cookie
const tokenCookie = useCookie('auth-token');
tokenCookie.value = response.token;
return response;
} catch (error) {
throw error;
}
};
const logout = async () => {
try {
await $fetch('/api/auth/logout', { method: 'POST' });
} finally {
user.value = null;
token.value = null;
const tokenCookie = useCookie('auth-token');
tokenCookie.value = null;
}
};
const fetchUser = async () => {
try {
const userData = await $fetch('/api/auth/me');
user.value = userData;
} catch (error) {
// Token might be invalid
logout();
}
};
return {
user: readonly(user),
token: readonly(token),
isAuthenticated,
isAdmin,
login,
logout,
fetchUser
};
});
Utilizando Tiendas en Componentes
<template>
<div>
<div v-if="isAuthenticated">
<p>Welcome, {{ user.name }}!</p>
<button @click="logout">Logout</button>
</div>
<div v-else>
<form @submit.prevent="handleLogin">
<input v-model="email" type="email" placeholder="Email" required />
<input v-model="password" type="password" placeholder="Password" required />
<button type="submit" :disabled="loading">Login</button>
</form>
</div>
</div>
</template>
<script setup>
const authStore = useAuthStore();
const { user, isAuthenticated } = storeToRefs(authStore);
const email = ref('');
const password = ref('');
const loading = ref(false);
const handleLogin = async () => {
loading.value = true;
try {
await authStore.login({
email: email.value,
password: password.value
});
await navigateTo('/dashboard');
} catch (error) {
console.error('Login failed:', error);
} finally {
loading.value = false;
}
};
const logout = async () => {
await authStore.logout();
await navigateTo('/');
};
</script>
Estado persistente
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
const items = ref([]);
// Persist to localStorage
const persistedItems = useCookie('cart-items', {
default: () => [],
serializer: {
read: (value: string) => {
try {
return JSON.parse(value);
} catch {
return [];
}
},
write: (value: any[]) => JSON.stringify(value)
}
});
// Initialize from persisted data
onMounted(() => {
items.value = persistedItems.value;
});
// Watch for changes and persist
watch(items, (newItems) => {
persistedItems.value = newItems;
}, { deep: true });
const addItem = (product: Product) => {
const existingItem = items.value.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
items.value.push({ ...product, quantity: 1 });
}
};
const removeItem = (productId: string) => {
const index = items.value.findIndex(item => item.id === productId);
if (index > -1) {
items.value.splice(index, 1);
}
};
const total = computed(() => {
return items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0);
});
return {
items: readonly(items),
total,
addItem,
removeItem
};
});
Styling
CSS y SCSS
<template>
<div class="component">
<h1 class="title">Hello World</h1>
<p class="description">This is a Nuxt component</p>
</div>
</template>
<style scoped>
.component {
padding: 2rem;
background: #f8f9fa;
}
.title {
color: #2c3e50;
font-size: 2rem;
margin-bottom: 1rem;
}
.description {
color: #7f8c8d;
line-height: 1.6;
}
</style>
<!-- SCSS support -->
<style lang="scss" scoped>
$primary-color: #3498db;
$secondary-color: #2c3e50;
.component {
padding: 2rem;
.title {
color: $primary-color;
font-size: 2rem;
&:hover {
color: darken($primary-color, 10%);
}
}
.description {
color: $secondary-color;
}
}
</style>
Tailwind CSS
# Install Tailwind CSS
npm install --save-dev @nuxtjs/tailwindcss
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss']
});
<template>
<div class="min-h-screen bg-gray-100">
<header class="bg-white shadow">
<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">
<h1 class="text-xl font-semibold text-gray-900">My App</h1>
</div>
<nav class="flex space-x-8">
<NuxtLink
to="/"
class="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
Home
</NuxtLink>
<NuxtLink
to="/about"
class="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
>
About
</NuxtLink>
</nav>
</div>
</div>
</header>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<slot />
</main>
</div>
</template>
CSS Módulos
<template>
<div :class="$style.container">
<h1 :class="$style.title">CSS Modules</h1>
<p :class="$style.description">Scoped and modular CSS</p>
</div>
</template>
<style module>
.container {
padding: 2rem;
background: #f8f9fa;
}
.title {
color: #2c3e50;
font-size: 2rem;
}
.description {
color: #7f8c8d;
}
</style>
Estilos globales
/* assets/css/main.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* Custom global styles */
body {
font-family: 'Inter', sans-serif;
}
.btn {
@apply px-4 py-2 rounded font-medium transition-colors;
}
.btn-primary {
@apply bg-blue-600 text-white hover:bg-blue-700;
}
.btn-secondary {
@apply bg-gray-600 text-white hover:bg-gray-700;
}
// nuxt.config.ts
export default defineNuxtConfig({
css: ['~/assets/css/main.css']
});
Plugins
Plugins del lado del cliente
// plugins/vue-toastification.client.ts
import Toast, { POSITION } from 'vue-toastification';
import 'vue-toastification/dist/index.css';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast, {
position: POSITION.TOP_RIGHT,
timeout: 3000,
closeOnClick: true,
pauseOnFocusLoss: true,
pauseOnHover: true,
draggable: true,
draggablePercent: 0.6,
showCloseButtonOnHover: false,
hideProgressBar: false,
closeButton: 'button',
icon: true,
rtl: false
});
});
Plugins del lado del servidor
// plugins/api.server.ts
export default defineNuxtPlugin(async (nuxtApp) => {
// Server-side initialization
const config = useRuntimeConfig();
// Setup API client
const api = $fetch.create({
baseURL: config.public.apiBase,
headers: {
'Authorization': `Bearer ${config.apiSecret}`
}
});
// Provide to app
return {
provide: {
api
}
};
});
Plugins universales
// plugins/pinia-persistence.ts
export default defineNuxtPlugin(() => {
// This runs on both client and server
const nuxtApp = useNuxtApp();
// Setup Pinia persistence
if (process.client) {
nuxtApp.$pinia.use(({ store }) => {
// Persist store state
const stored = localStorage.getItem(`pinia-${store.$id}`);
if (stored) {
store.$patch(JSON.parse(stored));
}
store.$subscribe((mutation, state) => {
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state));
});
});
}
});
Plugin con Composables
// plugins/auth.ts
export default defineNuxtPlugin(async () => {
const { $fetch } = useNuxtApp();
const user = useState('auth.user', () => null);
// Check for existing session
if (process.client) {
const token = useCookie('auth-token');
if (token.value) {
try {
const userData = await $fetch('/api/auth/me', {
headers: {
Authorization: `Bearer ${token.value}`
}
});
user.value = userData;
} catch (error) {
// Invalid token, clear it
token.value = null;
}
}
}
return {
provide: {
auth: {
user,
login: async (credentials: any) => {
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: credentials
});
user.value = response.user;
const token = useCookie('auth-token');
token.value = response.token;
return response;
},
logout: () => {
user.value = null;
const token = useCookie('auth-token');
token.value = null;
}
}
}
};
});
```_
## Módulos
### Instalación de módulos
```bash
# Popular Nuxt modules
npm install @nuxtjs/tailwindcss
npm install @nuxt/content
npm install @pinia/nuxt
npm install @nuxtjs/google-fonts
npm install @nuxt/image
npm install @vueuse/nuxt
Configuración del módulo
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/tailwindcss',
'@nuxt/content',
'@pinia/nuxt',
'@nuxtjs/google-fonts',
'@nuxt/image',
'@vueuse/nuxt'
],
// Module-specific configuration
googleFonts: {
families: {
Inter: [400, 500, 600, 700],
'Fira Code': [400, 500]
}
},
content: {
highlight: {
theme: 'github-dark'
}
},
image: {
cloudinary: {
baseURL: 'https://res.cloudinary.com/demo/image/upload/'
}
}
});
Módulo de contenido
<!-- pages/blog/[...slug].vue -->
<template>
<div>
<ContentDoc />
</div>
</template>
<!-- Alternative with custom rendering -->
<template>
<article>
<header>
<h1>{{ data.title }}</h1>
<p>{{ data.description }}</p>
<time>{{ formatDate(data.date) }}</time>
</header>
<ContentRenderer :value="data" />
</article>
</template>
<script setup>
const { data } = await useAsyncData('blog-post', () =>
queryContent('/blog').where({ _path: useRoute().path }).findOne()
);
if (!data.value) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found'
});
}
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
</script>
Módulo de imagen
<template>
<div>
<!-- Optimized images -->
<NuxtImg
src="/hero.jpg"
alt="Hero image"
width="800"
height="600"
loading="lazy"
/>
<!-- Responsive images -->
<NuxtPicture
src="/responsive.jpg"
alt="Responsive image"
:img-attrs="{
class: 'responsive-image',
style: 'display: block; margin: auto;'
}"
/>
<!-- Cloudinary integration -->
<NuxtImg
provider="cloudinary"
src="/sample.jpg"
width="400"
height="300"
:modifiers="{ quality: 'auto', format: 'auto' }"
/>
</div>
</template>
Server-Side Rendering
SSR Configuración
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true, // Enable SSR (default)
// Nitro configuration
nitro: {
prerender: {
routes: ['/sitemap.xml', '/robots.txt']
}
},
// Route rules
routeRules: {
// Homepage pre-rendered at build time
'/': { prerender: true },
// Product page generated on-demand, revalidates in background
'/products/**': { isr: true },
// Blog post cached for 1 hour
'/blog/**': { isr: 3600 },
// Admin dashboard renders only on client-side
'/admin/**': { ssr: false },
// API routes
'/api/**': { cors: true }
}
});
```_
### Rendering híbrido
```vue
<!-- pages/products/[id].vue -->
<template>
<div>
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>
<p>Price: ${{ product.price }}</p>
<!-- Client-side only component -->
<ClientOnly>
<ProductReviews :product-id="product.id" />
<template #fallback>
<div>Loading reviews...</div>
</template>
</ClientOnly>
</div>
</template>
<script setup>
// This runs on server for SSR
const route = useRoute();
const { data: product } = await $fetch(`/api/products/${route.params.id}`);
// Define route rules
defineRouteRules({
isr: 3600 // Regenerate every hour
});
</script>
Generación de sitios estáticos
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
prerender: {
routes: [
'/',
'/about',
'/contact'
]
}
}
});
# Generate static site
npm run generate
# Preview generated site
npm run preview
ISR (Regeneración Estatica Incremental)
<script setup>
// Enable ISR with 1 hour revalidation
defineRouteRules({
isr: 3600
});
// Fetch data that will be cached
const { data: posts } = await $fetch('/api/posts');
</script>
API Routes
Rutas básicas de API
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
// Get query parameters
const query = getQuery(event);
| const page = parseInt(query.page as string) | | 1; |
| const limit = parseInt(query.limit as string) | | 10; |
// Fetch users from database
const users = await getUsersFromDB({ page, limit });
return {
users,
pagination: {
page,
limit,
total: users.length
}
};
});
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
// Get request body
const body = await readBody(event);
// Validate input
| if (!body.email | | !body.name) { |
throw createError({
statusCode: 400,
statusMessage: 'Email and name are required'
});
}
// Create user
const user = await createUser(body);
// Set response status
setResponseStatus(event, 201);
return user;
});
Rutas dinámicas de API
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'User ID is required'
});
}
const user = await getUserById(id);
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'User not found'
});
}
return user;
});
// server/api/users/[id].put.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const body = await readBody(event);
const updatedUser = await updateUser(id, body);
return updatedUser;
});
// server/api/users/[id].delete.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
await deleteUser(id);
setResponseStatus(event, 204);
return null;
});
Middleware
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Only apply to API routes
if (!event.node.req.url?.startsWith('/api/')) {
return;
}
// Skip auth for public routes
const publicRoutes = ['/api/auth/login', '/api/auth/register'];
if (publicRoutes.includes(event.node.req.url)) {
return;
}
// Check authorization header
const authorization = getHeader(event, 'authorization');
if (!authorization) {
throw createError({
statusCode: 401,
statusMessage: 'Authorization header required'
});
}
const token = authorization.replace('Bearer ', '');
try {
const user = await verifyToken(token);
event.context.user = user;
} catch (error) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid token'
});
}
});
Cargo de archivos
// server/api/upload.post.ts
import { writeFile } from 'fs/promises';
import { join } from 'path';
export default defineEventHandler(async (event) => {
const form = await readMultipartFormData(event);
if (!form) {
throw createError({
statusCode: 400,
statusMessage: 'No file uploaded'
});
}
const file = form.find(item => item.name === 'file');
if (!file) {
throw createError({
statusCode: 400,
statusMessage: 'File field is required'
});
}
// Generate unique filename
const filename = `${Date.now()}-${file.filename}`;
const filepath = join(process.cwd(), 'public/uploads', filename);
// Save file
await writeFile(filepath, file.data);
return {
filename,
url: `/uploads/${filename}`,
size: file.data.length
};
});
Despliegue
Despliegue de Vercel
# Install Vercel CLI
npm install -g vercel
# Deploy to Vercel
vercel
# Deploy to production
vercel --prod
// vercel.json
{
"framework": "nuxtjs",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install"
}
Netlify Deployment
# netlify.toml
[build]
command = "npm run build"
publish = ".output/public"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[build.environment]
NODE_VERSION = "18"
Docker Deployment
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY . .
# Build application
RUN npm run build
# Expose port
EXPOSE 3000
# Start application
CMD ["node", ".output/server/index.mjs"]
Hosting estático
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/sitemap.xml']
}
},
// For static hosting
ssr: false, // Disable SSR for SPA mode
// Or use static generation
nitro: {
prerender: {
crawlLinks: true
}
}
});
Medio ambiente
# .env
NUXT_SECRET_KEY=your-secret-key
NUXT_PUBLIC_API_URL=https://api.example.com
# .env.production
NUXT_SECRET_KEY=production-secret-key
NUXT_PUBLIC_API_URL=https://api.production.com
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// Private keys (only available on server-side)
secretKey: process.env.NUXT_SECRET_KEY,
// Public keys (exposed to client-side)
public: {
apiUrl: process.env.NUXT_PUBLIC_API_URL
}
}
});
Pruebas
Configuración de protestas
# Install testing dependencies
npm install --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
test: {
environment: 'happy-dom'
}
});
Pruebas de componentes
// tests/components/Button.test.ts
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import Button from '~/components/Button.vue';
describe('Button', () => {
it('renders properly', () => {
const wrapper = mount(Button, {
props: { text: 'Hello world' }
});
expect(wrapper.text()).toContain('Hello world');
});
it('emits click event', async () => {
const wrapper = mount(Button);
await wrapper.trigger('click');
expect(wrapper.emitted()).toHaveProperty('click');
});
});
```_
### E2E Pruebas con Playwright
```bash
# Install Playwright
npm install --save-dev @playwright/test
npx playwright install
// tests/e2e/home.spec.ts
import { test, expect } from '@playwright/test';
test('homepage', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Nuxt/);
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
Optimización del rendimiento
Bundle Analysis
# Analyze bundle
npm run build -- --analyze
# Or use webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
Código de división
<script setup>
// Lazy load components
const LazyComponent = defineAsyncComponent(() => import('~/components/Heavy.vue'));
// Lazy load pages
definePageMeta({
mode: 'spa' // Client-side only
});
</script>
Optimización de imagen
<template>
<div>
<!-- Optimized images -->
<NuxtImg
src="/hero.jpg"
alt="Hero"
width="800"
height="600"
loading="lazy"
placeholder
/>
<!-- WebP format -->
<NuxtImg
src="/image.jpg"
alt="Image"
format="webp"
quality="80"
/>
</div>
</template>
Estrategias de producción
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Cache homepage for 1 hour
'/': { isr: 3600 },
// Cache blog posts for 1 day
'/blog/**': { isr: 86400 },
// Prerender at build time
'/about': { prerender: true }
}
});
Solución de problemas
Cuestiones comunes
Hidration Mismatch
<template>
<div>
<!-- Use ClientOnly for client-specific content -->
<ClientOnly>
<div>{{ new Date().toLocaleString() }}</div>
<template #fallback>
<div>Loading...</div>
</template>
</ClientOnly>
</div>
</template>
Memory Leaks
<script setup>
// Proper cleanup
const interval = setInterval(() => {
console.log('tick');
}, 1000);
onUnmounted(() => {
clearInterval(interval);
});
</script>
Errores de construcción
# Clear Nuxt cache
rm -rf .nuxt .output
# Clear node_modules
rm -rf node_modules package-lock.json
npm install
# Check for TypeScript errors
npx nuxi typecheck
Buenas prácticas
Estructura del proyecto
# Recommended structure
components/
├── ui/ # Basic UI components
├── forms/ # Form components
└── layout/ # Layout components
composables/
├── useAuth.ts # Authentication
├── useApi.ts # API calls
└── useState.ts # State management
utils/
├── validation.ts # Validation helpers
├── formatting.ts # Formatting utilities
└── constants.ts # App constants
Ejecución
<script setup>
// Use computed for expensive operations
const expensiveValue = computed(() => {
return heavyCalculation(props.data);
});
// Use lazy loading for heavy components
const HeavyChart = defineAsyncComponent(() => import('~/components/HeavyChart.vue'));
// Optimize images
</script>
<template>
<div>
<NuxtImg
src="/image.jpg"
alt="Image"
loading="lazy"
placeholder
/>
</div>
</template>
SEO
<script setup>
// Dynamic meta tags
const route = useRoute();
const { data: post } = await $fetch(`/api/posts/${route.params.slug}`);
useSeoMeta({
title: post.title,
ogTitle: post.title,
description: post.excerpt,
ogDescription: post.excerpt,
ogImage: post.image,
twitterCard: 'summary_large_image'
});
</script>
-...
Resumen
Nuxt.js es un poderoso marco Vue.js que proporciona una solución completa para la construcción de aplicaciones web modernas. Las características principales incluyen:
- Rotación a base de archivos: Enrutamiento automático basado en la estructura de archivos
- Server-Side Rendering: SSR incorporada con opciones de renderización híbrida
- importaciones automáticas: Componente automático e importaciones componibles
- Data Fetching: Datos potentes con caché y reactividad
- Modules: El rico ecosistema de módulos para la funcionalidad ampliada
- Performance: Optimizaciones incorporadas y división de código
Para obtener resultados óptimos, apalanque la reproducción del lado del servidor para un mejor SEO, utilice composables para una lógica reutilizable, implemente estrategias de caché adecuadas, y siga las convenciones Nuxt.js para routing y búsqueda de datos.
" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }
función generaPDF() { ventana.print(); } ■/script título