DEV Community

Cristian Velasquez Ramos
Cristian Velasquez Ramos

Posted on • Originally published at cvr.im

Using Dev.to as a CMS with Next.js

Thanks to @swyx for the idea and most of the logic. Check his post out.

Dev.to is a great place to share and learn from others.

It can get pretty tedious if you're busy posting blogs in two places. Luckily, Dev.to provides an API that allows you to get all your posted content for free!

If you want a full implementation, check out my website code

Here's the code I used to get it working. It has some minor changes from Swyx's code.

// postsApi.tsx

let normalizePost = (post: Post): Post => {
  const { data, content } = matter(post.body_markdown);
  return {
    ...post,
    // remove the last bit (its a 4 digit identifier, not needed here)
    slug: post.slug.split("-").slice(0, -1).join("-"),
    matter: { data, content },
  };
};

let sleep = async (ms: number = 0) => new Promise(resolve => setTimeout(resolve,ms))

export let query = async () => {
  // next does parallel builds
  // dev.to doesnt allow more than 2 requests per second (as far as I can tell
  // we gotta slow it down
  await sleep(1000)
  // we cache the response
  // otherwise we'll hit the 429 error "Too many requests" during build times
  let cached = cache.get<Post[]>();
  if (cached) return cached;

  let posts: Post[] = [];
  let page = 0;
  let per_page = 30; // can go up to 1000
  let latestResult = [];

  do {
    page += 1; // bump page up by 1 every loop
    latestResult = await fetch(
      `https://dev.to/api/articles/me/published?page=${page}&per_page=${per_page}`,
      {
        headers: {
          "api-key": process.env.dev_token as string,
        },
      }
    )
      .then((res) =>
        res.status !== 200 ? Promise.reject(res.statusText) : res.json()
      )
      .then((x) => (posts = posts.concat(x)))
      .catch((err) => {
        throw new Error(`error fetching page ${page}, ${err}`);
      });
  } while (latestResult.length === per_page);
  posts = posts.map(normalizePost);
  cache.set(posts);
  return posts;
};
Enter fullscreen mode Exit fullscreen mode

Here you can see it's a simple loop aggregating all of our posts.

I've added a cache because, on my website, Dev.to would return a 429 status code because the requests were happening too fast.

This is fine since there's almost no chance our posts will become stale while we are building.

After that I call query to get a list of my posts:

// pages/blog.tsx

export async function getStaticProps() {
  let posts = await postsApi.query();
  return {
    props: {
      posts,
    },
    revalidate: 1,
  };
}
Enter fullscreen mode Exit fullscreen mode

Tada! It was much smoother than I expected. Thanks a lot dev.to!

Top comments (4)

Collapse
 
alexandrudanpop profile image
Alexandru-Dan Pop

Did you also find a way to resolve twitter / youtube embedded content?
Don't know if the dev.to custom markdown like {% twitter 834439977220112384 %} can be used as mdx?

Collapse
 
tylerlwsmith profile image
Tyler Smith

I've been thinking about doing this myself! As of this week I'm up to 20 posts and that seems like enough to warrant their own site.

Collapse
 
ije profile image
X.

awesome!

Collapse
 
shadowtime2000 profile image
shadowtime2000

I did something like this. I used on the fly server side prop fetching instead though. Github.