How to Embed Your Dev.to Blog in Your Personal Website
Published: 2021-01-18 21:57:36

I recently switched my blog over from Medium to DEV, one of the primary reasons being I wanted an easy way to show my full blog on my personal website, as well as on the platform I'm using.

In Medium's desperate quest for monetization, they've made it extremely difficult to embed your blog on your own website. There are several projects out there on GitHub that attempt to help do this, but none of them seem to work very well, and they're all somewhat lacking in the styles department.

DEV has an open API, making it much easier to embed your blog in your own website, with full custom markup and styles, so you can easily make it look exactly how you want. In this article, we'll take a look at just how easy this is to get set up.

We're going to be using TypeScript, but if you're not familiar with it or just aren't using it, you should still be able to follow along, just excluding the TypeScript interfaces.

# API

I'm going to be using [Superagent](https://github.com/visionmedia/superagent) as my HTTP client, because I like the functional and declarative API, but you can easily swap this out for something else like Axios, or the browser Fetch API. The full API documentation for DEV can be found [here](https://docs.dev.to/api/).

Let's start by setting up some interfaces to type our responses with (if you're using vanilla JavaScript you can skip this step).

`dev-to-article-meta.ts`
```ts
export default interface DevToArticleMeta {
  type_of: string;
  id: number;
  title: string;
  description: string;
  readable_publish_date: string;
  slug: string;
  path: string;
  url: string;
  comments_count: string;
  public_reactions_count: string;
  collection_id?: number;
  published_timestamp: string;
  positive_reactions_count: string;
  cover_image: string;
  social_image: string;
  tag_list: Array<string>;
}
```

`dev-to-article.ts`
```ts
import DevToArticleMeta from "interfaces/dev-to-article-meta";

export default interface DevToArticle extends DevToArticleMeta {
  body_html: string;
  body_markdown: string;
}
```

Now let's set up a service to handle fetching our data. We'll need two methods:
- `async fetchArticles(): Promise<Array<DevToArticleMeta>>`
- `async getArticle(slug: string): Promise<DevToArticle>`

`dev-to-service.ts`
```ts
import DevToArticle from "interfaces/dev-to-article";
import DevToArticleMeta from "interfaces/dev-to-article-meta";
import superagent from "superagent";

// setup API endpoints and queries
const DEV_TO_USERNAME = "matjones"; // swap this for your username
const ARTICLES_API = "https://dev.to/api/articles";

// helper method to type responses
const parseResponse = <T>(response: any): T =>
  (typeof response === "string" ? JSON.parse(response) : response) as T;

const fetchArticles = async () => {
  // GET the endpoint
  const response = await superagent.get(ARTICLES_API)
          // and add the username query parameter
          .query({ username: DEV_TO_USERNAME });
  return parseResponse(response.body);
};

const getArticle = async (slug: string) => {
  // build the API endpoint URL, `slug` is the `slug`
  //property of one of your articles,
  // e.g. "protecting-your-privacy-online-3bmc"
  const endpoint = `${ARTICLES_API}/${DEV_TO_USERNAME}/${slug}`;
  const response = await superagent.get(endpoint);
  return parseResponse<DevToArticle>(response.body);
};

export const DevToService = {
  fetchArticles,
  getArticle,
};
```

And that's pretty much it for our API layer. Nice and simple. 😎

The `fetchArticles()` function will fetch all of your published articles. If you want to implement pagination or a maximum number of results, you can add the [`page` and `per_page` query parameters](https://docs.dev.to/api/#operation/getArticles) via the `.query()` method chained off of `superagent.get()`.

# Integrating Into a React App

While this is technically all you need to get the data needed to render your articles, we can make it a little easier to work with in a React app by adding some [custom React hooks](https://reactjs.org/docs/hooks-custom.html).

**STOP: If you're not familiar with React hooks, [go read up on hooks first](https://reactjs.org/docs/hooks-intro.html), then come back. I'll wait.**

We'll make a really simple hook for each of our service methods. Since `useEffect` callbacks can't be `async` themselves, we're going to be using an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE), but note that you could also use the trusty old `Promise.then()` syntax as well.

`use-dev-to-articles.ts`
```ts
import DevToArticleMeta from "interfaces/dev-to-article-meta";
import { useEffect, useState } from "react";
import { DevToService } from "utils/dev-to-service";

/**
* Fetch all of my published dev.to articles
* @param onError a callback which is invoked if the request fails
*/
export default function useDevToArticles(onError?: () => void) {
  const [loading, setLoading] = useState(true);
  const [articles, setArticles] = useState<Array<DevToArticleMeta>>([]);

  useEffect(() => {
    (async () => {
      try {
        setArticles(await DevToService.fetchArticles());
      } catch (e) {
        onError?.();
      }

      setLoading(false);
    })();
  }, [onError]);

  // return the array of articles, and the loading indicator
  return { articles, loading };
}
```

`use-dev-to-article.ts`
```ts
import DevToArticle from "interfaces/dev-to-article";
import { useState, useEffect } from "react";
import { DevToService } from "utils/dev-to-service";

/**
* Get a specific article given the article's slug.
* @param slug the slug of the article to retrieve.
* @param onError a callback which is invoked if the request fails
*/
export default function useDevToArticle(slug: string, onError?: () => void) {
  const [loading, setLoading] = useState(true);
  const [article, setArticle] = useState<DevToArticle>();

  useEffect(() => {
    // this bit may not be necessary for you; I needed
    // it because I'm using Next.js server side rendering
    // so slug is `undefined` on the initial render
    // since I'm getting it from a route parameter
    // e.g. /blog/:slug
    if (slug == null || slug.length === 0) {
      return;
    }

    (async () => {
      try {
        setArticle(await DevToService.getArticle(slug));
      } catch (e) {
        onError?.();
      }

      setLoading(false);
    })();
  }, [onError, slug]);
  // return the article, and the loading indicator
  return { article, loading };
}
```

## Rendering an Article's Body

There are two ways you can render an article's body; using the `body_html` property or the `body_markdown` property. If your article does not contain any HTML elements (you're only using Markdown) and does not contain any DEV [Liquid tags](https://docs.dev.to/frontend/liquid-tags/), you can use the `body_markdown` property and use something like [`react-markdown`](https://github.com/remarkjs/react-markdown) to render it.

However, if you're using Liquid tags, or you have HTML elements in your markdown (for example, I often use the `<figcaption>` element to add captions to images and code snippets), you'll need to render the `body_html` property. In React, this can be done like this:

```tsx
<div dangerouslySetInnerHTML={{ __html: article.body_html }}/>
```

Notice the `dangerouslySetInnerHTML` syntax; it is *intentionally* ugly to look at and difficult to type quickly, because doing this could potentially open your app to cross-site-scripting vulnerabilities. In general, you should only use `dangerouslySetInnerHTML` with trusted inputs (e.g. inputs that you control, not unknown end users).

# Results

Using this approach, I was able to easily embed my DEV blog into my personal website. Clicking the comment or thumbs up icon opens the article on dev.to so that you can add reactions or comments.

![Blog index page on my personal site](https://dev-to-uploads.s3.amazonaws.com/i/mmx1dqqz4ow3k9ayj7av.png)
<figcaption>The /blog page on my personal website.</figcaption>

![Blog article page on my personal site](https://dev-to-uploads.s3.amazonaws.com/i/4ooiwuhddje71w0jpoxl.png)
<figcaption>A specific blog article on my personal website.</figcaption>