El desarrollo tradicional de aplicaciones de una sola página (SPA) con Vue.js a menudo plantea desafíos para la optimización de motores de búsqueda (SEO). Nuxt.js es un framework que facilita la creación de aplicaciones con renderizado del lado del servidor (SSR), mejorando significativamente la visibilidad en buscadores.
Instalación inicial
npx create-nuxt-app nombre-del-proyecto
Gestión de recursos estáticos
Directorio assets
Los archivos aquí contenidos son procesados por Webpack. Para usarlos en estilos CSS:
/* En un archivo CSS */
.fondo {
background: url("~assets/imagen-fondo.svg");
}
En componentes Vue:
<img src="~/assets/logo.png">
Directorio static
Contiene archivos que no requieren procesamiento por Webpack. Se acceden mediante ruta relativa:
<img src="../static/imagen-publica.jpg"/>
Sistema de plantillas y páginas
Plantilla por defecto
Archivo layouts/default.vue:
<template>
<div class="contenedor-principal">
<slot/>
</div>
</template>
Plantillas personalizadas
1. Crear archivo layouts/plantilla-especial.vue:
<template>
<div class="estructura-especial">
<header>Encabezado personalizado</header>
<main><slot/></main>
<footer>Pie de página especial</footer>
</div>
</template>
2. Asignar en una página:
// En pages/pagina-especial.vue
export default {
layout: 'plantilla-especial'
}
Manejo de errores
Archivo layouts/error.vue:
<template>
<div class="pagina-error">
<h1 v-if="error.statusCode === 404">Recurso no encontrado</h1>
<h1 v-else>Ha ocurrido un error inesperado</h1>
<nuxt-link to="/">Volver al inicio</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error']
}
</script>
Sistema de plugins
Configuración de plugins globales
1. Crear archivo plugins/mi-plugin.js:
import Vue from 'vue'
Vue.prototype.$funcionGlobal = (mensaje) => {
console.log(`Mensaje del plugin: ${mensaje}`)
}
2. Registrar en nuxt.config.js:
export default {
plugins: ['~/plugins/mi-plugin.js']
}
3. Uso en componentes:
this.$funcionGlobal('Funcionando correctamente')
Inyección en el contexto de la aplicación
Archivo plugins/inyeccion-contexto.js:
export default ({ app }, inject) => {
const funcionUtil = (parametro) => {
return `Procesado: ${parametro}`
}
inject('utilidad', funcionUtil)
}
Configuración de rutas
Nuxt genera automáticamente las rutas basándose en la estructura del directorio pages.
Rutas dinámicas
Estructura de ejemplo:
pages/
├── articulos/
│ ├── _slug.vue
│ └── index.vue
├── usuarios/
│ └── _identificador.vue
└── index.vue
Configuración equivalente de Vue Router:
const rutas = [
{
nombre: 'inicio',
ruta: '/',
componente: 'pages/index.vue'
},
{
nombre: 'usuarios-perfil',
ruta: '/usuarios/:identificador?',
componente: 'pages/usuarios/_identificador.vue'
},
{
nombre: 'articulos-slug',
ruta: '/articulos/:slug',
componente: 'pages/articulos/_slug.vue'
}
]
Validación de parámetros:
// En pages/usuarios/_identificador.vue
export default {
validate({ params }) {
return /^\d+$/.test(params.identificador)
}
}
Middleware de aplicación
Los middlewares se ejecutan antes de la renderización de páginas.
Archivo middleware/registro-accesos.js:
export default function ({ route, req }) {
const datos = {
ruta: route.fullPath,
timestamp: new Date().toISOString(),
userAgent: process.server ? req.headers['user-agent'] : navigator.userAgent
}
// Ejemplo: enviar datos a un servicio de análisis
console.log('Datos de acceso:', datos)
}
Configuración global en nuxt.config.js:
export default {
router: {
middleware: 'registro-accesos'
}
}
O en una página específica:
export default {
middleware: ['registro-accesos']
}
Gestión de estado con Vuex
Configuración modular
Estructura de directorios:
store/
├── carrito/
│ ├── actions.js
│ ├── getters.js
│ ├── mutations.js
│ └── state.js
└── usuario.js
Uso en componentes:
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters({
productos: 'carrito/obtenerProductos',
perfil: 'usuario/perfilCompleto'
})
},
methods: {
...mapActions({
agregarProducto: 'carrito/agregarItem',
actualizarPerfil: 'usuario/cambiarDatos'
})
}
}
Obtención de datos asíncronos
El método asyncData se ejecuta antes de la inicialización del componente en el servidor.
export default {
async asyncData({ params, $axios, error }) {
try {
const articulo = await $axios.$get(`/api/articulos/${params.slug}`)
return { contenido: articulo }
} catch (e) {
error({ statusCode: 404, mensaje: 'Artículo no encontrado' })
}
}
}
Para observar cambios en parámetros de consulta:
export default {
watchQuery: ['pagina', 'orden']
}
Animaciones de transición
Configuración global
/* assets/estilos-transiciones.css */
.pagina-enter-active,
.pagina-leave-active {
transition: opacity 0.3s ease;
}
.pagina-enter-from,
.pagina-leave-to {
opacity: 0;
}
Registrra en nuxt.config.js:
export default {
css: ['~/assets/estilos-transiciones.css']
}
Transiciones específicas por página
/* assets/animaciones.css */
.deslizar-enter-active {
animation: deslizar-entrada 0.4s;
}
.deslizar-leave-active {
animation: deslizar-salida 0.3s;
}
@keyframes deslizar-entrada {
from { transform: translateX(-20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes deslizar-salida {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(20px); opacity: 0; }
}
Aplicación en página:
export default {
transition: {
name: 'deslizar',
mode: 'out-in'
}
}
Indicador de carga personalizado
Crear componente components/IndicadorCarga.vue:
<template>
<div v-if="cargando" class="indicador-carga">
<div class="spinner"></div>
<p>Cargando contenido...</p>
</div>
</template>
<script>
export default {
data: () => ({ cargando: false }),
methods: {
iniciar() { this.cargando = true },
finalizar() { this.cargando = false }
}
}
</script>
Configuración en nuxt.config.js:
export default {
loading: '~/components/IndicadorCarga.vue'
}
Variables de entorno y entorno de ejecución
Detección del entorno de ejecución:
// Determinar si estamos en servidor, cliente o modo estático
const entorno = process.static ? 'generador-estatico' :
(process.server ? 'servidor' : 'cliente')
// Ejemplo de uso condicional
if (process.server) {
// Código exclusivo para servidor
}
if (process.client) {
// Código exclusivo para cliente
}