Skip to content

Get Started Astro Tutorial — 8. Page with list of posts

00:00 / 00:00

8. Home Page – Post List

Let's get to the main page. In this case, we want to display a list of published posts.

How does this work in Astro?

At build time, we read all the posts and generate the listing page. The final result is a static HTML file served to the client with the list of posts.

To do this, we'll create a posts folder, where we define the model and API to fetch the list.

One interesting thing about Content Island is that we can directly generate the model of an entity in TypeScript from the application.

./posts/post.model.ts

import type { Media } from '@content-island/api-client';

export interface SinglePost {
  id: string;
  title: string;
  slug: string;
  date: string;
  summary: string;
  author: string;
  content: string;
  image: Media;
}

Now let's move on to the API. To fetch the list of posts, we use getContentList, specify the content type name, and map the content to our model.

./posts/posts.api.ts

import client from "../../lib/client";
import type { SinglePost } from "./post.model";
import { mapContentToModel } from "@content-island/api-client";

export async function getPosts(): Promise<SinglePost[]> {
  const posts = await client.getContentList<SinglePost>({
    contentType: "Post",
  });

  return posts;
}

And on the home page, we fetch the posts and display them.

In the server-side generation code, we read the list of posts and add a function to format the date.

./src/pages/index.astro

---
import { Image } from 'astro:assets';
import Layout from '../layouts/Layout.astro';
import { getPosts } from './posts/post.api';

const posts = await getPosts();

const date = (date: string) =>
  new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
---

Let's show the data without styling for now:

<Layout>
  <h1>Welcome to Astro Blog</h1>
  <a href="/about">About</a>
  <p>Latest posts:</p>

  <ul>
    {
      posts.map(post => (
        <li>
          <h2>
            <a href={`/posts/${post.slug}`}>{post.title}</a>
          </h2>
          <p>
            <strong>Author:</strong> {post.author}
          </p>
          <p>{post.summary}</p>
        </li>
      ))
    }
  </ul>
</Layout>

It works… although it looks pretty ugly. Should we add some styling?

Let’s add some styles at the end of the file:

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

And replace what we put in Layout with this:

Make sure to import:

---
+ import { Image } from 'astro:assets';
<Layout>
  <section class="container">
    <div class="posts__header">
      <h1>CurioVerso Blog</h1>
      <p>All Posts</p>
    </div>

    <ul class="posts__list">
      {
        posts.map(post => (
          <li>
            <article class="post">
              <a
                href={`/posts/${post.slug}`}
              >
                <Image
                  src={post.image.url}
                  alt={post.image.name}
                  width={250}
                  height={100}
                  class:list={'post__image'}
                  loading="eager"
                />
              </a>
              <div class="post__text">
                <div class="post__header">
                  <p class="post__date">
                    <time datetime="2025-03-05">{date(post.date)}</time>
                  </p>
                  <a
                    href={`/posts/${post.title
                      .toLowerCase()
                      .trim()
                      .replace(/[^a-z0-9\s-]/g, '')
                      .replace(/\s+/g, '-')}`}
                    class="post__title"
                  >
                    <h2>{post.title}</h2>
                  </a>
                </div>
                <div>
                  <p class="post__description">{post.summary}</p>
                </div>
              </div>
            </article>
          </li>
        ))
      }
    </ul>
  </section>
</Layout>

Looks nice! 🙂 Ready to tackle the page that displays a single post?