Qwik — useContent para crear menús
Claves para crear un menú completo avanzado con Qwik en varios pasos sencillos usando el hook useContent
--
Comenzamos un nuevo artículo en el que vamos a aprender a crear menús avanzados con Qwik mediante el uso de contenido de tipo Markdown junto con el hook useContent()
.
La información de referencia que vamos a utilizar es la que os proporciono a continuación.
Iremos paso a paso, desde la creación de varias rutas sencillas, hasta formar un menú de varios niveles ampliando con nociones para ser capaces de crear menús de n niveles.
Os recomiendo leer los artículos anteriores en orden y si practicáis, ¡¡mucho mejor!!.
Recordad, que si hay dudas, aun leyéndolo, podéis preguntarlo en los comentarios y sin dudas me gustaría que me comentéis también, para tener feedback.
Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:



Si queréis recibir notificaciones sobre este contenido y similares, os animo a que os suscribáis a mi lista de correo:
Solo habrá avisos con nuevos contenidos, es decir, nuevos artículos (1–3 máximo a la semana, generalmente 1).
En este artículo trabajaremos con la versión 1.1.4, que es la versión estable que tenemos en la actualidad (24/05/2023).
Usaremos la plantilla Empty App (Qwik City)
que es una variante de la Empty App (Qwik City) (Demo App with Routing built-in (recommended)
pero sin añadir rutas con páginas de demos.
Seguiríamos los siguientes pasos:
Una vez que tenemos el proyecto creado, podemos empezar a trabajar con ello mediante la creación de rutas y los contenidos asociados a cada ruta para enlazarlos a los menús.
También tenéis opción de empezar desde este punto, con el proyecto creado y listo de empezar a trabajar con el:
https://github.com/Qwik-Spanish/qwik-advanced-menu-from-md/archive/refs/heads/00-start.zip
Con esto ya estaremos en posición para seguir el artículo paso a paso.
Contenido del artículo
- Introducción
- Crear rutas principales de nuestros contenidos
- Crear un menú principal
- Crear un menú de varios niveles
- Crear un menú concreto para usarlo en una ruta
Introducción
Los menús son elementos esenciales en los proyectos web, ya que desempeñan un papel clave en la navegación y la experiencia del usuario. Proporcionan una estructura clara y organizada que guía a los visitantes a través del contenido del sitio.
Un menú bien diseñado facilita la búsqueda y acceso rápido a la información deseada, mejorando la usabilidad y la satisfacción del usuario.
Además, un menú visualmente atractivo contribuye a la estética general del sitio web.
En resumen, los menús son fundamentales para una navegación eficiente y una experiencia positiva en proyectos web y por esa razón, los vamos a trabajar en Qwik, con una funcionalidad super interesante que nos proporciona este Framework.
Antes de empezar a trabajar con los menús, que vamos a usar para acceder a los diferentes contenidos de las diferentes rutas, debemos de crear las carpetas de las rutas y los contenidos que estarán dentro de ellos, que será lo que necesitaremos.
Para saber todos los detalles como se trabaja con el enrutado (Routing), os animo a que accedáis a este artículo completo donde explico todas las claves para trabajar con el enrutamiento de contenidos en Qwik
Vamos a tener 3 rutas cuyos contenidos los vamos a trabajar con el formato Markdown (md) para este ejemplo:
- Inicio /
- Proyectos /projects
- Contacto /contact
Y quedará reflejado de la siguiente manera:
src/
└── routes/
├── contact
| └──index.md
├── projects
| └──index.md
├── layout.tsx
└── index.md
Y sus contenidos de cada uno de ellos será lo siguiente:
Guardamos los cambios en todos los contenidos y navegamos, introduciendo los paths (rutas) asociados a las carpetas identificando el enrutamiento:
Inicio (/)
Proyectos (/projects)
Contacto (/contact)
Llegados a este punto, ya tenemos el contenido de nuestro proyecto listo para trabajar con lo que vamos a ver en este artículo, crear elementos de menú avanzados mediante el hook useContent()
que obtendrá la información del fichero path activado en la ruta asociada.
Recordad, que el contenido del index de cada página se puede renderizar en diferentes formatos y por facilitar el proceso, lo haré en formato Markdown (md).
En este punto habremos implementado este código:
https://github.com/Qwik-Spanish/qwik-advanced-menu-from-md/archive/refs/heads/01-routing-pages.zip
Crear un menú principal
Ahora que ya tenemos las rutas y los contenidos asociados a esas rutas creados, vamos a empezar a crear nuestro menú avanzado con Qwik, usando contenido Markdown.
Los menús nos van a permitir describir la estructura de navegación del sitio de manera declarativa y sencilla.
Formas de trabajar con los menús:
- Aplicando código HTML en el propio contenido de la plantilla
- Usando valores constantes a mano que estarán en un array de elementos con su información (lo habitual) para poder renderizar este contenido en el layout
- Usando contenido de tipo Markdown en el documento con extensión markdown donde definimos los niveles de los menús y su submenús de una manera muy fácil, siguiendo estas normas sencillas para crear el contenido.
Trabajaremos en este caso, con la tercera opción basándonos en esta información que nos proporciona la documentación oficial, trasladando ese contenido a algo más fácil de entender y con ejemplos propios.
Básicamente, lo que vamos a hacer para crear el menú consta de varios pasos:
Crear el fichero del contenido del menú
Lo primero que debemos de hacer es crear un archivo menu.md
que contenga la estructura del menú para el directorio en el que se encuentra, siempre en alguna parte dentro del directorio src/routes
, sea para todas las rutas o rutas individuales.
En este ejemplo, lo haré para todas las rutas, con un nivel o varios. Por lo tanto, para activar este menú EN TODAS LAS RUTAS debemos de añadir ese fichero en src/routes
src/
└── routes/
├── contact
| └──index.md
├── projects
| └──index.md
├── layout.tsx
├── index.md
└── menu.md
Declarando la estructura del menú
Con este nuevo fichero menu.md
vamos a declarar la estructura de nuestro nuevo menú.
- Usaremos los encabezados (
#
,##
, etc.) para definir la profundidad del menú. - Utilizaremos la lista con viñetas (
-
) para definir los elementos del menú que contendrán el enlace interno o externo.
Partiendo de esta base, vamos a crear un menú de un nivel único, por lo que usaremos el encabezado #
con su título (que será el nombre de nuestra marca, path del logo,…) y después definiremos los elementos que van a componer ese menú principal.
Creando el menú de primer nivel
En este caso, como bien sabéis, tenemos las tres rutas de inicio, proyectos y contacto.
Añadimos el contenido de la siguiente forma en base nuestras rutas actuales:
# Menú Principal
- [Inicio](/)
- [Proyectos](/projects)
- [Contacto](/contact)
Al ser un menú de un nivel principal, añadimos un título de este menú, que podría ser el nombre de nuestra página web, el path del logotipo de nuestra marca o lo que queramos destacar. En este caso, añado una referencia orientativa, que no usaremos en este caso.
Añadimos los tres enlaces en formato lista, para poder acceder a las tres rutas que hemos definido anteriormente.
Ahora seguramente os estéis preguntando lo siguiente. ¿Cómo puedo renderizar este contenido como un menú?
Para poder renderizar el contenido, usaremos el hook useContent()
dentro de la plantilla donde se visualizará el contenido de la ruta que estará disponible.
En este caso, como queremos que este menú se vea tanto en la ruta inicial, proyectos y contacto, lo vamos a añadir dentro del fichero src/routes/layout.tsx
, de la siguiente forma:
import { component$, Slot, useTask$ } from '@builder.io/qwik';
import { useContent } from '@builder.io/qwik-city'; // <=== 1.- IMPORTAMOS
export default component$(() => {
// 2.- Añadimos de esta manera para coger solo el contenido de menú
// Dentro de este hook tenemos "headings" que incluye datos sobre los
// elementos de encabezado HTML `<h1>` a `<h6>` de un archivo markdown.
const { menu } = useContent();
useTask$(() => {
// 3.- Contenido del menú
console.log(menu)
})
return <Slot />;
});
Al guardar los cambios y visualizando en el registro de la consola del servidor, deberíamos de ver lo siguiente:
Podemos ver, asociando al fichero donde hemos definido el contenido que text
hace referencia al elemento de encabezado (en este #
) y los items
a los elementos de la lista con el carácter -
¿Podríamos hacer un menú de más niveles? SI. sin problema, eso si, tendríamos que ir anidando los elementos
#
y los elementos-
de la forma adecuada para poder definir bien los niveles. Esto lo veremos en este artículo al terminar este apartado, con un menú de dos niveles que será la base para poder crear menús de más niveles.
Tenemos el contenido disponible, y ahora para poder usarlo en todas las rutas, debemos de añadir el siguiente código dentro de src/routes/layout.tsx
dentro del apartado donde añadimos la información del layout:
<ul class="menu">
{menu &&
menu.items &&
menu.items.map((item) => (
<li>
<a href={item.href}>{item.text}</a>
</li>
))}
</ul>
Y quedará de la siguiente forma, donde vamos a recorrer la propiedad items
dentro del valor menu
que hemos iniciado con el hook useContent()
:
import { component$, Slot } from "@builder.io/qwik";
import { useContent } from "@builder.io/qwik-city";
export default component$(() => {
const { menu } = useContent();
return (
<>
<ul class="menu">
{menu &&
menu.items &&
menu.items.map((item) => (
<li>
<a href={item.href}>{item.text}</a>
</li>
))}
</ul>
<Slot />
</>
);
});
Guardamos y esta es la apariencia actual de nuestro proyecto, donde ya se puede ver el contenido del menú:
El diseño del menú es bastante pobre, por eso, vamos a darle un mínimo (tampoco vamos a ser pros con este artículo) de CSS, para que se vea ya algo más elegante y sea un menú funcional sin que nos sangren los ojos.
Aplicamos los siguientes estilos dentro de src/routes/layout.tsx
. Los estilos que se aplicarán serán (no estoy enseñando CSS, seguro que con vuestras habilidades hacéis cosas más chulas visualmente):
.menu {
list-style: none;
padding: 0;
margin: 0;
}
.menu li {
display: inline-block;
margin-right: 10px;
}
.menu li a {
text-decoration: none;
color: #000;
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.menu li a:hover {
background-color: #f0f0f0;
}
Usando la función useStyles$()
import { component$, Slot, useStyles$ } from "@builder.io/qwik";
import { useContent } from "@builder.io/qwik-city";
export default component$(() => {
useStyles$(`
.menu {
list-style: none;
padding: 0;
margin: 0;
}
.menu li {
display: inline-block;
margin-right: 10px;
}
.menu li a {
text-decoration: none;
color: #000;
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.menu li a:hover {
background-color: #f0f0f0;
}
`)
const { menu } = useContent();
return (
<>
<ul class="menu">
{menu &&
menu.items &&
menu.items.map((item) => (
<li>
<a href={item.href}>{item.text}</a>
</li>
))}
</ul>
<Slot />
</>
);
});
Guardamos, y este será el resultado:
No se ha hecho un cambio brutal, pero por lo menos hemos conseguido eliminar la apariencia de la lista que quedaba bastante mal.
Llegados a este punto, os he enseñado a crear un menú avanzado con Qwik, de un nivel, algo sencillo, pero que es fácil de gestionar haciendo simples cambios en el fichero menu.md
en src/routes
Lo trabajado hasta este punto lo podemos encontrar a continuación:
https://github.com/Qwik-Spanish/qwik-advanced-menu-from-md/archive/refs/heads/02-first-menu-use-content.zip
¿Os apetece profundizar haciendo un menú multinivel con este modo de trabajar? Si es así, seguid en el artículo desde el siguiente punto.
Crear un menú de varios niveles
Una vez que ya hemos creado un nivel sencillo y funcional, vamos a crear un menú de diferentes niveles, en este caso de padre e hijo, es decir, 2 niveles.
Con los conocimientos adquiridos en este apartado, podremos conseguir un menú más completo y mejor organizado y a su vez, aprender las bases para crear menús de n
niveles.
El resultado final que vamos a conseguir será el siguiente:
Este menú constará de opciones sin hijos y con hijos y en los enlaces tendremos la posibilidad de navegar de manera interna (sin abrir nueva pestaña) y de manera externa (abriendo nueva pestaña a un enlace externo).
Lo que vamos a definir es:
- Tres enlaces internos (Inicio, Proyectos y Contacto) (esto es lo que ya tenemos como base) sin hijos, ya con un enlace.
- Dos apartados principales con varias opciones que al hacer click nos llevarán a páginas externas a nuestro proyecto pero también podríamos hacerlo a nuestro proyecto.
Definir la estructura del menú
Teniendo en cuenta esto, lo que tenemos que hacer es crear una estructura similar siguiendo esta jerarquía:
menu.md
└── "Menú principal"
├── "Inicio"
| └── /
├── "Proyectos"
| └── /projects
├── "Qwik Oficial"
| └── "Documentación"
| └── https://qwik.builder.io/docs/
| └── "Playground"
| └── https://qwik.builder.io/examples/
| └── "Curso Gratis"
| └── https://medium.com/@mugan86/list/qwik-paso-a-paso-desde-0-al-detalle-e7df8b471166
| └── "Qwik en Español - Ayuda"
| └── https://github.com/Qwik-Spanish/qwik-i18n-translate-es
├── "Components"
| └── "Inicio"
| └── https://qwik.builder.io/docs/components/overview/
| └── "Tareas y Ciclos de Vida"
| └── https://qwik.builder.io/docs/components/tasks/#tasks
└── "Contacto"
└── /contact
Ahora con esta base, vamos el fichero menu.md
y cambiamos el contenido que tenemos actualmente por el siguiente:
# Menú Principal
## Inicio
- [-](/)
## Proyectos
- [-](/projects)
## Qwik Oficial
- [Documentación](https://qwik.builder.io/docs/)
- [Playground](https://qwik.builder.io/examples/)
- [Curso GRATIS](https://medium.com/@mugan86/list/qwik-paso-a-paso-desde-0-al-detalle-e7df8b471166)
- [Qwik en Español - Ayuda](https://github.com/Qwik-Spanish/qwik-i18n-translate-es)
## Componentes
- [Inicio](https://qwik.builder.io/docs/components/overview/)
- [Tareas y Ciclos de Vida](https://qwik.builder.io/docs/components/tasks/#tasks)
## Contacto
- [-](/contact)
Tenemos las tres opciones SIN HIJOS definidas con un elemento con ##
que en este caso usaremos como primer nivel de profundidad.
En este caso SOLO vamos a tener un enlace y con ello especificamos el enlace y el texto -
, para poderlo identificar como un menú sin hijos.
Luego tenemos las opciones Qwik Oficial y Componentes que TIENEN HIJOS y en este caso, especificamos la etiqueta (sin enlace) con ##
y dentro de estos, ponemos la lista con todos los enlaces, que en este caso serán externos, pero podríamos añadir sin problemas los internos como hemos hecho con Inicio, Proyectos y Contacto.
El contenido en nuestra app será el siguiente:
- 1,2 y 4: dentro de
items
SOLO tienen un elemento. - 3 tienen más de un elemento en
items
.
Con esto, vamos a ir a pintar esta información para llegar a nuestro objetivo final de este apartado.
Visualizar el contenido en nuestra aplicación
Ahora que ya tenemos la información del menú constituido lo que nos queda es plasmarlo en el layout como hemos realizado en el punto anterior en src/routes/layout.tsx
.
Vamos a seguir los siguientes pasos:
Especificar la configuración de estilos CSS
Crear un fichero menu.css
y añadir el siguiente contenido:
.parent {
display: block;
position: relative;
float: left;
line-height: 30px;
background-color: #4FA0D8;
border-right: #CCC 1px solid;
}
.parent a {
margin: 10px;
color: #FFFFFF;
text-decoration: none;
}
.parent:hover>ul {
display: block;
position: absolute;
}
.child {
display: none;
}
.child li {
background-color: #E4EFF7;
line-height: 30px;
border-bottom: #CCC 1px solid;
border-right: #CCC 1px solid;
min-width: 100%;
width: 140%;
}
.child li a {
color: #000000;
}
ul {
list-style: none;
margin: 0;
padding: 0px;
min-width: 10em;
}
ul ul ul {
left: 100%;
top: 0;
margin-left: 1px;
}
li:hover {
background-color: #95B4CA;
}
.parent li:hover {
background-color: #F0F0F0;
}
.expand {
font-size: 12px;
float: right;
margin-right: 5px;
}
Una vez que tenemos los estilos preparados, eliminamos el contenido actual que tenemos dentro de useStyles$()
y añadimos el contenido que hemos añadido recientemente para especificar los estilos.
Modificamos este código anterior:
import { component$, Slot, useStyles$ } from "@builder.io/qwik";
import { useContent } from "@builder.io/qwik-city";
export default component$(() => {
// Eliminaremos todo el contenido dentro de useStyles$
useStyles$(`
.menu {
list-style: none;
padding: 0;
margin: 0;
}
.menu li {
display: inline-block;
margin-right: 10px;
}
.menu li a {
text-decoration: none;
color: #000;
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.menu li a:hover {
background-color: #f0f0f0;
}
`)
const { menu } = useContent();
return (
<>
...
</>
);
});
A lo siguiente, dejando mucho más limpio nuestro fichero src/routes/layout.tsx
:
import { component$, Slot, useStyles$ } from "@builder.io/qwik";
import { useContent } from "@builder.io/qwik-city";
// 1.- Traemos los estilos
import menuStyles from './menu.css?inline';
export default component$(() => {
// 2.- Asignamos el contenido de los estilos
useStyles$(menuStyles)
const { menu } = useContent();
return (
<>
...
</>
);
});
Añadir la estructura HTML del menú
Vamos a añadir la estructura HTML teniendo en cuenta los dos casos, los menús SIN HIJOS y CON HIJOS, aparte de especificar si un enlace se abrirá de manera interna (_self
) o de manera externa (_blank
) en otra pestaña.
El contenido de src/routes/layout.tsx
será el siguiente con los nuevos cambios, que explicaré a continuación por partes:
import { component$, Slot, useStyles$ } from "@builder.io/qwik";
// 1 - Añadir ContentMenu
import { ContentMenu, useContent } from "@builder.io/qwik-city";
import menuStyles from "./menu.css?inline";
export default component$(() => {
useStyles$(menuStyles);
const { menu } = useContent();
// 2.- Para definir si un enlace es interno /externo
const isLocalUrl = (url: string) => {
return url.startsWith('http://') || url.startsWith('https://') ? '_blank' : '_self'
};
// 3.- Estructura del menú
return (
<>
<ul id="menu">
{menu?.items?.map((item: ContentMenu) => {
if (item?.items?.length === 1) { // Menús sin hijos
return (
<li class="parent">
<a href={item.items[0].href} target={isLocalUrl(item.items[0].href || '')}>{item.text}</a>
</li>
);
}
return <li class="parent">
<a href="#">{item.text}</a>
<ul class="child">
{
item?.items?.map((element: ContentMenu) => (
<li>
<a href={element.href || ''} target={isLocalUrl(element.href || '')}>{element.text || ''}</a>
</li>
))
}
</ul>
</li>;
})}
</ul>
<br />
<Slot />
</>
);
});
Esto es lo que hemos implementado:
- Hemos importado la clase
ContentMenu
para tipar correctamente los elementos del menú definido con esta técnica. - Función para especificar si un enlace es interno / externo. Se considera externo si contiene al inicio
https://
óhttp://
devolviendo el valor_blank
que se asignará a la propiedad target del elemento a. Si no contiene esas cadenas de texto, se considerará interno devolviendo el valor_self
- Tenemos dos partes:
- Comprueba si en
items
tenemos solo un elemento y si cumple esto, será el menú principal, sin hijos y contendrá un enlace que puede ser interno / externo dependiendo del valor de su enlace. - La segunda, ya sabiendo que tiene hijos, define la lista de estos elementos con sus enlace, dejando el principal como un texto que no tiene enlace.
- Tanto de una forma u otra, se añadirá su enlace y se especifica el
target
para abrirlo internamente ó externamente.
Cuando guardamos los cambios, si vamos a nuestra aplicación esto es lo que debería de mostrarnos:
¡Bien! De primeras visualmente se ve correcto, probad a ver si os funciona bien los enlaces internos y externos.
Pruebas para comprobar su funcionamiento
- Click en Contacto, debe de llevarnos a
/contact
2. Click en Proyectos, debe de llevarnos a /projects
3. Qwik Oficial / Cursos Gratis
Nos abrirá UNA NUEVA PESTAÑA a mi curso de Qwik GRATIS
Con estas tres pruebas, ya sería suficiente para poder decir que ya tenemos todo listo.
Ya hemos conseguido definir un menú de dos niveles, ya tenemos más conocimientos de como funciona el uso de useContent()
combinándolo con el fichero menu.md
Lo trabajado hasta este punto lo podemos encontrar a continuación:
https://github.com/Qwik-Spanish/qwik-advanced-menu-from-md/archive/refs/heads/02-first-menu-use-content.zip
Crear contenido de menús de más niveles
Y quizás tenéis la duda de como montar menús de 3,4, 5,…niveles y por eso, os dejaré un ejemplo con de como definirlo en el fichero menu.md
, ya en el layout lo deberéis trabajar por vuestra cuenta.
Con eso, ya deberíais de tener suficientemente información para poder avanzar.
Menú de 3 niveles
El contenido del fichero menu.md, donde haremos cambios añadiéndole un nuevo nivel dentro de Qwik, con dos apartados principales, Oficial y No oficial, añadiendo en los dos casos los enlaces correspondientes
# Menú Principal
## Inicio
- [-](/)
## Proyectos
- [-](/projects)
## Qwik <==== AHORA TIENE DOS HIJOS = Oficial y No Oficial
### Oficial <====== AQUÍ HEMOS AÑADIDO UN NUEVO NIVEL con ###
- [Documentación](https://qwik.builder.io/docs/)
- [Playground](https://qwik.builder.io/examples/)
### No Oficial <====== AQUÍ HEMOS AÑADIDO UN NUEVO NIVEL con ###
- [Curso GRATIS](https://medium.com/@mugan86/list/qwik-paso-a-paso-desde-0-al-detalle-e7df8b471166)
- [Qwik en Español - Ayuda](https://github.com/Qwik-Spanish/qwik-i18n-translate-es)
## Componentes
- [Inicio](https://qwik.builder.io/docs/components/overview/)
- [Tareas y Ciclos de Vida](https://qwik.builder.io/docs/components/tasks/#tasks)
## Contacto
- [-](/contact)
Y al definir, este será la estructura de ese menú, con los cambios implementados
Ahora lo que nos quedaría es mediante el renderizado en el HTML formar un menú multinivel, pero eso ya es cosa vuestra, el objetivo de mostraros las claves para entender el hook useContent()
ya se han proporcionado.
Conclusión
Y llegados a este punto, con todo lo que hemos visto, hemos aprendido a crear menús de varios niveles en Qwik mediante el uso de contenido de tipo Markdown, combinándolo con el hook useContent()
para hacer menús y poder mantenerlos de una manera super sencilla.
Llevamos aprendido muchísimo, os animo a que repaséis si hiciese falta.
Me gustaría que en los comentarios dejaseis resultados de cosas que vayáis practicando, ya que cuanta más variedad, todo el mundo nos beneficiamos de ello.
Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando a medida que vaya añadiendo nuevos contenidos con el orden natural recomendado:



Si queréis recibir notificaciones sobre este contenido y similares, os animo a que os suscribáis a mi lista de correo:
Solo habrá avisos con nuevos contenidos, es decir, nuevos artículos (1–3 máximo a la semana, generalmente 1).
Presencia en redes sociales
Podéis encontrarme en las siguientes redes.
- Twitter.
- Github
- Youtube
- Lista de correo (sobre nuevos artículos)