Tutorial: Primeros pasos con Nextjs — 7. Montando la página de About
- 1. Creando el proyecto en Content Island
- 2. Definiendo el modelo de datos
- 3. Añadiendo datos
- 4. Creando el proyecto en Nextjs
- 5. Creando la estructura de carpetas
- 6. Conectando Content Island con Nextjs
- 7. Montando la página de About
- 8. Página con lista de posts
- 9. Montando un post
- 10. Desplegando en Vercel
7. Montando la página de About
Vamos a montar la página de About, aquí podemos tomar dos aproximaciones:
Una sería montar el HTML de forma estática en Nextjs y después enlazar los datos de Content Island.
La otra es traernos los datos de Content Island, montar un HTML sin estilo, conectarlo todo y, cuando esté todo listo, añadir los estilos.
Ambas aproximaciones son igual de válidas, depende de tus preferencias y de cómo te sientas más cómodo. Quizás, si eres desarrollador, prefieras asegurarte que los datos cargan y luego, quizás pasarle este trabajo a un diseñador y que haga su magia. :)
Vamos a por la segunda opción, nos ponemos nuestras gafas de desarrollador y empezamos a trabajar.
Lo primero, estamos trabajando con TypeScript, vamos a definir el modelo que nos traemos de About page.
Si nos vamos a Content Island, en la pestaña de modelo, podemos ver que tenemos un modelo de About, con los siguientes campos:
- picture: Foto.
- fullname: nombre.
- shortBio: Mini bio.
- extendedBio: Bio extendida.
Podemos pulsar en el botón de TypeScript, copiar la interfaz y pegarla en nuestro código.
Para ello, vamos a crear un fichero api.model.ts en la raíz del proyecto, donde definiremos todos los modelos de Content Island. De momento, vamos a pegar aqui la interfaz de About que acabamos de copiar:
./src/api.model.ts
import type { Media } from '@content-island/api-client';
export interface About {
id: string;
language: 'en';
picture: Media;
fullname: string;
shortBio: string;
extendedBio: string;
}Y ahora vamos a definir una API para extraer estos datos.
¿Qué vamos a hacer aquí?
- Definir una función asíncrona
getAboutque nos traiga los datos de Content Island. - Aquí tiramos de la librería de Content Island y nos traemos los datos de
About. Para ello, usamos la funcióngetContent, a la que le pasamos elcontentTypedeAbout. - Como solamente tenemos un contenido de este tipo, no necesitamos pasarle ningun
id.
./src/api.ts
import type { About } from "./api.model";
import { client } from "./lib/client";
export async function getAbout(): Promise<About> {
return await client.getContent<About>({ contentType: "About" });
}
Ahora que lo tenemos, vamos a la página de About a montar un layout muy básico para ver que se están mostrando los datos.
Como estamos en Nextjs usando el App Router, podemos marcar el componente como async y usar await para esperar a que se carguen los datos. Por defecto, los componentes se ejecutan en el servidor, así que no hay problema en usar funciones asíncronas.
./src/app/about/page.tsx
+ import { getAbout } from "../../api";
- export default function About() {
+ export default async function About() {
+ const about = await getAbout();
return (
+ <div>
<h1>About</h1>
+ <img src={about.picture.url} alt={about.fullname} />
+ <h2>{about.fullname}</h2>
+ <p>
+ <strong>Short Bio:</strong> {about.shortBio}
+ </p>
+ <p>
+ <strong>Extended Bio:</strong> {about.extendedBio}
+ </p>
+ </div>
);
}
Ya va teniendo buena pinta, pero falta una cosa: el campo de bio extendida. Este campo tiene algo especial y es que está en Markdown. Como lo estamos generando de forma dinámica vamos a instalar una librería para procesarlo.
npm install markedAhora vamos a procesar el markdown:
./src/app/about/page.tsx
+ import { marked } from 'marked';
import { getAbout } from "../../api";
export default async function About() {
const about = await getAbout();
+ const aboutExtendedBio = marked(about.extendedBio || '');
return (
<div>
<h1>About</h1>
<img src={about.picture.url} alt={about.fullname} />
<h2>{about.fullname}</h2>
<p>
<strong>Short Bio:</strong> {about.shortBio}
</p>
<p>
- <strong>Extended Bio:</strong> {about.extendedBio}
+ <strong>Extended Bio:</strong>
+ <div dangerouslySetInnerHTML={{ __html: aboutExtendedBio }} />
</p>
</div>
);
}
¡Anda, ya tiene un mejor aspecto! Ahora vamos a darle un poco de estilo:
./src/app/about/page.module.css
.about {
display: flex;
flex-direction: column;
gap: var(--space-lg);
}
.about__header {
display: flex;
flex-direction: column;
gap: var(--space-md);
}
@media screen and (min-width: 768px) {
.about__header {
flex-direction: row;
align-items: flex-end;
}
}
.about__intro {
display: flex;
flex-direction: column;
gap: var(--space-xs);
& > h1 {
font-size: var(--fs-4xl);
font-family: var(--ff-title);
width: 10ch;
line-height: 1.1;
}
& > p {
max-width: 30ch;
font-size: var(--fs-md);
}
}
@media screen and (min-width: 768px) {
.about__intro {
padding-bottom: var(--space-md);
}
}
.about__image {
width: 100%;
height: auto;
object-fit: cover;
aspect-ratio: 16/9;
border-radius: var(--border-radius);
}
@media screen and (min-width: 768px) {
.about__image {
width: 50%;
aspect-ratio: 10/16;
}
}
@media screen and (min-width: 1024px) {
.about__image {
width: 30%;
}
}
.about__bio {
max-width: 800px;
margin-right: auto;
margin-left: auto;
display: flex;
flex-direction: column;
gap: var(--space-lg);
& img {
width: 100%;
object-fit: cover;
aspect-ratio: 21/9;
border-radius: var(--border-radius);
}
}Y vamos a cambiar el html:
./src/app/about/page.tsx
import { marked } from "marked";
import { getAbout } from "../../api";
+ import classes from "./page.module.css";
export default async function About() {
const about = await getAbout();
const aboutExtendedBio = marked(about.extendedBio || "");
return (
- <div>
+ <div className={`container ${classes.about}`}>
- <h1>About</h1>
+ <div className={classes.about__header}>
<img
+ className={classes.about__image}
src={about.picture.url}
alt={about.fullname}
+ width={100}
+ height={200}
/>
- <h2>{about.fullname}</h2>
- <p>
- <strong>Short Bio:</strong> {about.shortBio}
- </p>
+ <div className={classes.about__intro}>
+ <h1>
+ I'm, <br /> {about.fullname}
+ </h1>
+ <p>{about.shortBio}</p>
+ </div>
+ </div>
- <p>
- <strong>Extended Bio:</strong>
- <div dangerouslySetInnerHTML={{ __html: aboutExtendedBio }} />
- </p>
+ <div
+ className={classes.about__bio}
+ dangerouslySetInnerHTML={{ __html: aboutExtendedBio }}
+ />
</div>
);
}Vaya, esto tiene buena pinta. :)
¿Vamos a por la lista de posts?