Esta bueno tener un blog personal, pero hay momentos en que necesitamos hacer una publicación en una red social por diferentes razones. Si no desea duplicar las publicaciones de su blog con una publicación de una red social, podemos traer el contenido de la publicación y usar nuestro sitio web como una fuente principal de contenido, donde incluso podemos unir las diferentes publicaciones en una sola colección de publicaciones para que podamos aplicar filtros más adelante. Además, si no queremos ocupar el proyecto con muchas imágenes en el sistema de archivos local o tenemos que pagar el alojamiento de imágenes, o simplemente no queremos escribir en texto "simple" con Markdown. O, simplemente, en el caso que tenga su homepage desarrollada con Astro y en la seccion de Blog solo quiere traer los post de Hashnode, Wordpress, Medium, etc.
En este caso, lo haremos con Devto.
Tabla de contenido
- Requisitos previos
- 1. ¿Por qué Astro?
- 2. Empezar un nuevo proyecto con el template blog
- 2.1. Levantamos la app
- 3. Content Layer API de Astro para integrar publicaciones de dev.to en tu sitio
- 3.1. Configura el acceso a la API de dev.to
- 3.2. Define una colección en Astro
- 4. Utiliza las publicaciones en tus páginas
- 5. Use post de Devto en la página de Blog Slug (opcional)
- 5.1. Mostrar contenido del Post de Devto dentro de Astro
- 6. Nota
Requisitos previos
- Conocimiento fundamental de Typescript y React
- node.js ≥ 16.12.0
- Un editor de código VScode (opcional)
- Git (opcional)
1. ¿Por qué Astro?
Astro es el marco web para construir sitios web orientados al contenido, como blogs, marketing y comercio electrónico. Astro es conocido por ser pionero en una nueva arquitectura frontend llamada "Islas" para reducir la sobrecarga de JavaScript y la complejidad en comparación con otros marcos. Está optimizado por el rendimiento gracias a su estrategia de "cero JavaScript por defecto", solo se cargan los JS necesarios en las partes interactivas, lo que resulta en tiempos de carga rápidas y una excelente optimización de SEO. Es flexible con los marcos/bibliotecas de la interfaz de usuario; Es agnóstico, lo que significa que puede integrar componentes de React, Vue, Svelte, combinando lo mejor de cada uno. Y, por último, su facilidad de uso; Su sintaxis basada en HTML y su sistema de archivos simple hacen que la curva de aprendizaje sea muy suave, incluso si proviene de un fondo más tradicional.
2. Empezar un nuevo proyecto con el template blog
Esto es en caso de que no tenga un sitio creado, si lo hace, la implementación no variará mucho con un proyecto realizado con Astro.
Vamos a crear un blog con la plantilla ofrecida por la comunidad oficial.
npm create astro@latest -- --template blog
2.1 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/
3. 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:
3.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
3.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/me/published`, {
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/me/published`, {
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.
4. 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>
))
}
5. Use post de Devto en la página de Blog Slug (opcional)
En caso de que desee incluir el contenido de la publicación media dentro de Astro, debemos modificar src/content.config.ts
, src/pages/blog/index.astro
y src/paginas/blog/[... slug].astro
// src\content.config.ts
const devTo = defineCollection({
loader: async () => {
const headers = new Headers({
"api-key": DEV_API_KEY,
});
const posts = await fetch(`${DEV_TO_API_URL}articles/me/published`, {
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,
heroImage: post.cover_image || post.social_image,
url: post.url,
content: post.body_markdown,
}));
},
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
url: z.string(),
content: z.string(),
}),
});
5.1 Mostrar contenido del Post de Devto dentro de Astro
Necesitamos modificar el src\pages\blog\[…slug].astro
para mostrar la publicación en particular.
Dado que nuestro sitio está en modo estático de forma predeterminada, utilizaremos getStaticPaths()
para generar las rutas estáticamente en el momento de la compilación para mostrar la publicación en particular.
// src\pages\blog\[…slug].astro
export async function getStaticPaths() {
const blogPosts = await getCollection('blog');
const mediumPosts = await getCollection('devTo') || [];
const posts = [...blogPosts, ...mediumPosts];
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
Antes de usar los componentes propios de Astro para mostrar el post de Devto instalamos la libreria marked
que nos va servir para parsear con contenido en formato markdown a HTML
# https://www.npmjs.com/package/marked
npm i marked
Astro viene con unas funciones y componentes que sirven para renderizar nuestro contenido markdown tanto locales como de una API externa.
Utilizamos la función Render()
que viene con Astro para compilar las publicaciones de blog y <Content />
es como un componente de renderizado en sí, no habría necesidad de crear un componente especializado para representar todo el contenido de Markdown pero solo nos sirve para los markdown locales. En el caso de representar el contenido de publicaciones de Devto, utilizaremos el componente Fragment. <Fragment />
es un componente Astro incorporado que le permite evitar un elemento de envoltura innecesaria. Esto puede ser especialmente útil al obtener HTML de un CMS (por ejemplo, hashnode o WordPress). Como el contenido de Devto es de tipo markdown primero lo parseamos a HTML con la libreria marked
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
import { render } from 'astro:content';
import { marked } from 'marked';
export async function getStaticPaths() {
const blogPosts = await getCollection('blog');
const mediumPosts = await getCollection('devTo') || [];
const posts = [...blogPosts, ...mediumPosts];
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'blog'> | CollectionEntry<'devTo'>;
const post = Astro.props;
const isDevto = post.collection === "devTo";
const htmlContent = marked.parse(isDevto ? post.data.content : '');
const { Content } = await render(post);
---
<BlogPost {...post.data}>
<!-- renders local Markdown content -->
<Content />
<!-- renders the Devto post content directly as HTML -->
<Fragment set:html={htmlContent } />
<!-- renders a link to the Devto post at the end of the content -->
<a
href={isDevto ? post.data.url: ''}
target="_blank"
>
{isDevto && post.data.url}
</a>
</BlogPost>
Ejecutamos el proyecto
npm run dev
6. Nota
Tener en cuenta que si crea un nuevo post desde Dev.to tiene que volver a compilar el proyecto con npm run build
desde la plataforma deployada (Netlify, Vercel, Render, Cloudflare) 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.
Otras alternivas es aplicar ISR (Incremental Static Regeneration), este artículo explica muy bien como hacerlo. Otra opción es automatizar el deploy del proyecto con GitHub Actions. Y por último, para no sacrificar el beneficio de un sitio estático, se puede aplicar Deploy Hooks en cualquier plataforma de Hosting.
Repositorio: https://github.com/jmr85/astro-devto
Top comments (1)