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