tutoriales.com

Navegación Dinámica en Vue Router: Rutas Anidadas y Parámetros Avanzados

Este tutorial te guiará a través de la implementación de rutas anidadas y el manejo de parámetros de ruta avanzados en Vue Router. Aprenderás a construir interfaces de usuario complejas y dinámicas, mejorando la organización de tu código y la experiencia del usuario. Cubriremos desde la configuración básica hasta casos de uso avanzados.

Intermedio20 min de lectura7 views23 de marzo de 2026Reportar error

Introducción a la Navegación Dinámica con Vue Router ✨

Vue Router es el compañero oficial de Vue.js para la gestión de rutas. Permite mapear componentes de Vue a URLs específicas, creando aplicaciones de una sola página (SPA) con navegación fluida y sin recargas de página. En este tutorial, nos sumergiremos en dos características potentes: las rutas anidadas y los parámetros de ruta avanzados, que son fundamentales para construir aplicaciones Vue 3 robustas y escalables.

Las rutas anidadas nos permiten organizar la interfaz de usuario de forma jerárquica, mostrando diferentes sub-componentes dentro de un componente padre, basándose en la URL. Los parámetros de ruta, por otro lado, hacen que nuestras rutas sean dinámicas, permitiéndonos pasar información específica a los componentes, como el ID de un producto o el nombre de un usuario.

¿Por qué son importantes las Rutas Anidadas y los Parámetros? 🤔

Imagina una aplicación de comercio electrónico. Tendrás una página para 'Productos', y dentro de esa página, querrás ver 'Detalles del Producto' para un producto específico. Además, dentro de los detalles, podrías tener pestañas para 'Descripción', 'Reseñas' o 'Especificaciones'. Aquí es donde las rutas anidadas brillan. Los parámetros de ruta te permitirían especificar qué producto mostrar, por ejemplo, /productos/123 donde 123 es el ID del producto.

💡 Consejo: Una buena estructura de rutas mejora la experiencia del desarrollador y del usuario, haciendo la aplicación más intuitiva y fácil de mantener.

🛠️ Configuración Inicial de Vue Router

Antes de sumergirnos en las rutas anidadas y los parámetros, necesitamos tener una configuración básica de Vue Router. Si ya tienes un proyecto Vue 3 con Vue Router instalado, puedes saltarte esta sección.

Instalación de Vue Router

Si aún no tienes Vue Router instalado en tu proyecto Vue 3, puedes hacerlo con npm o yarn:

npm install vue-router@4
# o
yarn add vue-router@4

Creación del Router Básico

Primero, crearemos un archivo router/index.js (o similar) para configurar nuestro enrutador:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

Luego, lo integramos en nuestro archivo main.js:

// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router'; // Importa tu configuración de router

createApp(App).use(router).mount('#app');

Finalmente, en tu App.vue, usarás <router-view> para renderizar el componente correspondiente a la ruta actual y <router-link> para la navegación:

<!-- src/App.vue -->
<template>
  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </nav>
  <router-view />
</template>

Ahora tienes una configuración básica para empezar. ¡Manos a la obra!


🌳 Rutas Anidadas: Organizando tu UI

Las rutas anidadas (o nested routes) permiten que los componentes hijos se rendericen dentro del <router-view> de un componente padre. Esto es ideal para layouts complejos donde diferentes partes de la interfaz de usuario cambian independientemente o se superponen.

Creando un Componente Padre con Rutas Anidadas

Vamos a crear un ejemplo con una sección de Usuarios. Queremos tener una vista principal de Usuarios y, dentro de ella, vistas para Lista de Usuarios y Detalle del Usuario.

Primero, crearemos un componente UserLayout.vue que actuará como el contenedor para nuestras rutas anidadas:

<!-- src/views/UserLayout.vue -->
<template>
  <div class="user-layout">
    <h2>Sección de Usuarios</h2>
    <nav>
      <router-link to="/users">Lista de Usuarios</router-link> |
      <router-link to="/users/123">Usuario #123</router-link>
      <!-- Otros enlaces para sub-rutas -->
    </nav>
    <div class="user-content">
      <router-view /> <!-- Aquí se renderizarán los componentes hijos -->
    </div>
  </div>
</template>

<script>
export default {
  name: 'UserLayout',
};
</script>

<style scoped>
.user-layout {
  padding: 20px;
  border: 1px solid #ccc;
  margin-top: 20px;
}
.user-content {
  margin-top: 15px;
  padding: 15px;
  background-color: #f9f9f9;
  border: 1px dashed #eee;
}
</style>

Definiendo las Rutas Anidadas

Ahora, modificaremos nuestro router/index.js para incluir las rutas anidadas. Necesitamos componentes para la lista de usuarios y para el detalle de un usuario. Creemos UserList.vue y UserDetail.vue:

<!-- src/views/UserList.vue -->
<template>
  <div>
    <h3>Listado de Usuarios</h3>
    <ul>
      <li><router-link to="/users/john-doe">John Doe</router-link></li>
      <li><router-link to="/users/jane-smith">Jane Smith</router-link></li>
      <li><router-link to="/users/123">Usuario con ID 123</router-link></li>
    </ul>
    <p>Selecciona un usuario para ver sus detalles.</p>
  </div>
</template>

<script>
export default {
  name: 'UserList',
};
</script>
<!-- src/views/UserDetail.vue -->
<template>
  <div>
    <h3>Detalles del Usuario: {{ $route.params.id }}</h3>
    <p>Aquí se mostrará la información detallada del usuario.</p>
    <p>ID de usuario actual: <strong>{{ userId }}</strong></p>

    <details open><summary>Más Opciones para Usuario</summary>
      <ul>
        <li><router-link :to="{ name: 'user-profile', params: { id: userId } }">Ver Perfil</router-link></li>
        <li><router-link :to="{ name: 'user-settings', params: { id: userId } }">Ajustes</router-link></li>
      </ul>
      <router-view /> <!-- Para sub-rutas anidadas dentro del detalle -->
    </details>

  </div>
</template>

<script>
import { useRoute } from 'vue-router';
import { computed } from 'vue';

export default {
  name: 'UserDetail',
  setup() {
    const route = useRoute();
    const userId = computed(() => route.params.id);
    return { userId };
  },
};
</script>

Ahora, actualicemos router/index.js:

// src/router/index.js (fragmento relevante)
import UserLayout from '../views/UserLayout.vue';
import UserList from '../views/UserList.vue';
import UserDetail from '../views/UserDetail.vue';

const routes = [
  // ... otras rutas
  {
    path: '/users',
    component: UserLayout, // Componente padre que contiene un <router-view>
    children: [ // Definimos las rutas anidadas aquí
      {
        path: '', // Ruta por defecto para /users (UserList se renderizará en el <router-view> de UserLayout)
        name: 'user-list',
        component: UserList,
      },
      {
        path: ':id',
        name: 'user-detail',
        component: UserDetail,
        props: true, // Pasa los parámetros de ruta como props al componente
        children: [
          // Más anidación: Rutas dentro de UserDetail
          {
            path: 'profile',
            name: 'user-profile',
            component: { template: '<div class="callout note">📌 <strong>Perfil de Usuario:</strong> Mostrando información detallada de {{ $route.params.id }}</div>' },
          },
          {
            path: 'settings',
            name: 'user-settings',
            component: { template: '<div class="callout tip">💡 <strong>Ajustes de Usuario:</strong> Configuración para {{ $route.params.id }}</div>' },
          },
        ],
      },
    ],
  },
];

Con esta configuración:

  • Cuando navegas a /users, se renderiza UserLayout y dentro de su <router-view>, UserList.
  • Cuando navegas a /users/john-doe o /users/123, se renderiza UserLayout, y dentro de su <router-view>, UserDetail. Además, el parámetro id (john-doe o 123) estará disponible en UserDetail.
  • Cuando navegas a /users/123/profile, UserLayout renderiza UserDetail, y UserDetail a su vez renderiza el componente de profile dentro de su propio <router-view>.
🔥 Importante: Las rutas anidadas siempre se definen en la propiedad `children` del objeto de ruta padre. El `path` de las rutas hijas se considera relativo a la ruta padre, a menos que empiece con `/` (lo cual es una práctica menos común para rutas anidadas).
/users (UserLayout) '' (índice) (UserList) :id (UserDetail) profile settings

🧩 Parámetros de Ruta Avanzados

Los parámetros de ruta nos permiten crear rutas dinámicas donde una parte de la URL puede variar. Ya vimos un ejemplo básico con :id. Ahora profundicemos en opciones más avanzadas.

1. Parámetros Obligatorios (:paramName)

Son los más comunes. Si el parámetro no está presente en la URL, la ruta no coincidirá.

Ejemplo: /users/:id donde :id es el parámetro. Puedes acceder a él a través de $route.params.id en el componente o mediante useRoute() en el setup script.

// En UserDetail.vue (o cualquier componente de ruta)
import { useRoute } from 'vue-router';
import { computed } from 'vue';

export default {
  name: 'UserDetail',
  props: ['id'], // Si usas props: true en la configuración de la ruta
  setup(props) { // props contendrá el 'id' si props: true
    const route = useRoute();
    const userIdFromRoute = computed(() => route.params.id);

    // Si usas props: true, puedes usar props.id directamente
    const userIdFromProps = computed(() => props.id);

    return { 
      userIdFromRoute, 
      userIdFromProps 
    };
  },
};
📌 Nota: Usar `props: true` es una buena práctica porque desacopla el componente de la ruta, haciéndolo más fácil de reusar y testear.

2. Parámetros Opcionales (:paramName?)

¿Qué pasa si un parámetro puede estar presente o no? Usa el signo de interrogación ?.

Ejemplo: '/products/:category?'

// src/router/index.js (fragmento)
{
  path: '/products/:category?', // 'category' es opcional
  name: 'products',
  component: () => import('../views/ProductsView.vue'),
},
<!-- src/views/ProductsView.vue -->
<template>
  <div>
    <h2>Página de Productos</h2>
    <p v-if="$route.params.category">
      Mostrando productos en la categoría: <strong>{{ $route.params.category }}</strong>
    </p>
    <p v-else>
      Mostrando todos los productos.
    </p>
    <nav>
      <router-link to="/products">Todos</router-link> |
      <router-link to="/products/electronics">Electrónica</router-link> |
      <router-link to="/products/books">Libros</router-link>
    </nav>
  </div>
</template>

<script>
export default {
  name: 'ProductsView',
};
</script>

Ahora, /products mostrará todos los productos, y /products/electronics mostrará los de esa categoría.

3. Parámetros Repetidos/Múltiples (:paramName+ o :paramName*)

A veces, necesitas que un parámetro coincida con uno o más segmentos de URL, o cero o más. Aquí entran los repetidos (+) y los cero o más (*).

  • +: Coincide con uno o más segmentos. '/files/:paths+' coincidirá con /files/a, /files/a/b, pero no con /files.
  • *: Coincide con cero o más segmentos. '/files/:paths*' coincidirá con /files, /files/a, /files/a/b.

Los valores se obtendrán como un array.

Ejemplo: Para una ruta de descarga de archivos con una estructura de carpetas variable.

// src/router/index.js (fragmento)
{
  path: '/download/:folder+',
  name: 'download-files',
  component: () => import('../views/DownloadView.vue'),
  props: true,
},
<!-- src/views/DownloadView.vue -->
<template>
  <div>
    <h2>Descarga de Archivos</h2>
    <p>Ruta de la carpeta: <code>/{{ folder.join('/') }}</code></p>
    <p>Contenido de la carpeta:</p>
    <ul>
      <li v-for="(item, index) in folder" :key="index">{{ item }}</li>
    </ul>

    <p class="callout tip">💡 <strong>Navegar:</strong>
      <router-link :to="{ name: 'download-files', params: { folder: ['docs', 'reports', 'reporte-final.pdf'] } }">
        /download/docs/reports/reporte-final.pdf
      </router-link>
    </p>
  </div>
</template>

<script>
import { useRoute } from 'vue-router';
import { computed } from 'vue';

export default {
  name: 'DownloadView',
  props: ['folder'], // 'folder' será un array debido a :folder+
  setup(props) {
    // Si no se usa props: true, se accedería vía route.params.folder
    const route = useRoute();
    const folderFromRoute = computed(() => route.params.folder);
    return { folder: props.folder || folderFromRoute }; // Usar props si está disponible
  },
};
</script>
  • Ir a /download/images/logo.png resultará en folder: ['images', 'logo.png'].
  • Ir a /download/documents/invoices/2023/q4/invoice-123.pdf resultará en folder: ['documents', 'invoices', '2023', 'q4', 'invoice-123.pdf'].

4. Parámetros de Expresión Regular (:paramName(regex))

Para un control más fino sobre los parámetros, puedes definir expresiones regulares. Esto es útil para validar el formato de un parámetro directamente en la ruta.

Ejemplo: Un ID de producto que siempre es numérico.

// src/router/index.js (fragmento)
{
  path: '/products/:id(\d+)', // Solo números para 'id'
  name: 'product-numeric-id',
  component: () => import('../views/ProductDetailRegex.vue'),
  props: true,
},
{
  path: '/articles/:slug([a-z0-9-]+)', // Slug con letras minúsculas, números y guiones
  name: 'article-slug',
  component: () => import('../views/ArticleDetailRegex.vue'),
  props: true,
},
<!-- src/views/ProductDetailRegex.vue -->
<template>
  <div>
    <h3>Detalle de Producto (ID Numérico)</h3>
    <p>ID del producto: <mark>{{ id }}</mark></p>
    <div class="callout warning">⚠️ <strong>Advertencia:</strong> Esta ruta solo coincide si el ID es un número.</div>
  </div>
</template>

<script>
export default {
  name: 'ProductDetailRegex',
  props: ['id'],
};
</script>
<!-- src/views/ArticleDetailRegex.vue -->
<template>
  <div>
    <h3>Detalle del Artículo (Slug)</h3>
    <p>Slug del artículo: <mark>{{ slug }}</mark></p>
    <div class="callout note">📌 <strong>Nota:</strong> Esta ruta espera un slug con letras minúsculas, números y guiones.</div>
  </div>
</template>

<script>
export default {
  name: 'ArticleDetailRegex',
  props: ['slug'],
};
</script>
  • /products/123 coincidirá.
  • /products/abc no coincidirá con esta ruta (pero podría coincidir con otra si existe, o llevar a un 404).

5. Parámetros de Petición (Query Parameters) y Hash

Aunque no son parte de la path de la ruta, los query parameters (?key=value&key2=value2) y el hash (#section) son formas comunes de pasar datos adicionales y también son accesibles a través del objeto $route.

  • this.$route.query: Objeto con los parámetros de petición.
  • this.$route.hash: Cadena del hash (incluyendo el #).

Ejemplo: /search?q=vuejs&page=1#results

<!-- src/views/SearchResults.vue -->
<template>
  <div>
    <h2>Resultados de Búsqueda</h2>
    <p>Término de búsqueda: <strong>{{ $route.query.q }}</strong></p>
    <p>Página actual: <strong>{{ $route.query.page || 1 }}</strong></p>
    <p v-if="$route.hash">Sección de Hash: <strong>{{ $route.hash }}</strong></p>

    <nav>
      <router-link :to="{ path: '/search', query: { q: 'vue-router', page: 2 }, hash: '#top' }">
        Buscar Vue Router (Pág. 2)
      </router-link>
    </nav>
  </div>
</template>

<script>
export default {
  name: 'SearchResults',
};
</script>
🔥 Importante: Los *query parameters* no forman parte de la definición de la ruta. Cualquier *query parameter* se puede añadir a cualquier ruta.

🔄 Navegación Programática y Acceso a la Información de Ruta

Vue Router no solo permite la navegación declarativa con <router-link>, sino también la navegación programática usando router.push(), router.replace(), router.go(), etc. Esto es útil para redirigir después de una acción de usuario o una llamada a una API.

Navegación Programática con Parámetros

import { useRouter } from 'vue-router';

export default {
  // ...
  setup() {
    const router = useRouter();

    const goToUserDetail = (id) => {
      router.push({ name: 'user-detail', params: { id: id } });
    };

    const searchProducts = (term, page) => {
      router.push({ path: '/search', query: { q: term, page: page } });
    };

    const goToDownloadPath = (pathSegments) => {
      // pathSegments debe ser un array para :param+
      router.push({ name: 'download-files', params: { folder: pathSegments } });
    };

    return { goToUserDetail, searchProducts, goToDownloadPath };
  },
};

Acceso a la Información de Ruta en Componentes

Como ya hemos visto, puedes acceder al objeto route de varias maneras:

  1. Dentro del setup (Vue 3 Composition API):
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id);
console.log(route.query.q);
  1. Dentro del options API (this.$route):
export default {
mounted() {
console.log(this.$route.params.id);
console.log(this.$route.query.q);
},
};
  1. Mediante props: true: Si la ruta tiene props: true, los parámetros se pasarán directamente como props al componente, simplificando su uso y mejorando la reusabilidad.
export default {
props: ['id', 'category', 'folder'],
// ... puedes usar this.id, this.category, etc.
};
💡 Consejo: Usa `watch` en el objeto `route` (o `route.params` o `route.query`) si necesitas reaccionar a cambios en los parámetros de la ruta sin que el componente se destruya y recree. Esto es común en componentes de lista/detalle.
// Ejemplo de watch en un componente
import { useRoute } from 'vue-router';
import { watch } from 'vue';

export default {
  setup() {
    const route = useRoute();

    watch(
      () => route.params.id, // Observa solo el parámetro 'id'
      (newId, oldId) => {
        console.log(`El ID de usuario cambió de ${oldId} a ${newId}`);
        // Aquí puedes cargar nuevos datos basados en el newId
      }
    );
    
    watch(
      () => route.query.q, // Observa el parámetro de query 'q'
      (newQuery, oldQuery) => {
        console.log(`El término de búsqueda cambió de ${oldQuery} a ${newQuery}`);
        // Realizar nueva búsqueda
      }
    );

    return {};
  },
};

Casos de Uso Avanzados y Mejores Prácticas 🎯

Nombres de Ruta y Mapeo de Parámetros

Siempre es una buena práctica usar nombres de ruta (name: 'my-route') en lugar de rutas absolutas (path: '/my-route') cuando navegas programáticamente o con <router-link>. Esto hace que tu código sea más robusto a cambios en la estructura de URLs.

Tabla: Pros y Contras de Nombres vs Rutas Absolutas

CaracterísticaUso de name (Nombres de Ruta)Uso de path (Rutas Absolutas)
MantenimientoAlto. Si la URL cambia, solo actualizas la definición de ruta.Bajo. Cada vez que la URL cambia, debes actualizar todos los enlaces.
ClaridadAlta. El nombre describe el propósito de la ruta.Media. La URL puede ser menos descriptiva del propósito.
ParámetrosFácil de pasar como objeto params.Requiere concatenar strings para construir la URL.
Query Params/HashFácil de añadir con propiedades query y hash.Requiere concatenar manualmente a la string del path.
Flexibilidad (anidadas)Óptimo. Permite navegar a sub-rutas anidadas con facilidad.Menos flexible, más propenso a errores.

Manejo de Rutas no Encontradas (404) 🌐

Es fundamental tener una ruta catch-all para manejar URLs que no coinciden con ninguna de tus rutas definidas. Se define con un parámetro catch-all que utiliza (.*). Debe ser la última ruta en tu configuración.

// src/router/index.js (al final de la array 'routes')
{
  path: '/:pathMatch(.*)*', // Coincide con cualquier cosa no capturada antes
  name: 'NotFound',
  component: () => import('../views/NotFound.vue'),
},
<!-- src/views/NotFound.vue -->
<template>
  <div class="not-found">
    <h1>404 - Página no encontrada</h1>
    <p>Lo sentimos, la página que buscas no existe.</p>
    <router-link to="/">Ir a la página de inicio</router-link>
  </div>
</template>

<script>
export default {
  name: 'NotFound',
};
</script>

<style scoped>
.not-found {
  text-align: center;
  padding: 50px;
  background-color: #f8f8f8;
  border: 1px solid #eee;
  margin: 30px;
}
h1 {
  color: #d9534f;
}
</style>

Alias de Ruta

Puedes darle alias a una ruta para que la misma URL se comporte como múltiples rutas, o para tener URLs alternativas que mapeen a la misma vista.

{
  path: '/home',
  alias: '/', // '/home' y '/' mostrarán el mismo componente
  name: 'home',
  component: HomeView,
},
{
  path: '/users/:id',
  name: 'user-detail',
  component: UserDetail,
  props: true,
  alias: '/profile/:id', // '/users/123' y '/profile/123' apuntan a UserDetail
},

Redirecciones

Vue Router permite redirecciones de manera sencilla:

{
  path: '/old-path',
  redirect: '/new-path',
},
{
  path: '/legacy-users/:id',
  redirect: { name: 'user-detail' }, // Redirecciona por nombre de ruta
},
// Redirección dinámica
{
  path: '/products/:id',
  redirect: to => {
    // Puedes hacer lógica aquí basada en 'to' (objeto de ruta actual)
    return { path: `/new-products/${to.params.id}/overview` };
  },
},

Transiciones de Ruta

Para una experiencia de usuario más pulida, puedes añadir transiciones a tus <router-view>.

<template>
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>
⚠️ Advertencia: Las transiciones pueden añadir complejidad. Úsalas con moderación y asegúrate de que mejoren la experiencia, no que la ralenticen.

Conclusión 🎉

Has llegado al final de este completo tutorial sobre navegación dinámica en Vue Router. Hemos explorado a fondo las rutas anidadas para construir interfaces de usuario jerárquicas y los parámetros de ruta avanzados (obligatorios, opcionales, repetidos y con expresiones regulares) para crear URLs dinámicas y flexibles. También cubrimos cómo acceder a la información de la ruta, la navegación programática y mejores prácticas como el manejo de 404s y las transiciones.

Dominar estas características de Vue Router te permitirá diseñar aplicaciones Vue 3 complejas con una estructura de navegación sólida y una experiencia de usuario optimizada. Sigue practicando y experimentando con diferentes escenarios para solidificar tus conocimientos.

¡Feliz codificación! 🚀

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!