Empezar un nuevo proyecto con el template blog
npm create astro@latest -- --template blog
Levantamos la app
cd [nombre_proyecto] && npm run dev
A partir de Astro 5, se ha introducido la Content Layer API, una herramienta que permite cargar datos desde cualquier fuente durante la construcción de tu sitio y acceder a ellos mediante una API sencilla y con tipado seguro.
Esta API ofrece flexibilidad para manejar contenido de diversas fuentes, como archivos locales en Markdown, APIs remotas o sistemas de gestión de contenido (CMS). Al definir "colecciones" de contenido con esquemas específicos, puedes estructurar y validar tus datos de manera eficiente. Además, la Content Layer API mejora el rendimiento en sitios con gran cantidad de contenido, acelerando los tiempos de construcción y reduciendo el uso de memoria.
https://astro.build/blog/astro-5/
Content Layer API de Astro para integrar publicaciones de dev.to en tu sitio
Puedes utilizar la Content Layer API de Astro para integrar publicaciones de dev.to en tu sitio. Aunque no existe un cargador (loader) específico para dev.to, puedes crear uno personalizado que consuma su API y almacene las publicaciones en una colección de contenido en Astro.
Para lograr esto, sigue estos pasos:
1. Configura el acceso a la API de dev.to
Crear el archivo .env
en el root del proyecto
.env
DEV_TO_API_URL=https://dev.to/api/
DEV_API_KEY=tu_clave_de_api
2. Define una colección en Astro
En src/content.config.ts
, define una colección para las publicaciones de dev.to utilizando la Content Layer API:
Al crear el proyecto con el template de Astro, nos genera automaticamente la coleccion para el Blog
src\content.config.ts
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog };
Ahora creamos la coleccion para lor articlos de Dev.to
const devTo = defineCollection({
loader: async () => {
const headers = new Headers({
"api-key": DEV_API_KEY,
});
const posts = await fetch(`${DEV_TO_API_URL}articles?username=jmr85`, {
headers: headers
}).then(res => res.json());
return posts.map((post: any) => ({
id: post.slug,
title: post.title,
description: post.description,
pubDate: new Date(post.published_at),
updatedDate: post.edited_at ? new Date(post.edited_at) : null,
heroImage: post.cover_image || post.social_image,
url: post.url,
}));
},
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().nullable(),
url: z.string(),
}),
});
export const collections = { blog, devTo };
Asi queda el codigo completo de
src\content.config.ts
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';
const { DEV_API_KEY, DEV_TO_API_URL } = import.meta.env;
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
const devTo = defineCollection({
loader: async () => {
const headers = new Headers({
"api-key": DEV_API_KEY,
});
const posts = await fetch(`${DEV_TO_API_URL}articles?username=jmr85`, {
headers: headers
}).then(res => res.json());
return posts.map((post: any) => ({
id: post.slug,
title: post.title,
description: post.description,
pubDate: new Date(post.published_at),
updatedDate: post.edited_at ? new Date(post.edited_at) : null,
heroImage: post.cover_image || post.social_image,
url: post.url,
body_markdown: post.content,
}));
},
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().nullable(),
url: z.string(),
body_markdown: z.string().optional(),
}),
});
export const collections = { blog, devTo };
Fíjese el detalle en la definición de los campos en schema
, los campos tienen que coincidir con la collection
de blog del template de Astro y luego agregar los que son particulares de la collection de los posts de Dev.to. Tienen que tener el mismo nombre el tipo de dato, es para que podamos "fusionar" en la sección de Blog los posts markdown del template de Astro con los de Dev.to.
3. Utiliza las publicaciones en tus páginas:
Ahora puedes acceder a las publicaciones de dev.to en tus componentes o páginas de Astro utilizando getCollection
:
Originalmente:
src\pages\blog\index.astro
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro';
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!-- Iteracion los posts -->
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>
<img width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
Combinamos las colecciones blog de astro y blog de dev.to y volver a ordenar las publicaciones para garantizar que el orden cronológico sea correcto.
src\pages\blog\index.astro
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro';
const astroPosts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const devtoPosts = (await getCollection("devTo")).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const posts = [...astroPosts, ...devtoPosts]
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
Ahora la iteracion de los posts
lo haremos con un condicional inline que en caso que sea devto rediriga a la url del articulo del sitio de https://dev.to/{username}/{slug-article}
{
posts.map((post) => (
<li>
<a href={post.collection === "devTo" ? post.data.url : `/blog/${post.id}/`}>
<img width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
Nota: Tener en cuenta que si crea un nuevo post desde Dev.to tiene que volver a compilar el proyecto con npm run build && npm run preview
o recompilar desde la plataforma deployada ya que este proyecto web es totalmente estático, por defecto en Astro 5 es estatico (hibrido y estático). En caso que quiera que el sitio sea dinámico NO tiene que hacer el fetch()
desde el archivo de content.config.ts
, las partes que quiera que sean dinámicas tiene que usar prerender = false
e instalar un adaptador para node o de la plataforma que precise. Pero la naturaleza estática de Content Layer es intencional y es parte de lo que hace que sea tan eficiente en términos de rendimiento, pero también es la razón por la que no es adecuado para datos que necesitan actualizarse frecuentemente o en tiempo real.
Repositorio: https://github.com/jmr85/astro-devto
Top comments (0)