Saltearse al contenido

Tutorial: Primeros pasos con Nextjs — 8. Página con lista de posts

00:00 / 00:00

8. Página con lista de posts

Vamos a por la página principal. En este caso, queremos mostrar un listado de los posts que se han publicado.

¿Cómo funciona esto en Nextjs en modo estático? En tiempo de compilación, leemos todos los posts y montamos la página de listado. El resultado final es un HTML estático que se sirve al cliente con el listado de posts.

Para ello, vamos a definir el modelo y añadiremos la API para traernos la lista. Aquí lo mismo que hemos hecho con About, vamos al portal de Content Island, en la pestaña Modelo, en el modelo Post pulsamos el botón de TypeScript, lo copiamos y actualizamos el fichero api.model.ts:

./src/api.model.ts


+ export interface Post {
+   id: string;
+   language: 'en';
+   title: string;
+   slug: string;
+   date: string; // Stores the date in ISO 8601 format. For example: 2021-09-10T19:30:00.000Z
+   author: string;
+   summary: string;
+   image: Media;
+   content: string;
+ }

Y ahora vamos a por la API. Si recuerdas, para la página de About estamos usando el método getContent pero esto solo nos está trayendo los datos de un solo contenido, esta vez, necesitamos traernos un listado de posts, por lo que vamos a usar el método getContentList:

  • Nos traemos el nuevo modelo.
  • Creamos el método asíncrono getPosts que nos devuelve un array de Post.
  • Usamos el método getContentList filtrando por el contentType de Post.

./src/api.ts

- import type { About } from "./api.model";
+ import type { About, Post } from "./api.model";
import { client } from "./lib/client";

export async function getAbout(): Promise<About> {
  return await client.getContent<About>({
    contentType: "About",
  });
}

+ export async function getPosts(): Promise<Post[]> {
+   return  await client.getContentList<Post>({
+     contentType: "Post",
+   });
+ }

Y en la página principal, vamos a traernos los posts y mostrarlos. Al igual que antes, vamos a empezar a mostrar los datos sin diseño y luego le añadimos un poco de estilo.

  • Importamos la API de posts.
  • Marcamos el componente como async y usamos await para esperar a que se carguen los datos.
  • Y añadimos el HTML de cada campo.

./src/app/page.tsx

+ import { getPosts } from "../api";

- export default function Home() {
+ export default async function Home() {
+   const posts = await getPosts();

    return (
+     <>
        <h1>Hello Blog!</h1>
+       <ul>
+         {posts.map((post) => (
+           <li key={post.id}>
+             <h2>{post.title}</h2>
+             <p>{post.author}</p>
+             <p>{post.summary}</p>
+           </li>
+         ))}
+       </ul>
+     </>
    );
  }

Se ve... aunque bien feo. ¿Le añadimos diseño?

./src/app/page.module.css

.posts__header {
  text-align: center;
  margin-bottom: var(--space-lg);
  & > h1 {
    font-size: var(--fs-3xl);
    font-family: var(--ff-title);
  }
}
.posts__list {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  justify-items: center;
  gap: var(--space-lg);
}
.post {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.post__image {
  width: 100%;
  height: auto;
  object-fit: cover;
  object-position: top;
  aspect-ratio: 16/9;
  border-radius: var(--border-radius);
}
.post__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.post__header {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.post__date {
  font-size: var(--fs-2xs);
  opacity: 0.7;
}

.post__title {
  text-decoration: none;
  color: var(--text-color);
  transition: color 0.3s ease-in-out;
  &:hover {
    color: var(--title-hover-color);
  }

  & > h2 {
    font-size: var(--fs-lg);
    font-family: var(--ff-title);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    line-height: 1.175;
  }
}

.post__description {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  overflow: hidden;
}

Y actualizamos el componente con los nuevos estilos y el nuevo HTML:

./src/app/page.tsx

import { getPosts } from "../api";
+ import { Post } from "../api.model";
+ import classes from "./page.module.css";

export default async function Home() {
  const posts = await getPosts();
+ const formatDate = (date: string) =>
+   new Date(date).toLocaleDateString('en-US', {
+     year: 'numeric',
+     month: 'long',
+     day: 'numeric',
+   });

  return (
-   <>
+   <section className="container">
-     <h1>Hello Blog!</h1>
+     <div className={classes.posts__header}>
+       <h1>CurioVerso Blog</h1>
+       <p>All Posts</p>
+     </div>
-     <ul>
+     <ul className={classes.posts__list}>
        {posts.map((post) => (
          <li key={post.id}>
-           <h2>{post.title}</h2>
-           <p>{post.author}</p>
-           <p>{post.summary}</p>
+           <article className={classes.post}>
+             <a href={`/posts/${post.slug}`}>
+               <img
+                 src={post.image.url}
+                 alt={post.image.name}
+                 width={250}
+                 height={100}
+                 className={classes.post__image}
+                 loading="eager"
+               />
+             </a>
+             <div className={classes.post__text}>
+               <div className={classes.post__header}>
+                 <p className={classes.post__date}>
+                   <time dateTime={post.date}>{formatDate(post.date)}</time>
+                 </p>
+                 <a href={`/posts/${post.slug}`} className={classes.post__title}>
+                   <h2>{post.title}</h2>
+                 </a>
+               </div>
+               <div>
+                 <p className={classes.post__description}>{post.summary}</p>
+               </div>
+             </div>
+           </article>
          </li>
        ))}
      </ul>
-   </>
+   </section>
  );
}

¡Ha quedado bien! :), ¿Nos animamos ahora con la página que muestra un solo post?