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 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.
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
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
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
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 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.
STOP: If you're not familiar with React hooks, go read up on hooks first, 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, but note that you could also use the trusty old Promise.then()
syntax as well.
use-dev-to-articles.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
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, you can use the body_markdown
property and use something like 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:
<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.
Top comments (1)
thanks, really helpful