cover

How to integrate your blog with dev.to API Next.js 13

If you're reading this at dev.to, you can also take a look at my own website at nicholascosta.dev/blog

Little recap of the situation I was on

I've been refactoring my portfolio website from the start these days and took a chance to make my blog better(both in styling and how it worked). If you want to take a look, it's public at github, but keep in mind it's still in progress.

I used to save my blog posts in a folder containing the markdown files(that may be what you're used to), but I wanted to post both on dev.to and my blog, then, a friend of mine gave me an idea

"Why don't you use dev.to's API to get all the posts?"

At this point I didn't even know dev.to had an open API, so after that, I couldn't agree more, then I started the journey.

Technologies I chose to build my blog

I'm using Next.js(version 13 with the experimental app dir), and for rendering markdown on React I'm going to use react-markdown.

Integrating an existing Markdown-rendered app

If you already have built your blog and it's already capable of rendering markdown as HTML, 90% of your work is done, all that's left is integrating with the API.

The dev.to API is hosted at https://dev.to/api/ and you can get your posts by reaching out to https://dev.to/api/articles/latest?username={your_username}. This request will return at max 30 posts and will be ordered by descending published dates, you can take a look at their own documentation

Rendering your Posts

With the data you get from the API, you can render your posts the way you want, using cards, lists, literally anything, that's up to you.

But let's go to the interesting part, rendering the actual single post.

Post

You can search for the specific post by its slug using your username at this endpoint: https://dev.to/api/articles/${your_username}/{article_slug}.

For rendering the actual post, we first need to make the request, to keep it simple, I'll be using the native fetch API from the browser.

// utils.ts

type Post = {
  title: string
  description: string
  published_at: string
  slug: string
  id: number
  user: {
    name: string
    profile_image: string
  }
}

interface SinglePostResponse extends Post {
  body_markdown: string
  status?: number
}

// Get post by slug
export const fetchPost = async (postSlug: string) => {
  const response = await fetch(
    `${process.env.DEVTO_URL}/nicholascostadev/${postSlug}`,
  )
  const post = await response.json()
  return post as SinglePostResponse
}

// Get all posts
export const fetchPosts = async () => {
  const response = await fetch(
    `${process.env.DEVTO_URL}/latest?username=nicholascostadev`,
  )
  const posts = await response.json()
  return posts as Post[]
}

Now an actual example with Next.js 13. I'm using the async component feature(a feature that's still under development at the React Team), if you're not familiar with it, it's basically used as replacement for getStaticProps and getServerSideProps and you can take a look at Next.js docs for it.

Now let's get into coding

OBS 1:The process.env.DEVTO_URL maps to "https://dev.to/api/articles".

On this function, we Have the page component, which I named BlogPost, in Next.js 13 we can access the page params via props, but unfortunately it can't infer it yet, so we have to type it manually.

type BlogPostProps = {
  params: {
    slug: string
  }
}

export default async function BlogPost({ params }: BlogPostProps) {
 ...
}

Now we need to make the request, on this example I'm searching for both the single post and for all other posts available, so I can show some recommendations at the end of the post page.

const [post, allPosts] = await Promise.all([
  fetchPost(params.slug),
  fetchPosts(),
])

I'm using Promise.all for making both requests at the same time.

Also, if you send a non-existing ID to the API, it will return something like this:

{
  "error": "not found",
  "status": 404
}

Because of that, before rendering the post, I'm also checking if there is a status on the post response and if it's 404(not found), I throw an error and redirect user to page not-found.tsx.

if (post.status && post.status === 404) {
  throw new Error('post not found')
}

Because I'm using Next.js 13 with the new app directory, I can create a not-found.tsx file and render the page when calling the function notFound() imported from next/navigation.

try {
  ...
  if (post.status && post.status === 404) {
    throw new Error('post not found')
  }
} catch (err) {
  notFound()
}

Now for the final part, the actual post.

All you have to do is use the react-markdown I specified earlier, with that, you can use its component simply as so

<ReactMarkdown>
  {post.body_markdown}
</ReactMarkdown>

That's all to render the post as HTML, there are lots of things you can do to customize the results, you can check the remark plugins and rehype plugins to pass as props to <ReactMarkdown /> and you can also take a look at some other bloggers if you're looking for different styles for example Lee Robinson's or if you liked mine.

Full Code

import { notFound } from 'next/navigation'

type BlogPostProps = {
  params: {
    slug: string
  }
}

export default async function BlogPost({ params }: BlogPostProps) {
  try {
    const [post, allPosts] = await Promise.all([
      fetchPost(params.slug),
      fetchPosts(),
    ])

    if (post.status && post.status === 404) {
      throw new Error('post not found')
    }

    return (
      <ReactMarkdown>
        {post.body_markdown}
      </ReactMarkdown>
    )
  } catch (err) {
    notFound()
  }
}

Hope you liked the post, it's been a while since I last been here and I don't think I'll be that active, who knows?

Thanks for reading, I hope it helped you in some way ❤️