DEV Community

viistorrr
viistorrr

Posted on • Edited on

Integrar Dev.to API + NextJS + Typescript + TailwindCSS

[Dev.to + NextJS + Typescript + TailwindCSS]

En la actualización de mi website, estaba buscando cómo y dónde publicar mi blog, soy Frontend y no quería usar solamente wordpress porque - entre otras cosas no me gusta😆 - y tratando de solucionar qué hacer, encontré Dev.to que además me ofrece una API bastante sencilla de utilizar, para hacer ésta integración encontré un post e intenté seguirlo pero me pareció algo confuso, por eso acá les voy a mostrar lo más sencillo que pueda la forma como hice la implementación para que tu también logres hacerlo si quieres publicar tus post de Dev.to en tu sitio web personal o para que se lo ofrezcas a algún cliente, simplemente en el momento que consideres te pueda servir.

Debo aclarar que estoy asumiendo que ya tienes conocimientos previos de NextJS y Typescript, por eso me voy a saltar la parte de la creación del proyecto, seteado y todas esas cosas, vamos a ir directamente a la parte de la integración del blog

Antes debo aclarar que vamos a usar NextJS con Typescript, por eso la extensión de los archivos que verás son .tsx ahora si, vamos a lo que nos gustar, al código! 👨🏾‍💻

Crea una carpeta blog dentro de pages y agregas el archivo index.tsx

pages structure

En éste index.tsx vamos a tener la lista de todos tus posts en Dev.to, por eso, al final es a gusto personal la manera como quieras tener la UX, te mostraré lo sencilla de la mía porque no quería tener mucho diseño en ésta parte, pues lo que nos importa es cómo queda el código

Ahora, en pages/blog/index.tsx lo primero es conectarnos al api de Dev.to, ésto lo vamos a hacer con SSR de NextJS usando getServerSideProps es muy sencillo, sólo debes llamar a la URL

https://dev.to/api/articlesusername=${process.env.DEV_USERNAME}

quiere decir que debes tener claro tu nombre de usuario configurado en tu archivo .env

DEV_USERNAME=tunombredeusuario

Luego de ésto, el llamado a Dev.to API debe quedar así👇🏾

export const getServerSideProps = async () => {
  const devDotToPosts = await fetch(
    `https://dev.to/api/articles?username=${process.env.DEV_USERNAME}`
  );

  const res = await devDotToPosts.json();

  return {
    props: {
      devDotToPosts: res,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

NOTA: Vas a ver un componente ******<Layout> </Layout> pero no es más que un wrapper donde tengo un Header y Footer, no es más que ésto, eso lo podés modificar y hacerlo completamente a tu gusto

<div className="relative pt-6 pb-16 sm:pb-24 font-monserrat">
   <Header />
       <div className="flex overflow-hidden bg-white py-8 px-4 sm:px-2 lg:px-4 lg:py-6 justify-center">
          {children}
     </div>
   <Footer />
 </div>
Enter fullscreen mode Exit fullscreen mode

Dicho eso, vamos con la portada del blog pages/blog/index.tsx debe verse algo como ésto👇🏾

import Link from "next/link";
import Layout from "@components/Layout";
import { VImage } from "@components/image/VImage";

const Tags = (tags) => {
  return tags.tags.map((tag) => (
    <strong key={tag} className="text-xs text-primary">
      #{tag}{" "}
    </strong>
  ));
};

const Blog = ({ devDotToPosts }) => {
  return (
    <Layout>
      <div className="container mx-auto px-4">
        <div className="flex flex-wrap -mx-4">
          {devDotToPosts &&
            devDotToPosts.map((post) => (
              <div className="w-full md:w-1/2 lg:w-1/3 px-4 mb-8" key={post.id}>
                <Link href={`/blog/posts/${encodeURIComponent(post.slug)}`}>
                  <a>
                    <div className="bg-white rounded-lg shadow-lg overflow-hidden">
                      <VImage
                        src={post.social_image}
                        alt={post.title}
                        width={500}
                        height={200}
                      />
                      <div className="p-4">
                        <Tags tags={post.tag_list} />
                        <p className="text-secondary text-sm mt-1">
                          {post.description}
                        </p>
                      </div>
                    </div>
                  </a>
                </Link>
              </div>
            ))}
        </div>
      </div>
    </Layout>
  );
};

export const getServerSideProps = async () => {
  const devDotToPosts = await fetch(
    `https://dev.to/api/articles?username=${process.env.DEV_USERNAME}`
  );

  const res = await devDotToPosts.json();

  return {
    props: {
      devDotToPosts: res,
    },
  };
};

export default Blog;
Enter fullscreen mode Exit fullscreen mode

Como ven, tengo un custom componente VImage para cargar las imágenes, acá se los dejo

import Image from "next/image";
import { VImageProps } from "./types";

const vCustomLoader = ({ src, width, quality }) => {
  return `${src}?w=${width}&q=${quality || 75}`;
};

export const VImage = ({ src, alt, width, height, className }: VImageProps) => {
  return (
    <Image
      loader={vCustomLoader}
      src={src}
      alt={alt}
      width={width}
      height={height}
      className={className}
      priority
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

y acá las respectivas PropsTypes:

export type VImageProps = {
  src: string;
  alt: string;
  width: number | string;
  height: number | string;
  className?: string;
};
Enter fullscreen mode Exit fullscreen mode

Listo!!! Con ésto tenés la portada de tu Blog en tu Website personal, ahora vamos a ver una forma sencilla de pintar cada entrada.

Para ésto debes crear el archivo [slug].tsx dentro de la carpeta blog, así debe quedar pages/blog/posts/[slug].tsx

Una vez aquí, lo primero que se debe hacer es obtener la url del post que nos entrega Dev.to, ésto lo hacemos con getStaticPaths pues así le estamos diciendo a NextJS dónde va a ir a consultar la información del post que quieres mostrar, ésta función debe lucir algo como ésto👇🏾

export async function getStaticPaths() {
  const devDotToPosts = await fetch(
    `https://dev.to/api/articles?username=${process.env.DEV_USERNAME}`
  );
  const posts = await devDotToPosts.json();

  return {
    paths: posts.map((post) => {
      return {
        params: {
          slug: post.slug,
        },
      };
    }),
    fallback: false,
  };
}
Enter fullscreen mode Exit fullscreen mode

Ahora debemos traer las Props que se van a utilizar en la vista, ésto se hace con getStaticProps así:

export const getStaticProps = async ({ params }) => {
  const devDotToPost = await fetch(
    `https://dev.to/api/articles/${process.env.DEV_USERNAME}/${params.slug}`
  );
  const res = await devDotToPost.json();

  return {
    props: {
      devDotToPost: res,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Al final pages/blog/posts/[slug].tsx debe quedar algo como ésto

import Head from "next/head";
import Link from "next/link";
import { VImage } from "@components/image/VImage";
import Layout from "@components/Layout";

export default function Post({ devDotToPost }) {

  const {
    title,
    published_at,
    social_image,
    body_html,
    user,
    type_of,
    description,
    canonical_url,
  } = devDotToPost;

  return (
    <Layout>
      <Head>
        <meta property="og:type" content={type_of} />
        <meta property="og:title" content={title} />
        <meta property="og:description" content={description} />
        <meta property="og:image" content={social_image} />
        <meta property="og:url" content={canonical_url} />
      </Head>
      <div className="flex justify-center">
        <article className="text-lg w-full md:w-3/4 text-justify">
          <div className="my-12 border-2 text-secondary bg-white md:rounded-lg overflow-hidden">
            <div className="p-4 md:p-32">
              <h1 className="text-3xl font-bold text-primary">{title}</h1>
              <div className="flex items-center text-secondary rounded-lg py-6">
                <VImage
                  className="rounded-full"
                  src={user.profile_image_90}
                  alt={user.name}
                  width={50}
                  height={50}
                />
                <span className="mx-4 font-bold">{user.name}</span>
              </div>
              <div
                className="markdown"
                dangerouslySetInnerHTML={{ __html: body_html }}
              />
            </div>
          </div>
          <div className="flex justify-center">
            <Link href="/blog">
              <a className="text-primary inline-flex items-center md:mb-2 lg:mb-0 cursor-pointer text-base pb-8">
                <svg
                  className="w-6 h-6 mr-2"
                  stroke="currentColor"
                  strokeWidth="2"
                  fill="none"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  viewBox="0 0 24 24"
                >
                  <path d="M19 12H5M12 19l-7-7 7-7" />
                </svg>
                Volver
              </a>
            </Link>
          </div>
        </article>
      </div>
    </Layout>
  );
}

export async function getStaticPaths() {
  const devDotToPosts = await fetch(
    `https://dev.to/api/articles?username=${process.env.DEV_USERNAME}`
  );
  const posts = await devDotToPosts.json();

  return {
    paths: posts.map((post) => {
      return {
        params: {
          slug: post.slug,
        },
      };
    }),
    fallback: false,
  };
}

export const getStaticProps = async ({ params }) => {
  const devDotToPost = await fetch(
    `https://dev.to/api/articles/${process.env.DEV_USERNAME}/${params.slug}`
  );
  const res = await devDotToPost.json();

  return {
    props: {
      devDotToPost: res,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Y ya!!! Con así ya tenés toda la información disponible de tu post al momento de renderizar, el resto ya es a tu imaginación, al forma como quieras que luzca la página de blog y qué tanta info, en mi caso quise hacerlo lo más sencillo posible, pero seguro vos podrás ponerlo mucho más bonito, lo importante acá era mostrarte el proceso de cómo se hace la integración de NextJS con el API de Dev.to

Eso es todo! Espero te pueda servir, dale like y me podés seguir en twitter como @viistorrr

@viistorrr

viistorrr.com

Top comments (0)