Qwik — Routing
Claves al detalle para trabajar con las rutas en Qwik
Comenzamos un nuevo artículo en el que vamos a trabajar de manera más profunda con las rutas en Qwik, después de haber obtenido las nociones básicas en el artículo inicial.
Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:
En este artículo anterior hemos visto los primeros pasos de una aplicación de una manera muy general después de ver las características y hacer comparativa con otras tecnologías, por lo que el aspecto de las rutas lo trataremos con más cariño, ya que es un aspecto importante a la hora de trabajar con este framework.
Lo que vamos a trabajar en este artículo es lo siguiente:
- ¿Qué es Routing (Enrutamiento)?
- Básico. Enrutamiento basado en directorios.
- Avanzado. Enrutamiento mediante parámetros en diferentes variantes.
Requisitos a tener en cuenta
- Haber completado la lectura del artículo anterior.
- Tener instalado Node 16 ó superior.
- Ganas de seguir aprendiendo.
- Deseable tener algo de experiencia con otras tecnologías trabajando con diferentes rutas.
- Tener creado el proyecto de Qwik.
¿Qué es Routing?
El enrutamiento (Routing) es una forma de asignar direcciones URL públicas de un sitio a componentes específicos declarados en nuestra aplicación.
Qwik City utiliza el enrutamiento basado en directorios. Esto significa que la estructura de su directorio de rutas especificará las URL públicas que el usuario va a poder hacer uso en su aplicación.
Vamos a la práctica para implementarlo.
Enrutamiento basado en directorios
Antes de empezar a trabajar con ello, vamos al proyecto creado y eliminamos el directorio flower
, para empezar únicamente con la ruta inicial simplificando lo que tenemos y así empezaremos desde 0. El resultado actual queda de la siguiente forma en el directorio routes
.
En el directorio de rutas de Qwik City (src/routes
), tendremos una nueva carpeta que será la carpeta-ruta-principal
+ un fichero index
que harán que se asignen a una ruta URL de manera automática.
Por ejemplo, imaginaros que vemos una ruta con esta apariencia:
Sabemos que esta ruta se mostrará con el contenido del componente exportado en src/routes/public/index
(ya sea .mdx
o .tsx
, entre otros formatos, que comentaré a continuación de los que podemos hacer uso).
src/
└── routes/
└── public/
└── index.tsx # (.mdx, .jsx, ...) # http://127.0.0.1:5173/public
Hay que tener en cuenta que el archivo del final de la ruta se debe de llamar index
. Esto es SUPER importante.
Quizás en otros frameworks con los que habéis trabajado, se hace una distinción entre páginas
y endpoints de datos
o rutas API
. En Qwik City no hay distinción, todos son endpoints en el fichero index.[tipo-de-fichero]
.
Qwik City admitirá los siguientes tipos de archivos para rutas, lo que si tenemos que hacer es SIEMPRE llamar al fichero index
, como hemos mencionado.
Nosotros podemos definir el fichero index.[tipo-de-fichero]
donde [tipo-de-fichero]
puede ser .ts
, .tsx
, .js
, .jsx
, .md
, o .mdx
.
Ejemplo con un caso QUE NO FUNCIONA en Qwik (por no estar creando el directorio con su fichero index dentro) pero que probablemente funcione en otros frameworks o tecnologías con las que habéis trabajado.
Creamos los siguientes ficheros y en todos añadimos el contenido del componente por defecto como este contenido en cada uno de los 4 ficheros (index.tsx
no tocamos):
Siendo esta la estructura de los ficheros en estos momentos:
src/
└── routes/
├── contact.tsx
├── blog.tsx
├── portfolio.tsx
└── index.tsx
Si hacemos la prueba con los siguientes endpoints teniendo como base la URL local, que en mi caso es http://127.0.0.1:5173
:
index.tsx
(http://127.0.0.1:5173)
portfolio.tsx
(http://127.0.0.1:5173/portfolio)
Si probamos con blog.tsx
(http://127.0.0.1:5173/blog) y con contact.tsx
(http://127.0.0.1:5173/contact) nos pasará como con portfolio
, que no encontrará la ruta.
SOLO funcionará con index.tsx
que cumple la nomenclatura del nombre de fichero, dentro de routes en la raíz de esta carpeta, por lo que como se ha podido ver, accede al contenido.
Por lo tanto, para que SI funcione en nuestro proyecto Qwik debemos de dejar el contenido estructurado de la siguiente forma, con el directorio especificado para la página y dentro un fichero index.tsx
:
src/
└── routes/
├── portfolio/
│ └── index.tsx # http://127.0.0.1:5173/portfolio
├── contact/
│ └── index.tsx # http://127.0.0.1:5173/contact
├── blog/
│ └── index.tsx # http://127.0.0.1:5173/blog
└── index.tsx # http://127.0.0.1:5173/
Comprobando las rutas que nos han dado problemas.:
- /blog
Haremos lo mismo con portfolio
y contact
, nos debería de aparecer el contenido concreto a esas rutas, comprobadlo.
Puede pasar lo siguiente, que al ir a cargar cualquiera de las rutas en las que hemos visualizado el error “404 Not found” se muestre el contenido de la página en blanco, cosa que no debería:
Y en la consola un mensaje similar al siguiente:
Si ocurre esto, debemos de parar la ejecución del servidor y reiniciarlo. Volved a probar.
Al principio, esto puede parecer un trabajo extra, pero este enfoque tiene sus ventajas.
Una de esas ventajas es poder definir archivos de componentes en un directorios de ruta sin que se representen, pudiendo usarlo como un componente complementario para esa ruta que se añade al componente principal representado en index. Considera lo siguiente:
src/
└── routes/
├── index.tsx # http://127.0.0.1:5173/
...
└── blog/
├── index.tsx # http://127.0.0.1:5173/blog
└── other-component.tsx # Fichero ignorado y sin mapea en cualquier URL.
El fichero other-component.tsx
puede ser importado y usado dentro blog/index.tsx
, donde por otro lado será completamente ignorado por Qwik City.
El contenido podremos implementarlo en formato componente o mediante el uso del renderizado de páginas con contenido markdown (extensiones md o mdx). Nos centramos en cada uno de ellos.
Implementando un componente
Para devolver HTML para una ruta específica, deberá implementar un componente. Para archivos .tsx, el componente debe exportarse por defecto que llamaremos default
. De manera alternativa, podremos renderizar contenido en formato Markdown con las extensiones .md
y .mdx
.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <H1>Hello World!</H1>;
});
Implementando contenido en formato Markdown
Para poder trabajar con rutas que visualizan contenido en formato Markdown, lo que es el modo de crear el path, es el mismo, creando la carpeta.
Lo único que va a cambiar es el fichero que crearemos, que en vez de llamarse index.tsx como hemos estado haciendo hasta este momento, será index.mdx
(o index.md
)
Añadimos una nueva ruta llamada /readme
junto con su fichero index
, que en este caso será con la extensión mdx
.
src/
└── routes/
├── index.tsx # http://127.0.0.1:5173/
...
└── readme/
├── index.mdx # http://127.0.0.1:5173/readme
└── blog/
├── index.tsx # http://127.0.0.1:5173/blog
Una vez creado el fichero, añadimos el siguiente contenido:
Ahora si accedemos al endpoint asignado: http://127.0.0.1:5173/readme
Debería de mostrarse la información (recordad, que habrá que reiniciar para que se asigne el endpoint en el caso de no visualizarse el contenido):
Si añadimos un elemento de lista con un enlace:
# Readme Rutas
Trabajando con formato markdown
* [Twitter](https://twitter.com/mugan86)
Al recargar quedará de la siguiente forma:
Routing Avanzado — Enrutamiento mediante parámetros
Los parámetros de ruta son partes de las URL que se extraen en los parámetros.
Imaginaros que tenemos una página con las siguientes URLs donde id-del-producto
puede ser cualquiera de los miles de productos que tenemos en nuestra base de datos.
Obviamente, no sería nada práctico crear una ruta para cada producto ya que no sería nada fácil de mantener y a la vez no se aplicarían buenas prácticas. En su lugar, vamos a definir un parámetro de ruta (una parte de la URL) que se va a usar para extraer el id de nuestro producto con id-del-producto
de manera dinámica.
Teniendo como referencia el parámetro para el id del producto
, especificamos el parámetro de ruta dentro del directorio con el formato [<nombre-parametro>]
donde teniendo el id del producto
llamado por ejemplo productId
, se quedará asignado como [productId]
.
Dentro del proyecto especificaremos de la siguiente manera:
src/
└── routes/
├── index.tsx # http://127.0.0.1:5173/
...
└── product/
└── [productId]/
├── index.tsx # http://127.0.0.1:5173/product/1234
└── details/
└── index.tsx # http://127.0.0.1:5173/product/1234/details
En el proyecto se refleja así, de la siguiente manera:
Probamos ejecutando diferentes URLs (reiniciar el servidor):
Página del producto principal
http://127.0.0.1:5173/product/[productId]
- OK:
http://127.0.0.1:5173/product/1234
- OK:
http://127.0.0.1:5173/product/xyz-567
- 404:
http://127.0.0.1:5173/product/xyz-567/34
Con una de las opciones, por ejemplo la opción /product/1234
, como se puede apreciar se carga el contenido de la página principal del producto, tal como se ha especificado en el componente del fichero index.tsx
en la ruta de ficheros src/routes/product/[productId]/index.tsx
:
Página de detalles del producto seleccionado:
http://127.0.0.1:5173/product/[productId]/details
- OK:
http://127.0.0.1:5173/product/1234/details
- 404:
http://127.0.0.1:5173/product/1234/detail
(IMPORTANTE FIJARSE COMO TERMINA, falta la “s”)
Con una de las opciones, por ejemplo la opción /product/1234/details
, se cargará el contenido de la página de detalles del producto, tal como se ha especificado en el componente del fichero index.tsx
en la ruta de ficheros src/routes/product/[productId]/details/index.tsx
:
Bien, ahora ya sabemos como añadir información de manera dinámica mediante parámetros de ruta donde hemos añadido el id de un producto y lo que nos hace falta es plasmarlo en la información de nuestra página y esto lo haremos mediante la obtención del parámetro ruta desde una URL
Obtención del parámetro ruta desde una URL
Una vez que tenemos [productId]
en la URL, necesitamos recuperar el valor que se adjunta en ese parámetro y esto lo podemos hacer mediante el uso de la API useLocation().
Lo aplicaré en el fichero index.tsx
dentro de la carpeta details, cuya ruta es src/routes/product/[productId]/details/index.tsx
Dejando el componente de esta forma:
import { component$ } from "@builder.io/qwik";
import { useLocation } from "@builder.io/qwik-city";
export default component$(() => {
const location = useLocation();
return (
<>
<div>Product Item Details- Página</div>
<p>Href: {location.href}</p> // URL princ. + path + valor de parámetro de ruta
<p>Pathname: {location.pathname}</p> // path + valor de parámetro de ruta
<p>Product Id: {location.params.productId}</p> // Valor de parámetro de ruta
</>
);
});
Una vez guardados los cambios, se reflejará de la siguiente manera introduciendo 1234
como valor del productId
en http://127.0.0.1:5173/product/1234/details/
Para poder obtener el valor del parámetro de ruta en la página principal del producto (src/routes/product/[productId]/index.tsx)
deberemos de aplicar también el uso de useLocation() y hacer lo mismo. Esto lo aplicáis para afianzar lo aprendido y comprobáis el resultado que debe de mostrar esta información:
Capturar todas las rutas
También es posible crear rutas comodín agregando un directorio como [...productIds]
. Esto funciona como el ejemplo descrito anteriormente, pero también considerará sub-apartados del URI.
También se va a poder aplicar en el nivel raíz de las rutas.
src/
└── routes/
└── product/
└── [...productIds]/
└── index.tsx
Y dentro del fichero index.tsx
añadimos el siguiente contenido, donde en el apartado de productIds obtendrá un string concatenado con todos los ids de productos que para mostrarlo como string, lo convertiremos:
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
export default component$(() => {
const location = useLocation();
return (
<>
<div>Producto - Page principal</div>
<p>Href: {location.href}</p>
<p>Pathname: {location.pathname}</p>
<p>Product Id: {JSON.stringify(location.params.productIds)}</p>
</>
);
});
Usando la configuración establecida, ejecutamos varias Url y vemos su resultado:
Podríamos “extraer” esos ids y mostrarlos de una manera más elegante, recorriendo el contenido, mediante la creación de un array desde separar elementos mediante split usando como parámetro “/”:
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
export default component$(() => {
const location = useLocation();
return (
<>
....
<p>Product Id:</p>{' '}
<ul>
{location.params.productIds.split('/').map((id, index) => {
return (id !== '') ? (
<li>
Product select ({index + 1}): {id}
</li>
): undefined
})}
</ul>
</>
);
});
Visualizándose de la siguiente forma:
¿Podremos especificar un límite para introducir los datos?
Si, es posible limitar la captura de los parámetros de ruta si añadimos un directorio que lo limite. Para especificar ese límite debemos de añadir un nuevo directorio (ó más de uno) con un nombre descriptivo de la página que limitará esa captura de valores de la ruta. Por ejemplo, yo añadiré uno llamado details
dentro de [...productIds]
, en el que cogeré todos los ids anteriores y los muestro de una manera detallada tal y como se hace en la página principal.
src/
└── routes/
└── product/
└── [...productIds]/
└── index.tsx
└── details/
└──index.tsx
Cuyo contenido será el siguiente, que servirá ya para diferenciarlo respecto al index.tsx
anterior que sería la página principal:
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
export default component$(() => {
const location = useLocation();
return (
<>
<div>Productos - Page con detalles</div>
<p>Href: {location.href}</p>
<p>Pathname: {location.pathname}</p>
<p>Product Id:</p>{' '}
<ul>
{location.params.productIds.split('/').map((id, index) => {
return (id !== '') ? (
<li>
Producto seleccionado ({index + 1}): {id}
</li>
): undefined
})}
</ul>
</>
);
});
La siguiente estructura dará opción a capturar esta ruta:
Extrayendo 203/204/205
como los valores de los ids de los productos ya que details
ya considera el final de la ruta y no tiene en cuenta ese valor utilizando el fichero que hemos creado en la siguiente ruta: src/routes/product/[...productIds]/details/index.tsx
.
En cambio, si añadimos
Extraerá 203/204/205/details/2333
como los valores de los ids de los productos ya que details
NO se ha especificado como final de la ruta a la hora de la introducción de la URL. Por ese motivo no usa el fichero index.tsx
que se encuentra en la ruta src/routes/product/[...productIds]/details/index.tsx
.
Lo que va a usar será el fichero que se encuentra en la ruta src/routes/product/[...productIds]/index.tsx
, mostrándose de la siguiente manera, como se ha especificado anteriormente:
Agrupación
En algunos casos, puede surgir la necesidad de mover nuestras rutas a un subdirectorio sin afectar el nombre de la ruta que va a generar Qwik City en función de la estructura de carpetas, para poder tener más orden.
Puede darse el caso que tengamos varias rutas en el mismo nivel que deberían usar diferentes diseños (esto lo veremos con más detalle en un artículo que se publicará en breves)
Tanto para la primera situación o la segunda, podemos crear directorios entre paréntesis (p. ej. (products)
). Lo vamos a reflejar de la siguiente manera:
src/
└── routes/
└── (products)/
├── detail/
| └──index.tsx
├── preview/
└──index.tsx
Realizamos las pruebas correspondientes, donde usamos el nombre directorio ignorando completamente el directorio (products)
:
Como se ha mencionado products
NO SERÁ parte del pathname.
Conclusión
Hemos podido aprender las deferentes formas que tenemos de como gestionar las rutas y como obtener la información de los parámetros de rutas, desde lo más sencillo a conceptos más avanzados.
Con lo aprendido en este artículo y el anterior, podremos pasar al siguiente donde nos faltaría ver los aspectos de las plantillas anidadas, donde podremos construir diferentes plantillas en base a la ruta, como por ejemplo zona privada y pública y también el enrutamiento en base a los menús. Esto lo veremos en un próximo artículo.
Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:
Presencia en redes sociales
Podéis encontrarme en las siguientes redes.