How to integrate your blog with dev.to API Next.js 13
Tags: webdev, typescript, tutorial, react
Published: 2023-02-16 22:12:47

If you're reading this at dev.to, you can also take a look at my own website at [nicholascosta.dev/blog](https://nicholascosta.dev/blog)

## Little recap of the situation I was on

I've been refactoring my [portfolio website](https://nicholascosta.dev) 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](https://github.com/nicholascostadev/portfolio-remake), 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](https://github.com/remarkjs/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/](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](https://developers.forem.com/api/v0#tag/articles/operation/getLatestArticles)

## 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.

```ts
// 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](https://github.com/reactjs/rfcs/pull/229) 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](https://beta.nextjs.org/docs/data-fetching/fetching#asyncawait-in-server-components) 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.

```tsx
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.

```ts
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:

```json
{
  "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`.

```ts
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`.

```ts
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

```tsx
<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](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) and [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) 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](https://leerob.io/blog)'s or if you liked [mine](https://nicholascosta.dev/blog).


## Full Code

```tsx
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 ❤️