Saltearse al contenido

Contentful & Astro

Contentful es un CMS headless que te permite administrar contenido, integrar con otros servicios y publicar en múltiples plataformas.

En esta sección, usaremos el SDK de Contentful para conectar tu espacio de Contentful con Astro sin JavaScript en el lado del cliente.

Para empezar, necesitarás lo siguiente:

  1. Un proyecto Astro - Si aún no tienes un proyecto Astro, nuestra guía de instalación te ayudará a comenzar en cuestión de minutos.

  2. Una cuenta de Contentful y un espacio de Contentful. Si aún no tienes una cuenta, puedes registrarte para obtener una cuenta gratuita y crear un nuevo espacio de Contentful. También puedes usar un espacio existente si ya tienes uno.

  3. Credenciales de Contentful - Puedes encontrar las siguientes credenciales en tu panel de control de Contentful Settings > API keys. Si no tienes ninguna clave de API, crea una seleccionando Add API key.

    • ID del espacio de Contentful - El ID de tu espacio de Contentful.
    • Token de acceso de entrega de Contentful - El token de acceso para consumir contenido publicado de tu espacio de Contentful.
    • Token de acceso de previsualización de Contentful - El token de acceso para consumir contenido no publicado de tu espacio de Contentful.

Para agregar las credenciales de tu espacio de Contentful a Astro, crea un archivo .env en la raíz de tu proyecto con las siguientes variables:

.env
CONTENTFUL_SPACE_ID=TU_ID_DE_ESPACIO
CONTENTFUL_DELIVERY_TOKEN=TU_TOKEN_DE_ENTREGA
CONTENTFUL_PREVIEW_TOKEN=TU_TOKEN_DE_PREVISUALIZACION

Ahora, puedes usar estas variables de entorno en tu proyecto.

Si deseas tener IntelliSense para tus variables de entorno de Contentful, puedes crear un archivo env.d.ts en el directorio src/ y configurar ImportMetaEnv de la siguiente manera:

src/env.d.ts
interface ImportMetaEnv {
readonly CONTENTFUL_SPACE_ID: string;
readonly CONTENTFUL_DELIVERY_TOKEN: string;
readonly CONTENTFUL_PREVIEW_TOKEN: string;
}

Tu directorio raíz ahora debería incluir estos nuevos archivos:

  • Directorysrc/
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Para conectar tu proyecto Astro con tu espacio de Contentful, instala los siguientes paquetes usando el comando de tu gestor de paquetes preferido:

Ventana de terminal
npm install contentful @contentful/rich-text-html-renderer

Después, crea un nuevo archivo llamado contentful.ts en el directorio src/lib/ de tu proyecto.

src/lib/contentful.ts
import contentful from "contentful";
export const contentfulClient = contentful.createClient({
space: import.meta.env.CONTENTFUL_SPACE_ID,
accessToken: import.meta.env.DEV
? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
: import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});

La viñeta de código de arriba crea un nuevo cliente de Contentful, pasando las credenciales del archivo .env.

Finalmente, tu directorio raíz ahora debería incluir estos nuevos archivos:

  • Directorysrc/
    • env.d.ts
    • Directorylib/
      • contentful.ts
  • .env
  • astro.config.mjs
  • package.json

Los componentes de Astro pueden obtener datos de tu cuenta de Contentful usando el contentfulClient y especificando el content_type.

Por ejemplo, si tienes un tipo de contenido “blogPost” que tiene un campo de texto para un título y un campo de texto enriquecido para el contenido, tu componente podría verse así:

---
import { contentfulClient } from "../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { EntryFieldTypes } from "contentful";
interface BlogPost {
contentTypeId: "blogPost",
fields: {
title: EntryFieldTypes.Text
content: EntryFieldTypes.RichText,
}
}
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
---
<body>
{entries.items.map((item) => (
<section>
<h2>{item.fields.title}</h2>
<article set:html={documentToHtmlString(item.fields.content)}></article>
</section>
))}
</body>

Puedes encontrar más opciones de consulta en la documentación de Contentful.

Creando un blog con Astro y Contentful

Sección titulada Creando un blog con Astro y Contentful

Con la configuración de arriba, ahora puedes crear un blog que use Contentful como CMS.

  1. Un espacio Contentful - Para este tutorial recomendamos comenzar con un espacio vacío. Si ya tienes un modelo de contenido, puedes usarlo, pero deberás modificar nuestros fragmentos de código para que coincidan con tu modelo de contenido.
  2. Un proyecto Astro integrado con el SDK de Contentful - Consulta integrando con Astro para obtener más detalles sobre cómo configurar un proyecto Astro con Contentful.

Configurando un modelo de Contentful

Sección titulada Configurando un modelo de Contentful

Dentro de tu espacio de Contentful, en la sección Content model, crea un nuevo modelo de contenido con los siguientes campos y valores:

  • Nombre: Publicación de Blog
  • Identificador de API: blogPost
  • Descripción: Este tipo de contenido es para una publicación de blog

En el tipo de contenido recién creado, usa el botón Add Field para agregar 5 nuevos campos con los siguientes parámetros:

  1. Campo de texto
    • Nombre: título
    • Identificador de API: title (deja los otros parámetros con sus valores predeterminados)
  2. Campo de fecha y hora
    • Nombre: fecha
    • Identificador de API: date
  3. Campo de texto
    • Nombre: slug
    • Identificador de API: slug (deja los otros parámetros con sus valores predeterminados)
  4. Campo de texto
    • Nombre: descripción
    • Identificador de API: description
  5. Campo de texto enriquecido
    • Nombre: contenido
    • Identificador de API: content

Presiona Save para guardar los cambios.

En la sección Content de tu espacion de Contentful, crea una nueva entrada haciendo clic en el botón Add Entry. Luego, completa los campos:

  • Title: ¡Astro es increíble!
  • Slug: astro-es-increíble
  • Descripción: Astro es un nuevo generador de sitios estáticos que es increíblemente rápido y fácil de usar.
  • Fecha: 2022-10-05
  • Content: ¡Este es mi primer publicación blog!

Presiona Publish para guardar la entrada. Acabas de crear tu primer post.

¡Siéntete libre de agregar tantos posts como quieras, luego cambia a tu editor de código favorito para comenzar a hackear con Astro!

Mostrando una lista de publicaciones de blog

Sección titulada Mostrando una lista de publicaciones de blog

Crea una nueva interfaz llamada BlogPost y agrégala a tu archivo contentful.ts en src/lib/. Esta interfaz coincidirá con los campos de tu tipo de contenido de publicación de blog en Contentful. Lo usarás para tipar tu respuesta de entradas de publicaciones de blog.

src/lib/contentful.ts
import contentful, { EntryFieldTypes } from "contentful";
export interface BlogPost {
contentTypeId: "blogPost",
fields: {
title: EntryFieldTypes.Text
content: EntryFieldTypes.RichText,
date: EntryFieldTypes.Date,
description: EntryFieldTypes.Text,
slug: EntryFieldTypes.Text
}
}
export const contentfulClient = contentful.createClient({
space: import.meta.env.CONTENTFUL_SPACE_ID,
accessToken: import.meta.env.DEV
? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
: import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});

Ahora, ve a la página de Astro donde obtendrás los datos de Contentful. En este ejemplo usaremos la página de inicio index.astro en src/pages/.

Importa la interfaz BlogPost y contentfulClient de src/lib/contentful.ts.

Obtén todos los registros de Contentful con un tipo de contenido de blogPost mientras pasas la interfaz BlogPost para tipar tu respuesta.

src/pages/index.astro
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
---

Esta llamada de búsqueda devolverá una arreglo de tus publicaciones en entries.items. Puedes usar map() para crear una nueva matriz (posts) que formatee tus datos devueltos.

El ejemplo de abajo devuelve las propiedades items.fields de nuestro modelo de contenido para crear una vista previa de la publicación del blog, y al mismo tiempo, reformatea la fecha a un formato más legible.

src/pages/index.astro
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const posts = entries.items.map((item) => {
const { title, date, description, slug } = item.fields;
return {
title,
slug,
description,
date: new Date(date).toLocaleDateString()
};
});
---

Finalmente, puedes usar posts en tu plantilla para mostrar una vista previa de cada publicación.

src/pages/index.astro
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const posts = entries.items.map((item) => {
const { title, date, description, slug } = item.fields;
return {
title,
slug,
description,
date: new Date(date).toLocaleDateString()
};
});
---
<html lang="es">
<head>
<title>Mi Blog</title>
</head>
<body>
<h1>Mi Blog</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.slug}/`}>
<h2>{post.title}</h2>
</a>
<time>{post.date}</time>
<p>{post.description}</p>
</li>
))}
</ul>
</body>
</html>

Generando publicaciones de blog individuales

Sección titulada Generando publicaciones de blog individuales

Usa el mismo método para obtener tus datos de Contentful que arriba, pero esta vez, en una página que creará una ruta de página única para cada entrada del blog.

Si estás usando el modo estático predeterminado de Astro, usarás rutas dinámicas y la función getStaticPaths(). Esta función se llamará en tiempo de compilación para generar la lista de rutas que se convierten en páginas.

Crea un archivo nuevo llamado [slug].astro en src/pages/posts/.

De la misma manera que importaste en index.astro, importa la interfaz BlogPost y contentfulClient de src/lib/contentful.ts.

Esta vez, obtén tus datos dentro de una función getStaticPaths().

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
}
---

Luego, mapea cada elemento a un objeto con una propiedad params y props. La propiedad params se usará para generar la URL de la página y la propiedad props se pasará al componente de página como props.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const pages = entries.items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
content: documentToHtmlString(item.fields.content),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
---

La propiedad dentro de params debe coincidir con el nombre de la ruta dinámica. Como nuestro nombre de archivo es [slug].astro, usamos slug.

En nuestro ejemplo, el objeto props pasa tres propiedades a la página:

  • título (una cadena)
  • contenido (un documento de texto enriquecido convertido a HTML usando documentToHtmlString)
  • fecha (formateada usando el constructor Date)

Finalmente, puedes usar las propiedades de la página para mostrar tu publicación.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
export async function getStaticPaths() {
const { items } = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
const pages = items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
content: documentToHtmlString(item.fields.content),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
const { content, title, date } = Astro.props;
---
<html lang="en">
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<time>{date}</time>
<article set:html={content} />
</body>
</html>

Navega a http://localhost:4321/ y haz clic en uno de tus artículos para asegurarte de que tu ruta dinámica esté funcionando.

Si has optado por el modo SSR, usarás una ruta dinámica que usa un parámetro slug para obtener los datos de Contentful.

Crea un archivo nuevo llamado [slug].astro en src/pages/posts/. Usa Astro.params para obtener el slug de la URL, luego pásalo a getEntries:

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
---

Si la entrada no fue encontrada, puedes redirigir al usuario a la página 404 usando Astro.redirect.

src/pages/posts/[slug].astro
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
} catch (error) {
return Astro.redirect("/404");
}
---

Para pasar los datos de la publicación a la sección de plantilla, crea un objeto post fuera del bloque try/catch.

Usa documentToHtmlString para convertir content de un documento a HTML, y usa el constructor de Date para formatear la fecha. title se puede dejar como está. Luego, agrega estas propiedades a tu objeto post.

src/pages/posts/[slug].astro
---
import Layout from "../../layouts/Layout.astro";
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
let post;
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
const { title, date, content } = data.items[0].fields;
post = {
title,
date: new Date(date).toLocaleDateString(),
content: documentToHtmlString(content),
};
} catch (error) {
return Astro.redirect("/404");
}
---

Finalmente, puedes usar post para mostrar tu publicación en la sección de plantilla.

src/pages/posts/[slug].astro
---
import Layout from "../../layouts/Layout.astro";
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";
let post;
const { slug } = Astro.params;
try {
const data = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
"fields.slug": slug,
});
const { title, date, content } = data.items[0].fields;
post = {
title,
date: new Date(date).toLocaleDateString(),
content: documentToHtmlString(content),
};
} catch (error) {
return Astro.redirect("/404");
}
---
<html lang="en">
<head>
<title>{post?.title}</title>
</head>
<body>
<h1>{post?.title}</h1>
<time>{post?.date}</time>
<article set:html={post?.content} />
</body>
</html>

Para desplegar tu sitio web, visita nuestras guías de despliegue y sigue las instrucciones de tu proveedor de alojamiento preferido.

Reconstrucción al cambiar contenido en Contentful

Sección titulada Reconstrucción al cambiar contenido en Contentful

Si tu proyecto esta usando el modo estático por defecto de Astro, necesitarás configurar un webhook para que se ejecute una nueva compilación cuando tu contenido cambie. Si estás usando Netlify o Vercel como proveedor de alojamiento, puedes usar su función de webhook para ejecutar una nueva compilación desde eventos de Contentful.

Para configurar un webhook en Netlify:

  1. Ve a tu panel de control y haz clic en Build & deploy.

  2. En la pestaña Continuous Deployment, encuentra la sección Build hooks y haz clic en Add build hook.

  3. Proporciona un nombre para tu webhook y selecciona la rama en la que quieres que se ejecute la compilación. Haz clic en Save y copia la URL generada.

Para configurar un webhook en Vercel:

  1. Ve a tu panel de control y haz clic en Settings.

  2. En la pestaña Git, encuentra la sección Deploy Hooks.

  3. Proporciona un nombre para tu webhook y selecciona la rama en la que quieres que se ejecute la compilación. Haz clic en Add y copia la URL generada.

En tu espacio de Contentful, ve a settings y haz clic en la pestaña Webhooks. Crea un nuevo webhook haciendo clic en el botón Add Webhook. Proporciona un nombre para tu webhook y pega la URL del webhook que copiaste en la sección anterior. Finalmente, haz clic en Save para crear el webhook.

Ahora, cada vez que publiques una nueva publicación de blog en Contentful, se iniciará una nueva compilación y se actualizará tu blog.

Más guías de CMS