Skip to content

Get Started NextjsTutorial — 8. Page with list of posts

00:00 / 00:00

8. Page with list of posts

Now let’s build the main page. In this case, we want to display a list of published posts.

How does this work in static mode with Next.js? At build time, we fetch all the posts and generate the listing page. The final result is a static HTML page served to the client with the list of posts.

To do this, we’ll define the model and add the API to fetch the list. Just like we did for About, we go to the Content Island portal, open the Model tab, click on the Post model TypeScript button, copy it and update our api.model.ts file:

./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;
+ }

Now we build the API. Remember, for the About page we used getContent, but that only fetches a single item. This time, we want a list of posts, so we’ll use getContentList:

  • Import the new model.
  • Create an async method getPosts that returns an array of Post.
  • Use getContentList, filtering by the Post contentType.

./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",
+   });
+ }

Then in the homepage, we fetch and display the posts. As before, we’ll first show the data without styles, then enhance it later.

  • Import the getPosts API.
  • Mark the component as async and use await to fetch the data.
  • Display each field’s data in HTML.

./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>
+     </>
    );
  }

It works… though it looks pretty plain. Let’s add some styling.

./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;
}

Now update the component to use the new styles and markup:

./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>
  );
}

Looks great! :)
Shall we move on to the single post page?