Saltearse al contenido

Tutorial: Primeros pasos con Astro — 9. Montando un post

00:00 / 00:00

9. Montando un post

Para la página en la que queremos mostrar el detalle de un post vamos a reaprovechar mucho de lo que hemos hecho en el paso anterior con la página de listado de posts. Y te estarás preguntando: ¿Y eso por qué, si aquí estoy sólo cargando un post? Esto es así por como trabaja astro. En tiempo de generación, se leen todos los posts y se genera una página por cada post, por lo que el proceso es muy similar al de la página de listado de posts.

Lo que sí, para generar una página por post, en el nombre de la página le indicaremos que tire de un "[slug]", ese slug es el nombre del post en formato de url amistosa, con lo que si tenemos un post con el nombre "Mi primer post" la url generada será algo así como "posts/mi-primer-post.html".

Vamos a por ello, debajo la carpeta "posts" creamos una fichero "[slug].astro"

Este slug si te fijas está entre corchetes y es un parametro dinámico, lo que significa que astro generará una página por cada post que tengamos.

Configuramos la generación de markdown y sintax highlighting:

./src/pages/posts/[slug].astro

---
import { marked } from 'marked';
import Layout from '../../layouts/Layout.astro';
import { getPosts } from './post.api';
import hljs from 'highlight.js';

const renderer = new marked.Renderer();
renderer.code = ({ text, lang }) => {
  const validLang = hljs.getLanguage(lang || 'plaintext') ? lang || 'plaintext' : 'plaintext';
  const highlighted = hljs.highlight(text, { language: validLang }).value;
  return `<pre><code class="hljs ${validLang}">${highlighted}</code></pre>`;
};

marked.setOptions({ renderer });
---

Y dentro utilizando "getStaticPaths" generamos un slug por cada post. Añade justo antes de las rejas:

export async function getStaticPaths() {
  const posts = await getPosts();
  return posts.map(post => ({
    params: {
      slug: post.slug
    },
    props: { post },
  }));
}

Y ahora, ya partimos de que estamos en una página concreta y justo debajo del código que acabamos de teclear, añadimos lo siguiente:

const { post } = Astro.props;

const postContent = marked(post.content || '');
const postDate = new Date(post.date).toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
});

const postImage = `url(${post.image.url})`;

Aquí:

  • Leemos de las props la información del post en concreto.
  • Convertimos el markdown del contneido del post a HTML utilizando marked.
  • La imagen la vamos a usar en el css para ponerla de fondo, así que la convertimos a una url.

Y ahora, vamos a mostrarlo de forma rápida:

<Layout>
  <article>
    <h1>{post.title}</h1>
    <p><strong>Date:</strong> {post.date}</p>
    <p><strong>Author:</strong> {post.author}</p>
    <img src={post.image.url} alt={post.image.name} width="800" />
    <p><strong>Summary:</strong> {post.summary}</p>
    <div>
      <strong>Content:</strong>
      <p>{post.content}</p>
    </div>
  </article>
</Layout>

Y ya que estamos, vamos a ponerlo bonito con unos estilos:

Fijate que aquí le vamos a pasar postImage como variable de CSS, para poder usarla en el background de la página.

<style define:vars={{ postImage }}>
  .background::before {
    content: '';
    position: absolute;
    z-index: -1;
    top: 0;
    left: 0;
    width: 100%;
    height: 360px;
    background-image: var(--postImage);
    background-size: cover;
    background-repeat: no-repeat;
    background-position: top center;
  }

  .article {
    max-width: 800px;
    margin-right: auto;
    margin-left: auto;
    margin-top: var(--space-2xl);
    padding: var(--space-lg);
    background-color: var(--pure-white);
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
  }

  .article__header {
    text-align: center;
  }
  .article__title {
    font-size: var(--fs-3xl);
    font-family: var(--ff-title);
  }

  .article__content {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);

    h2 {
      margin-top: var(--space-sm);
    }
    ul {
      padding-left: var(--space-md);
    }
    img {
      width: 100%;
      object-fit: cover;
      aspect-ratio: 21/9;
      border-radius: var(--border-radius);
    }

    code {
      font-size: var(--fs-xs);
      border-radius: var(--border-radius);
      padding: var(--space-xs);
    }
  }
</style>

Y cambiamos el markup:

<Layout>
  <div class="container background">
    <article class="article">
      <header class="article__header">
        <p>
          <time datetime="2025-03-05">{postDate}</time>
        </p>
        <h1 class="article__title">{post.title}</h1>
      </header>
      <div set:html={postContent} class="article__content" />
      <footer>
        <p>Written by <strong>{post.author}</strong></p>
      </footer>
    </article>
  </div>
</Layout>

¡Y ya lo tenemos!