Comenzamos un nuevo capítulo en el que vamos a trabajar de manera más profunda con las plantillas en Qwik. Haremos uso de los conocimientos adquiridos del capítulo del Routing implementando el uso del enrutamiento para así ir cargando el contenido seleccionado por la ruta especificada siempre procurando aplicar unas buenas prácticas.
Para poder trabajar en este capítulo es fundamental haber leído y entendido los capítulos anteriores:
Es fundamental haber entendido estos conceptos anteriores para poder aplicar estos conocimientos a las plantillas, en las diferentes situaciones que podamos enfrentarnos en cualquier proyecto común de cualquier día cotidiano.
Una vez realizada la introducción os voy a exponer los puntos que trabajaremos en este capítulo.
Os expongo a continuación los puntos que vamos a tratar en este capítulo.
Para empezar a trabajar con los aspectos teórico-prácticos de los layouts (Plantillas), debemos de crear un proyecto nuevo tal y como se ha explicado anteriormente. En este caso os recomiento llamar al directorio 04-01-basic-navigation. Este será el primer de varios que vamos a completar en este capítulo.
Cuando estamos trabajando con las rutas, nos encontramos ante la situación que muchas de estas (en general es así, por lo menos con alguno de estos elementos o extras) compartirán en común una cabecera (<Header />
), pie de página (<Footer />
) y un menú (<Menu />
). A estas partes las llamamos partes comunes (commons
) de una plantilla.
En estos casos, nuestro trabajo como desarrolladores será extraer esas partes y añadirlas en componentes, con el objetivo de tener ese código aislado y con opción a poder utilizarlo en el futuro en una o más plantillas diferentes, manteniendo la reutilización de estos apartados comunes, aplicando buenas prácticas.
Aparte de lo mencionado nos ayudará a no tener que cargar una y otra vez el mismo contenido, solo lo haremos la primera vez.
Para verlo más claro, vamos a plantear un caso de uso común, como la portada de un blog / página web donde tenemos estos tres elementos mencionados.
Para facilitar el trabajo y podamos avanzar más rápido y ver lo siguiente:
Debemos de realizar los cambios en el proyecto en estos archivos:
src/routes/layout.tsx
src/routes/index.tsx
src/global.css
Obteniendo el código desde el siguiente Gist, actualizaremos la información correspondiente a cada uno de ellos en nuestro proyecto.
Una vez realizados los cambios y viendo nuestro proyecto con la apariencia mencionada tenemos que separarlo por partes.
Para especificar el contenido de manera dinámica, tendremos el Slot (3)
para ir cargando el contenido de las páginas dinámicamente mediante la selección de la ruta y por otro lado, los apartados comunes Header (1)
, Menu (2)
y Footer (4)
que serán los apartados que convertiremos a componentes, con los conocimientos adquiridos en el capítulo anterior.
En estos momentos, con el proyecto tenemos únicamente creada la ruta inicial /
Para poder enfocar el proyecto a algo más real debemos de crear más rutas, por ejemplo la ruta /about
en la que podemos cargar otra información que sea de nuestro agrado y/o necesidad.
Esta nueva ruta tiene que hacer la carga del contenido únicamente en el <Slot>
, lo demás, sigue igual, los elementos comunes, podemos implementarlos en el layout.tsx
inicial.
Sabiendo sobre los componentes a reutilizar y esto último, especificamos el siguiente estado en el proyecto, implementando estos ficheros y directorios dentro de components
y routes
:
src/
├── components/
| ...
| └── core/
| ├── header.tsx # Implementación del componente Header
| ├── footer.tsx # Implementación del componente Footer
| └── menu.tsx # Implementación del componente Menu
└── routes/
├── layout.tsx # Plantilla con: <Header>, <Footer> y<Menu>
├── about/
│ └── index.tsx # /about
└── index.tsx # /
¿Para qué extraerlos de layout.tsx
? Si los extraemos y los convertimos en componentes individuales nos proporcionan más libertad para poder hacer uso de estos en otras variantes de plantilla, que justo en este capítulo lo vamos a ver al detalle.
En src/routes/about/index.tsx
añadimos lo siguiente:
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
export default component$(() => {
return (
<div>
<h1>Ruta "/about"</h1>
Esta es la página <b>about</b>
</div>
);
});
export const head: DocumentHead = {
...
};
Si guardamos y accedemos a la ruta /about
debemos de visualizar lo siguiente:
Una vez definidas las dos rutas mencionadas vamos a comenzar seleccionando la zona a extraer desde layout.tsx
, para coger ese código y llevarlo al componente <Header />
:
Lo añadimos en src/components/core/header.tsx
de esta manera:
export const Header = (() => {
return (
<nav>
<h1>Header</h1>
</nav>
);
});
Y añadimos el componente utilizando el código refactorizado en el nuevo componente:
Antes de centrarnos en el apartado de los elementos del menú creamos el componente <Footer />
, que no tiene mucho misterio, seguimos con los mismos pasos enfocados con este apartado:
Añadimos el código de manera aislada en el componente <Footer />
que se encuentra en src/components/core/footer.tsx
:
export const Footer = () => {
return (
<footer>
<h2>Footer</h2>
</footer>
);
};
Añadir el componente en el layout:
Ahora que ya se han realizado estos primeros cambios, como podréis apreciar, la apariencia es la misma, solo que ahora estaremos reutilizando estos componentes en esta plantilla y en otras variantes que podamos tener, con lo que estaríamos aplicando muy buenas prácticas.
Esto es lo básico de trabajar con plantillas que junto con los conocimientos adquiridos sobre el enrutamiento en el capítulo Routing (Enrutamiento), vamos a ver un nuevo elemento que va a ser super útil en el desarrollo de aplicaciones Qwik, en lo que respecta a la navegación.
Lo que faltaría para hacer la refactorización completa sería la creación del componente <Menu />
que implementaremos dentro del próximo punto donde se explican las diferencias entre los elementos de navegación existentes en Qwik.
Existirán dos formas principales de navegar en Qwik. En este caso va a desaparecer la distinción entre aplicaciones de una sola página (SPA) y aplicaciones multipágina (MPA).
Cada aplicación puede utilizar ambas en el mismo proyecto, dependiendo de nuestras necesidades.
A la hora de navegar por la aplicación, tenemos que tener en cuenta lo siguiente para las dos formas existentes:
Aplicaciones multipágina (MPA)
: Navegación cargando todo el contenido cada vez que se hace click con el selector a
Aplicaciones tipo SPA (igual a Angular)
: Solo recarga lo que vamos a meter en el <Slot />
haciendo uso del componente <Link />
y / o el hook useNavigate()
.a
: Navegación MPASerá el enlace de toda la vida y para navegar a una ruta u otra, simplemente debemos de de asignarle el valor de esa ruta en el atríbuto href
<a href={'/about'}>Enlace cargando todo el contenido (estilos globales y todo el documento de nuevo)</a>
Este elemento nos llevará a la ruta about
de nuestra aplicación y lo hará cargando TODO EL CONTENIDO DE NUEVO, es decir, cargando el contenido de los estilos globales, cabeceras, etc.
Teniendo como referencia la plantilla que hemos trabajado al principio de este capítulo, cargaría aparte de lo que tenemos en la ruta, cargaría de nuevo todos los componentes individualmente, haciendo que su rendimiento no sea el más adecuado.
En este caso, no nos interesa estar recargando una y otra vez componentes que van a estar siempre a la vista en nuestro proyecto como puede ser una cabecera (<Header />
) o un pie de página (<Footer />
). También nos interesa que el apartado del menú sea fijo y que se cargue una primera vez.
¿Cómo solucionamos esto para tener una experiencia Single Page Application (SPA)
? Usando el componente <Link />
y/o el hook useNavigate()
.
Para realizar la navegación con experiencia tipo SPA, Qwik (como en Anugular por ejemplo) nos ofrece un componente <Link />
y un hook useNavigate()
.
Podemos usarlo haciendo este import:
import { Link, useNavigate } from '@builder.io/qwik-city';
Estos elementos se pueden utilizar para iniciar una actualización de SPA o navegar entre páginas SIN RECARGAR todo el documento.
El componente <Link />
es la forma recomendada de navegar cuando interactuamos desde un elemento HTML, ya que utiliza la etiqueta HTML <a>
, que es la forma más accesible de moverse entre páginas.
Si necesitamos navegar programáticamente, podemos utilizar el hook useNavigate()
que mantendrá el mismo comportamiento que <Link />
pero ejecutándose después de un click a un elemento <button>
, por poner un ejemplo.
useNavigate()
Para obtener más información complementaria desde la documentación oficial de Qwik, la podéis encontrar a continuación:
https://shorten-up.vercel.app/uogNNg2NGK
El código de ejemplo para trabajar con estos dos elementos sería el siguiente:
import { component$ } from '@builder.io/qwik';
// 1.- Importamos el componente y el hook para navegación tipo SPA
import { Link, useNavigate } from '@builder.io/qwik-city';
export default component$(() => {
// 2.- Iniciamos el hook
const nav = useNavigate();
// 3.- Link y/o nav => Nos llevan a '/about'.
// El primero desde un elemento HTML y segundo desde una acción de click a un botón
return (
<div>
<Link href="/about">About (Más recomendable)</Link>
<button onClick$={() => nav('/about')}>About</button>
</div>
);
});
Tanto <Link />
como useNavigate()
nos llevarán a la ruta /about
de manera interna, solo recargando el contenido de esa ruta y no todo el documento. Esto lo veremos con más detalle cuando trabajemos con los estilos en el siguiente capítulo, viendo su comportamiento ante el uso de <a>
ó <Link />
.
No debemos de olvidar que el componente Link
usará internamente el hook useNavigate()
.
Esto es lo básico que necesitamos saber, si queréis profundizar en este aspecto sobre el apartado SPA y el uso de <Link />
junto con useNavigate()
, os invito que accedáis a la documentación oficial (Siguiendo esta referencia: https://shorten-up.vercel.app/T-WhiunHkj) y exploréis las diferentes opciones con este apartado.
Una vez vistas las formas de navegar en Qwik, con el nuevo elemento <Link />
junto con useNavigate()
más al detalle, vamos a empezar a realizar el último paso de la refactorización aplicando el uso de <Link />
para los elementos del menú.
<Menu/>
{#menu-component-navigation}Ahora mismo, estamos enfocados en el apartado donde añadiremos el contenido correspondiente al componente <Menu />
Hacemos lo mismo con la parte del menú, trasladándolo al componente <Menu>
:
Lo añadimos en src/components/core/menu.tsx
de esta manera:
import { Link } from "@builder.io/qwik-city";
export const Menu = () => {
return (
<div class="column menu-items">
<h2>Menu</h2>
<ul>
<li>
<Link href={"/"}>Home</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
</ul>
</div>
);
};
Y añadimos el componente <Menu />
aislado en el layout, como hemos hecho con <Header />
:
Después de realizar todos estos cambios y mejoras en el código, vamos a trabajar con cosas más especificas acorde a los layouts (plantillas).
En este punto es donde queremos buscar una personalización de plantillas enfocándonos en diferentes apartados que podamos encontrar dentro de nuestros proyectos, como podría ser tener una plantilla pública y una privada.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto, donde se ha trabajado con los aspectos básicos de la navegación en Qwik.
Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/fKWVTB0KPh
Partimos de lo que hemos trabajado anteriormente y creamos una variante nueva de proyecto con el nombre 04-02-grouped-layouts donde vamos a crear una nueva carpeta dentro de src/routes
llamada (complete)
donde dejaremos agrupados los elementos que usarán la plantilla completa ignorando completamente el texto complete
en el enrutamiento tal y como se ha visto en Routing (Enrutamiento).
Por refrescar lo visto anteriormente, lo que hacemos en este caso es organizar mejor nuestras carpetas sin que afecten en nada a las rutas originales, por lo que esto nos evitarán muchos quebraderos de cabezas ante la posibilidad de romper rutas.
Hacemos ese cambio, y comprobamos que podemos seguir accediendo a las rutas /
y /about
de la misma forma que hasta ahora.
Lo implementamos de la siguiente forma:
src/
├── components/
| ...
| └── core/
| ├── header.tsx # Componente Header
| ├── footer.tsx # Componente Footer
| └── menu.tsx # Componente Menu
└── routes/
| # Nuevo directorio
└── (complete)
├── layout.tsx # Plantilla con: <Header>, <Footer> y <Menu>
├── about/
│ └── index.tsx # /about
└── index.tsx # /
Una vez que hemos separado un grupo de rutas asociadas a un tipo de layout con (complete)
, dentro de src/routes
creamos una carpeta principal para agrupar rutas con otro tipo de layout, que lo llamaremos only-header
.
Y dentro del contenido de la carpeta src/routes/(only-header)
recien creada, añadimos un nuevo fichero layout.tsx
que servirá para añadir la apariencia del nuevo tipo de layout asociado a este grupo de rutas.
Dentro de este nuevo fichero es muy importante que no olvidemos de incorporar el componente <Slot />
, ya que sin esto, no proyectará el contenido de la ruta asociada.
Añadimos lo siguiente, únicamente reutilizando el componente <Header />
olvidándonos de <Menu />
y <Footer />
:
import { component$, Slot } from "@builder.io/qwik";
import { Header } from "~/components/core/header";
export default component$(() => {
return (
<>
<Header />
<section class="row">
<Slot />
</section>
</>
);
});
Con los cambios que hemos realizado NO DEBERÍA haber cambios en las rutas. Si probamos las rutas /
y /about
, podremos ver que sigue cargándose el contenido correctamente, solo que estas rutas ya estarán aisladas con su correspondiente layout principal, simplemente por temas de organizar mejor nuestros directorios y ficheros.
Ahora nos olvidamos del contenido de la carpeta (complete)
y seguimos trabajando en el otro grupo recien creado src/routes/(only-header)
.
Especificamos las características de la nueva plantilla que contiene:
<Slot /
.Creamos las nuevas rutas contact
y blog
siguiendo el proceso ya visto.
La estructura de ficheros se reflejaría de la siguiente forma:
src/
├── components/
| ...
| └── core/
| ├── header.tsx # Componente Header
| ├── footer.tsx # Componente Footer
| └── menu.tsx # Componente Menu
└── routes/
├── (complete) # Directorio para plantilla completa
| ├── layout.tsx # Plantilla con: <Header>, <Footer> y<Menu>
| ├── about/
| │ └── index.tsx # /about
| └── index.tsx # /
|
| # ¡¡El nuevo directorio con solo "Header" + contenido!!
└── (only-header)
├── layout.tsx # Plantilla con: <Header>
├── blog/
│ └── index.tsx # /blog
└── contact/
└── index.tsx # /contact
El código aplicado en esta dos nuevas páginas:
src/routes/(only-header)/blog/index.tsx
import { component$ } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";
export default component$(() => {
return (
<div>
<h1>Ruta "/blog"</h1>
Esta es la página <code>blog</code> con la plantilla <code>only-header</code>
<br />
<Link href={"/"}>Volver a la página principal</Link>
</div>
);
});
src/routes/(only-header)/contact/index.tsx
import { component$ } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";
export default component$(() => {
return (
<div>
<h1>Ruta "/contact"</h1>
Esta es la página <code>contact</code> con la plantilla <code>only-header</code>
<br />
<Link href={"/"}>Volver a la página principal</Link>
</div>
);
});
Aplicando los cambios, si navegamos a las 4 rutas disponibles:
only-header
, solo cabecera + contenido/blog
(only-header)/contact
(only-header)complete
{#complete-layout}/
(complete)/about
(complete)Llegados a este punto, ya sabemos como trabajar con los Grouped Layouts
(Plantillas Agrupadas) para poder tener de manera agrupada las rutas que corresponde a un tipo de layout, para poder hacer diferentes variantes de layouts para nuestros objetivos.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/-7y1Fp6xNn
Partimos desde el código base de lo que se ha trabajado en Introducción a los layouts cuyo enlace lo tenéis a continuación:
https://shorten-up.vercel.app/fKWVTB0KPh
Usad ese proyecto, haciendo una copia y lo renombramos a04-03-named-layouts.
Después de trabajar con las plantillas agrupadas, comenzamos con las plantillas nombradas, que sería prácticamente lo mismo que las agrupadas pero ya usando una plantilla u otra mediante referencias nombradas.
A veces, las rutas relacionadas deben tener diseños drásticamente diferentes respecto a otras rutas de la misma aplicación.
Es posible definir múltiples diseños para diferentes rutas hermanas.
Podremos tener un único diseño predeterminado como base para un uso general y aparte, un número indeterminado de diseños aplicando dichos diseños con una referencia nombrada.
Qwik City
va a definir la convención de que los diseños van a encontrarse siempre dentro de src/routes
y que el nombre del fichero empieza con layout
para cargar correctamente lo asociado a una ruta seleccionada y siempre el diseño por defecto se llamará layout.tsx
.
Cualquier diseño con nombre (nombrado) también va a comenzar con el texto layout + - (guión) + nombre-asignado + .tsx que no se podrá duplicar obviamente.
Si creamos una plantilla con únicamente un header
, podría ser como la anterior only-header
.
Teniendo en cuenta esa referencia en este caso a la plantilla la debemos de llamar como layout-only-header.tsx.
Para seguir trabajando en este apartado, vamos a crear esta estructura de directorios y ficheros:
src/
...
└── routes/
|── about/ # /about
| └── index.tsx
|── blog/ # /blog
| └── index@only-header.tsx
|── contact/ # /contact
| └── index@only-header.tsx
|── index.tsx # /
├── layout-only-header.tsx # Layout con solo Header
└── layout.tsx # Layout por defecto
Analizando con más detalle tenemos lo siguiente:
4 rutas, /
, /about
, /blog
y /contact
Dos plantillas, la plantilla por defecto layout.tsx
y la plantilla personalizada layout-only-header.tsx
Haciendo referencia a la plantilla layout.tsx
mediante el index.tsx
/
/about
Hacemos referencia a la plantilla layout-only-header.tsx
mediante el index@only-header.tsx
tanto en src/routes/blog
como src/routes/contact
:
/blog
/contact
En este caso harán referencia a la plantilla personalizada nombrada que acabamos de crear mediante @only-header
. Si no añadimos nada después del index.tsx
de la ruta SIEMPRE seleccionará la plantilla correspondiente a src/routes/layout.tsx
, que es la plantilla por defecto.
Imaginaros que si hubiese una plantilla llamada layout-anartz.tsx
, para su uso deberíamos de hacer dentro del index de una ruta
, la referencia con index@anartz.tsx
.
Como hemos hecho antes, añadimos el siguiente contenido en layout-only-header.tsx
, prácticamente sería hacer lo mismo pero en este caso con plantillas nombradas en vez de agrupadas:
import { component$, Slot } from "@builder.io/qwik";
import { Header } from "~/components/core/header";
export default component$(() => {
return (
<>
<Header />
<section class="row">
<Slot />
</section>
</>
);
});
Y en los ficheros index@only-header.tsx
tanto de contact
como de blog
, debe de añadirse el mismo código que se ha añadido en el apartado anterior.
El resultado que debe de mostrar en las 4 rutas es el mismo que en las rutas agrupadas (Grouped Layouts)., por lo que debéis de hacer las mismas comprobaciones que el apartado anterior.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/Y3rUoeKkJI
Para facilitar el proceso y no perder tiempo eliminando y reconfigurando carpetas, partimos desde el código base de lo que se ha trabajado en Introducción a los layouts cuyo enlace lo tenéis a continuación:
https://shorten-up.vercel.app/fKWVTB0KPh
Usad ese proyecto, lo copiamos y lo renombramos como 04-04-nested-layouts
Como hemos visto en Routing (Enrutamiento) y en los apartados anteriores, si elegimos la ruta /
haciendo referencia a src/routes/index.ts
usará únicamente el layout
padre layout.tsx
y renderizará en base a esto, cogiendo el index.tsx
asociado a esta ruta:
Si tuviésemos la ruta /about
, sin usar ninguna plantilla adicional en las opciones asociadas a grouped
o named
layouts, lo que hará es coger, el layout.tsx
que es la plantilla predeterminada y cargará posteriormente el index.tsx
asociado a src/routes/about/index.tsx
.
Tanto en una como en otra, estamos cogiendo la plantilla principal SIN ANIDAR nuevas plantillas anidadas (plantillas hijas).
Para verlo más claro, vamos a ir implementándolo en el código.
Lo primero que vamos a hacer, es añadir estilos donde implementamos un borde rojo punteado en el div principal del src/routes/layout.tsx
principal para visualizarlo mejor, pasando de esto:
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<div>
<p>
<code>src/routes/layout.tsx</code>
</p>
<Slot />
</div>
);
});
A lo siguiente:
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<div style="border: 4px dotted red; padding: 25px; margin-top: 1rem">
<p>
<code>src/routes/layout.tsx</code>
</p>
<Slot />
</div>
);
});
En src/global.css
especificamos las reglas CSS para añadir la apariencia del selector <code></code>
:
code {
background: rgb(204, 203, 203);
font-size: x-large;
}
Y al guardar todos los cambios su resultado es:
Vamos a hacer que los contenidos de las páginas también tenga la referencia de la ruta del fichero y que tengan un borde azul. Lo realizamos tanto para la ruta principal /
y /about
:
src/routes/index.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<div style="border: 4px dotted blue; padding: 25px">
<p>
<code>src/routes/index.tsx</code>
</p>
<h1>Ruta "/"</h1>
Esta es la página principal
</div>
);
});
Cuyo resultado es el siguiente:
src/routes/about/index.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<div style='border: 4px dotted blue; padding: 25px'>
<p>
<code>src/routes/about/index.tsx</code>
</p>
<h1>Ruta "/about"</h1>
Esta es la página 'about'
</div>
);
});
Cuyo resultado es:
Para acercarnos al uso habitual que se da en los proyectos reales, es deseable anidar diseños entre sí, ya que el contenido de una página se podrá anidar en numerosos diseños envolviéndolos en diferentes niveles para hacer una personalización más ajustada al contenido que deseamos mostrar.
Esto estará determinado por la estructura del directorio en el que en una ruta NO principal (por ejemplo /about
), le añadimos para cargar su layout anidado al principal correspondiente, como se puede ver a continuación, con el objetivo de poder hacer más personalizado este apartado respecto a la página inicial:
src/
...
└── routes/
|── index.tsx # /
├── layout.tsx # Layout Padre - Inicial (Por defecto)
└── about/
├── layout.tsx # Layout hijo. Se anida después del padre
└── index.tsx # /about
# Se introduce en Slot del layout.tsx de "about"
En esta estructura que acabamos de ver, tenemos dos diseños que se van a aplicar dentro del componente de página la /about
.
Se realizará en este orden:
src/routes/layout.tsx
src/routes/about/layout.tsx
Finalmente se carga el contenido index.tsx
de src/routes/about/index.tsx
en el Slot
del layout.tsx
(src/routes/about/layout.tsx
) que se cargará todo agrupado en el Slot
de src/routes/layout.tsx
. Es decir, irá "navegando" de abajo arriba agrupando los layout hasta renderizarlos en base a lo que hemos pedido desde la URL.
En este caso, los diseños se van a anidar entre sí, con la página dentro de cada uno de ellos, siguiendo el orden primero desde el index.tsx
correspondiente al recurso pedido.
Después irá introduciéndose en los Slot de los layout.tsx
de abajo hacia arriba, hasta llegar al layout.tsx
principal, que será el que ya agrupe todo y lo muestre en el navegador.
Esto sería una idea de lo que se vería:
Aplicándolo al código, con nuestros colores, vamos a crear el fichero layout.tsx
dentro de src/routes/about
con el siguiente contenido:
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<div style="border: 4px dotted green; padding: 25px">
<p>
<code>src/routes/about/layout.tsx</code>
</p>
<Slot />
</div>
);
});
Cuyo resultado será el siguiente:
Recordando el orden de carga:
index.tsx
principal (src/routes/about/index.tsx
) en el primer Slot
.Slot
se encontrará a nivel de directorio about
(src/routes/about/layout.tsx
) y cogiendo todo eso agrupado finalmente en el Slot
que se ha añadido en el layout principal (src/routes/layout.tsx
)En este ejemplo lo haremos con dos hijos y lo bueno de esto es que si conseguimos entender bien esto, lo vamos a poder aplicar hasta con n
hijos, con los niveles que necesitemos.
Imaginaros que dentro de about
, queremos añadir otro nivel para abajo para mostrar unos detalles.
Lo que habría que hacer es adaptar la ruta a otro nivel más para abajo dentro del directorio about
quedando de la siguiente forma estructurado:
src/
...
└── routes/
|── index.tsx # /
├── layout.tsx # Layout padre
└── about/
├── layout.tsx # Layout hijo de routes/layout.tsx se anida a el
├── index.tsx # /about
└── about-one/ # NUEVO DIRECTORIO
├── layout.tsx # Layout hijo de about/layout.tsx y de layout.tsx
└── index.tsx # /about/about-one
En esta situación, tenemos 3 rutas disponibles:
/
, donde solo cargaría la plantilla /src/routes/layout.tsx
y dentro del Slot
de este src/routes/index.tsx
(primer ejemplo visto en los nested layouts)
/about
carga lo siguiente: src/routes/layout.tsx
=> Slot
=> src/routes/about/layout.tsx
=> src/routes/about/index.tsx
(visto en Anidando un layout hijo en una ruta NO principal)
/about/about-one
: Este caso que lo veremos a continuación, pasa por más layouts, haciendo el siguiente camino: src/routes/layout.tsx
=> Slot
=> src/routes/about/layout.tsx
=> src/routes/about/about-one/layout.tsx
=> src/routes/about/about-one/index.tsx
Aplicándolo al código debemos de realizar lo siguiente:
Crear el directorio about-one
dentro src/routes/about
.
Crear los ficheros index.tsx
y layout.tsx
dentro de src/routes/about/about-one
Añadir el contenido en los ficheros creados.
Y sabiendo esto, definimos el contenido de estos dos nuevos ficheros de la siguientes manera:
src/routes/about/about-one/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
export default component$(() => {
return (
<div style="border: 4px dotted orange; padding: 25px">
<p>
<code>src/routes/about/about-one/layout.tsx</code>
</p>
<Slot />
</div>
);
});
src/routes/about/about-one/index.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<div style='border: 4px dotted blue; padding: 25px'>
<p>
<code>src/routes/about/index.tsx</code>
</p>
<h1>Ruta "/about/about-one"</h1>
Esta es la página 'about/about-one'
</div>
);
});
Reiniciamos en la consola el proyecto (en el caso de que sea necesario), para que pueda coger los cambios de la nueva ruta que hemos añadido y vamos a probar ruta a ruta el resultado de cada una de ellas:
/
/about
Como se puede observar, al entrar en juego src/routes/about/layout.tsx
(2), se ha añadido una nueva plantilla con los bordes amarillos y posteriormente se carga el contenido de src/routes/about/index.tsx
(1)
/about/about-one
Y con esto terminaríamos todo lo relacionado a los Nested layouts (Layout anidados), como he mencionado, podríamos seguir añadiendo más niveles para abajo.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/km716DXVGk
Es posible que cualquier usuario visite una URL que no exista en nuestro sitio.
Imaginaros que un usuario visita http://localhost:5143/no-existe
.
Al no encontrarse ese endpoint para devolver una respuesta, el servidor deberá de responder con un código de estado HTTP 404 (https://shorten-up.vercel.app/SLZbtIEdSb), y la página debería tener al menos alguna explicación de lo que podría estar ocurriendo en lugar de mostrar una página en blanco.
Para cualquier ruta determinada, Qwik City puede elegir cómo manejar mejor la respuesta 404
para el usuario, estas son las formas que nos encontraremos:
Vamos a ir punto por punto viendo como funciona cada una de ellas.
En lugar de mostrar una página en blanco, por defecto Qwik City nos va a proporcionar una página genérica 404
para cualquier ruta que no esté disponible por no existir.
Para visualizar esta página no necesitamos hacer ningún ajuste ni configuración adicional en el proyecto.
La página 404 predeterminada se renderiza como una alternativa cuando no se encuentra una página personalizada 404.
La página predeterminada de Qwik City es muy útil, para notificarnos que la ruta seleccionada no es la correcta, ya que no existe.
Aun siendo útil lo ideal es que nosotros proporcionemos una página 404 personalizada para ofrecer una mejor experiencia al usuario, como proporcionar el encabezado y la navegación comunes para que el usuario pueda encontrar la página que busca.
Vamos a forzar que nos muestre una página de error predeterminada 404 al introducir una ruta no existente, que en este caso sería el siguiente caso, introduciendo la ruta /contact
(podéis poner cualquier otra ruta no registrada), ruta que no existe, por tener únicamente la inicial (/
):
1
: Nos notifican que la ruta introducida /contact
no existe y de ahí el mensaje 404 | /contact/ not found
.2
: Nos muestra la lista de rutas disponible, en este caso solo tenemos la de raíz, pero imaginaros que tenemos más rutas, nos mostraría todas las disponibles como sugerencia.3
: Al darse un código de error 404, en Red
, dentro de las herramientas de desarrollador nos muestra el estado del recurso que queríamos cargar, en color rojo, que si hacemos click, nos debe de mostrar algo como esto:Como se puede observar en la imagen anterior (la primera de este apartado), nos muestra la lista de rutas que SI
existen a la vez que nos dice que la que hemos seleccionado, no existe.
En lugar de mostrar la típica y aburrida respuesta genérica 404, tenemos la posibilidad de mostrar una página 404 personalizada utilizando la misma estructura de diseño que nuestra plantilla principal layout.tsx
.
Debemos de crear un nuevo proyecto para ir añadiendo los cambios y la sugerencia de nombre es 04-05-custom-404-page
Para crear una página 404 personalizada general, debemos de agregar un archivo 404.tsx
en el directorio raíz src/routes
.
src/
...
└── routes/
├── 404.tsx # Página 404 personalizada
├── layout.tsx # Diseño predeterminado
└── index.tsx # /
En este punto, la página 404.tsx
usará el diseño layout.tsx
, ya que es un archivo hermano del diseño en el mismo directorio.
Este fichero actuará como una ruta, como podría ser /
, /about
o cualquier otra ruta existente. El contenido del fichero 404.tsx
solo se mostrará cuando no exista la ruta que estamos introduciendo.
Además, al utilizar el enrutamiento basado en directorios de Qwik City, es posible crear páginas 404 personalizadas en diferentes rutas.
Por ejemplo, si también se agregamos src/routes/account/404.tsx
en la estructura, entonces la página 404 personalizada de la cuenta solo se aplicará a las rutas /account/*
, mientras que todas las demás páginas 404 utilizarán la página 404.tsx
del directorio raíz.
Definida de esta manera, así se reflejaría:
src/
...
└── routes/
├── account/
│ └── 404.tsx # Página 404 personalizada de account
│ └── index.tsx # /account
├── 404.tsx # Página 404 personalizada para todas las rutas
├── layout.tsx # Diseño predeterminado
└── index.tsx # /
Durante el desarrollo y en el modo de vista previa, las páginas 404 personalizadas no se renderizarán, sino que se mostrará la página 404 predeterminada de Qwik City.
Sin embargo, al compilar la aplicación para producción, la página 404 personalizada se generará estáticamente como un archivo 404.html
estático y podremos verlo.
Sabiendo esto, lo aplicamos a la práctica y realizamos estos cambios
Hay que tener en cuenta que las páginas 404 personalizadas se van a generar estáticamente en tiempo de compilación, lo que va a dar como resultado un archivo estático 404.html
, en lugar de renderizar páginas individuales generadas en el servidor.
Esta estrategia va a reducir la carga en nuestro servidor HTTP al evitar el renderizado de páginas 404 en el servidor, y así preservará los recursos.
Vamos a empezar a trabajar con el apartado práctico, basándonos en la estructura sugerida y luego ya veremos como se ve en desarrollo y como se vería en producción.
A continuación os muestro paso a paso como vamos a implementar la configuración de las páginas de error.
Recordad que debemos de subirlo a producción para poder ver su funcionamiento real.
Mientras trabajamos en desarrollo, podremos ver de manera previa como se renderiza estas páginas de error, aunque nos seguirá mostrando la página de error predeterminada, solo que ya aparecerán las páginas 404
que añadamos, sea en la ruta principal o en rutas secundarias como account
con su extensión html
que podremos visualizarlos a mano.
Los pasos que daremos en el ejemplo basándonos en la estructura que os he mencionado:
src/routes/404.tsx
:import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';
export default component$(() => {
return <div>Página de error <code>principal</code>. Solo existen las siguientes rutas:
<ul>
<li>Principal: <Link href='/'>Ir a inicio</Link></li>
<li>Account: <Link href='/account'>Ir a account</Link></li>
</ul>
</div>
});
src/routes/index.tsx
Aplicaremos el contenido que se renderizará correctamente para la página principal asociada a la ruta /
.
import { component$ } from "@builder.io/qwik";
import { Link, type DocumentHead } from "@builder.io/qwik-city";
export default component$(() => {
return (
<>
<h1>Bienvenido a mi página 👋</h1>
<p>Página principal</p>
<h2>Otras páginas de interés</h2>
<ul>
<li>
Account:{" "}
<Link href="/account" class="my-link">
Ir al account
</Link>
</li>
<li>
Account con detalles (error 404):{" "}
<Link href="/account/details" class="my-link">
Ir al account con error
</Link>
</li>
</ul>
</>
);
});
export const head: DocumentHead = {
...
};
Y esta es la apariencia que tendrá:
Vamos a añadirle el siguiente código CSS en el fichero src/global.css
para darle un poco de estilo:
body {
padding: 2rem;
line-height: inherit;
border: 2px dotted red;
margin: 1rem;
border-radius: 1rem;
}
h1 {
text-align: center;
border-bottom: 1px solid green;
}
h2 {
color: orange;
margin: 1rem;
}
li {
list-style: circle;
}
Y ahora se visualizará de la siguiente forma:
src/routes/account/index.tsx
Aplicaremos el contenido que se renderizará correctamente para la página asociada a la ruta /account
.
import { component$ } from "@builder.io/qwik";
import { Link, type DocumentHead } from "@builder.io/qwik-city";
export default component$(() => {
return (
<>
<h1>
Bienvenido a la página <code>account</code>
</h1>
<p>
Estamos en la página de <code>account</code>
</p>
<h2>Otras páginas de interés</h2>
<ul>
<li>
Principal:
<Link href="/" class="my-link">
Ir al inicio
</Link>
<li>
Blog (error 404):{" "}
<Link href="/blog" class="my-link">
Ir al blog con error (No existe)
</Link>
</li>
</li>
</ul>
</>
);
});
export const head: DocumentHead = {
...
};
src/routes/account/404.tsx
:Tal como hemos hecho anteriormente con el fichero 404.tsx
en src/routes
asignamos el código correspondiente para que se renderice este contenido siempre y cuando no existan las rutas que ejecutemos dentro del ámbito de account
.
import { component$ } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";
export default component$(() => {
return (
<div>
Página de error <code>account</code>. Solo existe la ruta{" "}
<code>/account</code>
<ul>
<li>
Principal: <Link href="/">Ir a inicio</Link>
</li>
<li>
Account: <Link href="/account">Ir a account</Link>
</li>
</ul>
</div>
);
});
En estos dos casos se han añadido dos enlaces a rutas QUE NO EXISTEN dentro de los ficheros index.tsx
y son las siguientes:
/blog
: En este caso debería de renderizar lo que está en src/routes/404.tsx
./account/details
: Al intentar acceder aquí, se renderizaría src/routes/account/404.tsx
.En desarrollo no se mostrarán directamente, pero vamos a verlo ahora ejecutando con el proyecto en marcha en desarrollo (yarn start
o npm start
).
Al iniciar el proyecto, podemos visualizar la página principal de la ruta /
:
Tendremos esta información a la vista:
1
: URL principal /
que accede al contenido de la página principal2
: Enlace a la página de la ruta /account
que SI existe.3
: Enlace a la ruta /blog
que no existe y nos saltaría un error teniendo que mostrar el contenido de src/routes/404.tsx
(recordad, en desarrollo no muestra directamente) por estar dentro de lo que sería el ámbito de raíz y no intentando acceder desde account
.Pulsamos en la opción 2
para ir a /account
y veremos lo siguiente:
Donde encontraremos la siguiente información:
1
: Enlace a la página de la ruta /account
que SI existe.2
: URL principal /
que accede al contenido de la página principal3
: Enlace a la ruta /account/details
que no existe y nos saltaría un error teniendo que mostrar el contenido de src/routes/account/404.tsx
por estar queriendo acceder a un recurso NO existente dentro de account
(recordad, en desarrollo no muestra directamente).Probando las rutas NO existentes
Siguiendo en /account
pulsamos en la opción 3
que nos debería de llevar a /account/details
, pero como no existe, nos debería de mostrar el contenido de src/routes/account/404.tsx
cosa que en desarrollo, nos mostrará de la siguiente forma:
Como se puede observar, se detecta que la ruta NO existe (1
) mostrándonos todas las rutas disponibles a las que podemos acceder.
Aquí vemos que las páginas de error 404
se generan con la extensión HTML y si queremos ver sus contenidos, en este caso deberemos de hacer click sobre ellos.
Para el caso de account/details
, la página que se mostrará es la correspondiente a /account/404.html
(2
) y la 404.html
(3
) sería para cualquier ruta que no este dentro del directorio account
.
Lo primero que vamos a hacer es acceder a una página que ya está publicada, en este caso en Netlify. Accedemos desde aquí:
https://shorten-up.vercel.app/4BPcbzZWxq
Y estaremos en la página inicial:
Probamos a acceder a la página /account/details
mediante Account con detalles...
que nos dará un error al no encontrarse ningún contenido asociado a esa ruta:
Y una vez que accedemos, ahora a diferencia de trabajar en desarrollo ya nos muestra el contenido de la ruta /account/details
con lo implementado en src/routes/account/404.tsx
:
Lo mismo podríamos hacer para rutas que no se encuentran dentro de /account
y como queremos verlo, vamos a /blog
que nos mostrará el contenido asociado a src/routes/404.tsx
:
Llegados a este punto, ya hemos aprendido a realizar la configuración de nuestras páginas de error 404 personalizadas.
Pasamos al desarrollo de estas con información dinámica.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/msBaqcy6mC
Al renderizar una página, lo predeterminado es siempre responder con un código de estado HTTP 200 (https://shorten-up.vercel.app/2ESgj-gxQx), que indica al navegador que todo está bien y la ruta existe.
En este caso, tenemos la posibilidad de manejar el renderizado de la página y establecer manualmente el código de estado de respuesta en algo diferente a 200, como 404 aunque no sería aplicar buenas prácticas.
Por ejemplo, supongamos que tenemos una página de producto con una URL como http://localhost:5143/product/abc
.
La página del producto se manejaría utilizando una ruta basada en directorios src/routes/product/[productId]/index.tsx
, donde [productId]
es un parámetro dinámico en la URL.
En este ejemplo, productId
se podría utilizar como clave para cargar los datos del producto desde la base de datos.
Como en este momento no tenemos disponible acceso a ninguna base de datos (y no es lo importante para probar esta funcionalidad), vamos a mockear una lista de productos, simulando una base de datos.
Tendremos dos casos:
Se encuentran los datos del producto, perfecto, se renderizarán correctamente los datos. Se devuelve el código de estado 200
.
NO se encuentran los datos del producto en la información que disponemos, aún podemos manejar el renderizado de esta página, pero en lugar de ello, responderemos con un código de estado HTTP 404 (status(404)
).
Vamos a verlo practicando y para ello necesitamos una fuente de datos / base de datos.
En este caso, trabajaremos con unos datos en local, añadiendo un fichero mock en src/data/products.ts
con este contenido:
export const products = [
{
id: "1",
name: "Smartphone",
description: "Teléfono inteligente de última generación con cámara de alta resolución, pantalla táctil y capacidad de conexión a internet.",
price: 500
},
{
id: "2",
name: "Laptop",
description: "Computadora portátil ligera y potente con pantalla de alta definición y capacidad para ejecutar programas y juegos exigentes.",
price: 1000
},
{
id: "3",
name: "Headphones",
description: "Auriculares inalámbricos con cancelación de ruido, sonido envolvente y comodidad ergonómica.",
price: 200
},
{
id: "4",
name: "Smart TV",
description: "Televisor inteligente con resolución Ultra HD, conectividad a Internet y acceso a aplicaciones y servicios de streaming.",
price: 800
},
{
id: "5",
name: "Digital Camera",
description: "Cámara fotográfica digital con alta resolución, zoom óptico y capacidad de grabación de video en alta definición.",
price: 400
},
{
id: "6",
name: "Tablet",
description: "Dispositivo portátil con pantalla táctil de alta resolución, conexión inalámbrica y capacidad para descargar aplicaciones.",
price: 300
},
{
id: "7",
name: "Printer",
description: "Dispositivo de impresión que permite imprimir documentos y fotografías en diferentes formatos y con calidad profesional.",
price: 150
}
];
Ahora creamos dentro de src/routes
un directorio product
y dentro de este último un nuevo directorio llamado [productId]
que servirá para añadir dinámicamente el valor id
del producto para seleccionar cualquier producto de nuestra tienda o comercio electrónico.
Quedaría de la siguiente forma:
src/
...
└── routes/
├── product/
| └── [productId]/
| └── index.tsx # /product/1234
...
├── layout.tsx # Diseño predeterminado
└── index.tsx # /
Y a continuación añadimos un nuevo elemento, llamado routeLoader$
que sirve para cargar los datos en el servidor y al inicio de la ejecución de la ruta, con el objetivo que estén disponibles para su uso dentro de los Componentes de Qwik.
Se activan cuando ocurre la navegación de SPA / MPA, por lo que pueden ser invocados por los Componentes de Qwik durante el proceso de renderizado.
routeLoader$()
Más información sobre esta función a continuación:
https://shorten-up.vercel.app/pSwaIJLiXH
Y en este fichero creado en src/routes/product/[productId]/index.tsx
, aplicamos el código, donde realizaremos la búsqueda de los datos mockeados en base al id
del producto introducido:
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
// Datos mock en un fichero local para simular una base de datos
import { products } from '~/data/product';
export const useProductLoader = routeLoader$(async ({ params, status }) => {
// Ejemplo de llamada a la base de datos (local) utilizando el parámetro productId que será el valor "id" de nuestros productso introducido desde la ruta
// Como he mencionado, haremos una búsqueda en el mock
// El resultado podría devolverse como null si no se encuentra el producto
const data = products.find((item) => item.id === params.productId);
// Verifica si hemos encontrado el producto
if (!data) {
// No se encontraron datos del producto
// Establecer el código de estado en 404 (por defecto 200)
status(404);
}
// Devolver los datos y su código estado 200
return data;
});
export default component$(() => {
// Obtener los datos del producto desde el hook creado que funciona como un useSignal
const product = useProductLoader();
...
});
Y con esto, debemos de obtener el resultado y evaluar que resultado sea satisfactorio (status(200)
) o no, en el caso de que no encuentre nada devolviendo un status(404)
, mostrándolo de la siguiente forma:
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { products } from '~/data/products';
export const useProductLoader = routeLoader$(async ({ params, status }) => {
...
});
export default component$(() => {
// Obtener los datos del producto desde el hook creado
const product = useProductLoader();
// Evaluamos si existe ese producto
if (!product.value) {
// No se encontraron datos del producto
// Por lo tanto, renderizamos nuestra propia página personalizada de producto 404
// y está notificado al navegador que su estado es el 404 que se ha especificado en el routeLoader$
return <p>Lo sentimos, parece que no tenemos este producto.</p>;
}
// Se encontraron los datos del producto (código estado 200), así que los renderizamos
return (
<div>
<h1>{product.value?.name}</h1>
<p>{product.value?.price}</p>
<p>{product.value?.description}</p>
</div>
);
});
Verificamos las dos situaciones haciendo uso de la siguiente URL teniendo en cuenta el parámetro productId
:
http://localhost:5173/product/<productId>/
Comenzamos con las pruebas y pasamos el id
cuyo valor será 1
que corresponde a un Smartphone
.
La ruta será http://localhost:5173/product/1/
ejecutándose en el navegador y el resultado será lo siguiente.
Y como se puede observar, obtenemos la información del producto seleccionado, ya que existe y está disponible para mostrarse.
Si observamos dentro de Red
, en las herramientas de desarrollador
podemos observar que registra el código de estado 200
, notificandonos que ha encontrado el recurso pedido.
En cambio, si añadimos un id
que no existe como 1234
siendo la ruta http://localhost:5173/product/1234/
el resultado debería de ser el siguiente:
Y aquí es donde nos muestra el mensaje de disculpa, diciéndonos que no existe ese producto y si observamos de nuevo en Red
, podemos observar que registra el error 404
, mencionando que no ha encontrado ese recurso.
El código que encontráis es el resultado final de todo el proceso realizado durante este último punto. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.
El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/WyQJ8X6Z5j
Al finalizar este capítulo, deberíamos de ser capaces de:
Hemos podido aprender las diferentes formas que tenemos de como mostrar diferentes plantillas en base a las diferentes variantes que nos proporciona Qwik.
Esto nos permite afrontar los diferentes objetivos o situaciones en las que necesitemos hacer variantes, como podría ser una parte privada y una pública, como ejemplo destacable.
Se han aplicado los conceptos 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 junto con el uso de componentes que hemos aprendido anteriormente.
Estos conceptos ya nos van a permitir empezar a trabajar con nuestros proyectos, ya que tenemos la base inicial necesaria para poder crear lo que es el esqueleto de nuestras aplicaciones.