DEV Community

Cover image for AstroJS 5.1: Integra contenido de Dev.to de manera sencilla
Juan Martin Ruiz
Juan Martin Ruiz

Posted on

1

AstroJS 5.1: Integra contenido de Dev.to de manera sencilla

Empezar un nuevo proyecto con el template blog

npm create astro@latest -- --template blog

Image description

Levantamos la app

cd [nombre_proyecto] && npm run dev

Image description

index de la pagina

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

Image description

Image description

Image description

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
Enter fullscreen mode Exit fullscreen mode

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 };

Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

index de la pagina

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());

---


Enter fullscreen mode Exit fullscreen mode

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>
                        ))
                    }
Enter fullscreen mode Exit fullscreen mode

click devto article

devto article

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

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay