Qwik — Layouts

Claves para trabajar con los layouts (plantillas) aplicando buenas prácticas

Anartz Mugika Ledo🤗
14 min readFeb 14, 2023

Comenzamos un nuevo artículo en el que vamos a trabajar de manera más profunda con las plantillas en Qwik en base a las rutas que iremos seleccionando.

Para poder trabajar en este artículo es fundamental haber leído y entendido los artículos anteriores, el correspondiente al enrutamiento (Routing) y al trabajo por componentes.

Recordad, que si hay dudas, aun leyéndolo, podéis preguntarlo en los comentarios.

Una vez entendido como podemos trabajar con las diferentes formas del enrutamiento en Qwik junto con los componentes, podremos aplicar estos conocimientos a las plantillas, en las diferentes situaciones que podamos enfrentarnos en un proyecto.

Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:

Qwik paso a paso desde 0 al detalle

23 stories

Requisitos a tener en cuenta

  • Haber completado la lectura de los artículos anteriores.
  • Tener instalado Node 16 ó superior.
  • Ganas de seguir aprendiendo.
  • Deseable tener algo de experiencia con otras tecnologías trabajando con diferentes rutas.
  • Descargar el proyecto e instalar las dependencias para empezar desde 0.

Una vez realizada la introducción del artículo, os expongo a continuación los puntos que vamos a tratar en este artículo, ¡¡espero que sea de vuestro agrado!!

  • Introducción a los layouts
  • Plantillas Agrupadas (Grouped Layouts)
  • Plantillas mediante Nombres (Named layouts).
  • Plantillas Anidadas (Nested Layouts)

Sabiendo los puntos a trabajar vamos a empezar poco a poco, para seguir avanzando en el curso.

Introducción a los layouts

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 es 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.

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.

Ahora iniciamos la ejecución del proyecto que hemos descargado (e instalado las dependencias) al principio:

npm start

Al iniciar el proyecto, la estructura de la plantilla inicial será lo que vemos a continuación:

Para especificar el contenido de manera dinámica, tendremos el Slot (3) para ir cargando el contenido de las páginas mediante la selección de la ruta y por otro lado, los apartados comunes serán Header (1), Menu (2) y Footer (4).

En estos momentos, con el proyecto tenemos dos rutas configuradas, la inicial /y la de /about que podemos visualizarla accediendo a http://localhost:5143/about:

Como se puede observar, tenemos la carga de contenido de las rutas únicamente en el Slot, lo demás, sigue igual.

Sabiendo lo de 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 # Implementación plantilla usando: <Header>, <Footer> y<Menu>
├── about/
│ └── index.tsx # http://localhost:5143/about
└── index.tsx # http://localhost:5143

Vamos a extraer desde layout en los siguientes componentes, para tenerlo todo más ordenado con opción a reutilizar esa información, por si hacemos diferentes plantillas con componentes como el footer que se repiten prácticamente en todos los apartados (o cualquier otro que podamos encontrar en otras base de plantilla).

Comenzamos seleccionando la zona a extraer desde layout, 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 ya haciendo uso del componente que acabamos de crear:

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:

export const Menu = () => {
return (
<div class='column menu-items'>
<h2>Menu</h2>
<ul>
<li>Home</li>
<li>About</li>
</ul>
</div>
);
};

Y añadimos el componente aislado en el layout, como hemos hecho con Header:

Ahora lo que nos quedaría sería hacerlo con el Footer, no tiene mucho misterio, seguimos con los mismo pasos enfocados con este apartado:

Añadir ese código de manera aislada en el componente Footer:

export const Footer = () => {
return (
<footer>
<h2>Footer</h2>
</footer>
);
};

Añadir el componente en el layout:

Ahora que ya se han realizado los 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 buenas prácticas.

Esto es lo básico de trabajar con plantillas, ahora vamos a empezar ya a cosas más especificas 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.

¡¡Vamos a por ello!!

Si queréis ver el código y los cambios que se han realizado desde el inicio del tutorial hasta este punto, os dejo el enlace al repositorio con la rama correspondiente a lo trabajado

Grouped layouts

Partimos de lo que hemos trabajado y creamos una nueva carpeta llamada (complete) donde dejaremos agrupados los elementos que usarán la plantilla completa ignorando completamente el texto complete en el enrutamiento como bien hemos visto en el anterior artículo sobre el Routing.

Lo implementamos de la siguiente forma:

Y dentro del contenido del fichero src/routes/(only-header)/layout.tsx tendremos lo siguiente, muy importante que no olvidemos el componente Slot, ya que sin esto, no cargará el contenido de la ruta asociada:

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 el contenido ya estará aislada con su layout principal.

Seguimos y creamos otro grupo donde le vamos a implementar el nombre only-header, que usaremos con una plantilla que vamos a personalizar con únicamente el Header + el Slot donde carga el contenido, añadiendo los siguientes elementos:

  • Carpetas (contact y blog) con sus respectivos ficheros index.tsx (index.mdx si lo deseamos)
  • Fichero layout.tsx y copiamos el contenido que tenemos en (complete)/layout.tsx, eliminando el menú izquierdo y el footer.

La estructura de ficheros se reflejaría de la siguiente forma:

El código aplicado en esta dos nuevas páginas:

  • src/routes/(only-header)/blog/index.tsx
import { component$ } from "@builder.io/qwik";

export default component$(() => {
return (
<div>
<h1>Ruta "/blog"</h1>
Esta es la página <code>blog</code> con la plantilla <code>only-header</code>
</div>
);
});
  • src/routes/(only-header)/contact/index.tsx
import { component$ } from "@builder.io/qwik";

export default component$(() => {
return (
<div>
<h1>Ruta "/contact"</h1>
Esta es la página <code>contact</code> con la plantilla <code>only-header</code>
</div>
);
});

Aplicando los cambios, si navegamos a las 4 rutas:

Only Header, solo cabecera + contenido

  • /blog (only-header)
  • /contact (only-header)

Página completa con complete

  • / (complete)
  • /about (complete)

Llegados a este punto, ya sabemos como trabajar con los Grouped Layouts para poder tener de manera agrupada las rutas que corresponde a un tipo de layout, para poder hacer variantes de layouts para nuestros objetivos.

Si queréis ver el código y lo trabajado hasta este punto, os dejo el enlace al repositorio con la rama correspondiente a lo trabajado

Named Layouts

Para comenzar este apartado, para no estar eliminando y reconfigurando las carpetas, os recomiendo que partáis desde el código que se ha implementado en la “Introducción a los 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 de sus hermanos. 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, cualquier número de diseños aplicando los diseños con nombre.

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.

Un diseño con nombre también va a comenzar con el texto layout + - (guión) + nombre + .tsx que no se podrá duplicar obviamente.

Si el nombre fuese only-header la plantilla la llamaríamos layout-only-header.tsx.

Para seguir trabajando en este apartado, vamos a crear esta estructura de directorios y ficheros:

Lo que contiene ahora es lo siguiente:

  • 4 rutas, /, /about, /blog y /contact
  • Dos plantillas, la plantilla por defecto layout.tsx y la plantilla personalizada layout-only-header.tsx
  • Rutas / y /about usan la plantilla por defecto.
  • /blog y /contact usarán la plantilla layout-only-header.tsx debido a que en sus ficheros index, hemos añadido el texto @only-header .

Detallando más que rutas usarán una plantilla u otra:

Plantilla (layout.tsx)

Haciendo referencia a la plantilla layout.tsx mediante el index.tsx

  • /
  • /about

Plantilla (layout-only-header.tsx)

Haciendo referencia a la plantilla layout-only-header.tsx mediante el index@only-header.tsx

  • /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 layout.tsx.

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).

Si queréis ver el código y lo trabajado en este punto, os dejo el enlace al repositorio con la rama correspondiente a lo trabajado

Nested Layouts

Para comenzar este apartado, para no estar eliminando y reconfigurando las carpetas, os recomiendo que partáis desde este código base

Ya sabemos como hemos visto en el artículo del routing y en los apartados anteriores, que 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 como 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 layout.tsx principal, 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">
<p>
<code>src/routes/layout.tsx</code>
</p>
<Slot />
</div>
);
});

Cuyo 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:

Anidando un layout hijo dentro de una ruta NO principal

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 # http://localhost:5143/
├── layout.tsx # Layout Padre - Inicial
└── about/
├── layout.tsx # Layout hijo. Se anida después del padre
└── index.tsx # http://localhost:5143/about // Se introduce en Slot del layout.tsx de about

En esta estructura de arriba, tenemos dos diseños que se van a aplicar alrededor dentro del componente de página /about.

  1. src/routes/layout.tsx
  2. src/routes/about/layout.tsx
  3. 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 y luego 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.

Aplicándolo al código, 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 yellow; padding: 25px">
<p>
<code>src/routes/about/layout.tsx</code>
</p>
<Slot />
</div>
);
});

Cuyo resultado será el siguiente:

Recordando el orden de carga, primero carga el index.tsx principal (src/routes/about/index.tsx) en el primer Slot de este que 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)

Anidando dos layout hijos dentro de una ruta NO principal

En este ejemplo lo haremos con dos hijos, pero entendiendo bien esto lo vamos a poder aplicar hasta n hijos, 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

src/
└── routes/
|── index.tsx # http://localhost:5143/
├── layout.tsx # Layout padre
└── about/
├── layout.tsx # Layout hijo de routes/layout.tsx se anida a el
└── index.tsx # http://localhost:5143/about
└── about-one/
├── layout.tsx # Layout hijo de about/layout.tsx y de layout.tsx
└── index.tsx # http://localhost:5143/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: src/routes/layout.tsx => Slot => src/routes/about/layout.tsx => src/routes/about/about-one/layout.tsx => src/routes/about/about-one/index.tsx (lo vemos ahora)

Trabajando con la ruta http://localhost:5143/about/about-one nos encontraremos con esta situación:

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.

La estructura actual de directorios y ficheros será la siguiente:

Y el contenido de estos dos nuevos ficheros lo especificamos 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, para que pueda coger los cambios de la nueva ruta que hemos añadido y vamos a probar, a ver como queda con la nueva ruta:

  • /
  • /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, como he mencionado, podríamos seguir añadiendo más niveles para abajo, siguiendo lo que se ha implementado como conceptos en este artículo.

Si queréis ver el código y lo trabajado en este punto, os dejo el enlace al repositorio con la rama correspondiente a lo trabajado

Conclusión

Hemos podido aprender las deferentes formas que tenemos de como mostrar diferentes plantillas en base a las diferentes formas que nos proporciona Qwik para 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 en entregas anteriores.

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.

En el siguiente artículo hablaré de como trabajar aplicando buenas prácticas con los estilos, usando las funciones useStyles$ y useStylesScopes$

Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:

Qwik paso a paso desde 0 al detalle

23 stories

Presencia en redes sociales

Podéis encontrarme en las siguientes redes.

--

--

Anartz Mugika Ledo🤗

[{#frontend:[#mobile:{#android, #kotlin, #ionic}}, {#web:{#angular, #qwik, #bootstrap}}],{#backend: [{#graphql, #nestjs,#express, #mongodb, #mysql}]}]