Tutorial: Primeros pasos con Nextjs — 8. Página con lista de posts
- 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
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
getPostsque nos devuelve un array dePost. - Usamos el método
getContentListfiltrando por elcontentTypedePost.
./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
asyncy usamosawaitpara 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?