Startec

Startec

Build a Blog using Next.JS and DEV.to

Mai 17, às 18:04

·

8 min de leitura

·

4 leituras

In this article you will learn how to build a Next.JS blog by fetching your Posts directly from DEV.to. I received an incredible feedback from my Post Use Notion as a database for your Next.JS Blog thanks...
Build a Blog using Next.JS and DEV.to

Cover image for Build a Blog using Next.JS and DEV.to

In this article you will learn how to build a Next.JS blog by fetching your Posts directly from DEV.to.


I received an incredible feedback from my Post Use Notion as a database for your Next.JS Blog thanks from all of you 🙌

I even saw the Post on the front page of daily.dev 😶

Today I wanted to share with you how I built my Personal Blog under an hour by using the DEV.to API.

Let's get started 🔥


1. Create a new Next.JS App

Start by using the create-next-app utility using your favorite package manager.

$ npx creat[email protected]
$ yarn create next-app
$ pnpm create next-app

Enter fullscreen mode Exit fullscreen mode

Check ✅ for everything! We want linting, typings and obviously the App Router! I also highly recommend to use the src folder.

2. Install dependencies

You will need 9 dependencies:

  • remark: We will use it to parse our Posts Markdown
  • remark-html: A Remark plugin to transform our Markdown into HTML
  • rehype: A library to process and extend HTML
  • rehype-highlight: A Rehype plugin to plug highlight.js to highlight our code
  • rehype-slug: A Rehype plugin that adds ids to our Post titles (anchors)
  • @jsdevtools/rehype-toc: A Rehype plugin that generates a table of content based on our Post titles
  • rehype-stringify: A Rehype plugin that transform our Rehype output into a String
  • remark-rehype: A Remark plugin to use Remark and Rehype in symbiose
  • unified: The library to make easy to use all thoses plugins together
$ npm install remark remark-html rehype rehype-highlight rehype-slug @jsdevtools/rehype-toc rehype-stringify remark-rehype unified
$ yarn add remark remark-html rehype rehype-highlight rehype-slug @jsdevtools/rehype-toc rehype-stringify remark-rehype unified

Enter fullscreen mode Exit fullscreen mode

3. Fetch from DEV.to

DEV.to provide a wonderful Public API that does not require any authentication you can find the official documentation here.

In our case we will need two things:

  • Fetching our Posts /api/articles?username=<username>
  • Fetching a specific Post /api/articles/<username>/<post-slug>

Add environment variables

It is a good practice to avoid hardcoding values, in case you want to change your username or open-source your blog.

Add your DEV.to username in .env.local to avoid hardcoding it:

DEVTO_USERNAME="martinp"

Enter fullscreen mode Exit fullscreen mode

Add typings

Let's add some typings to type the response of the DEV.to API in src/lib/devto/types.ts:

// src/lib/devto/types.ts
export type User = {
 user_id: number;
 name: string;
 username: string;
 twitter_username: string | null;
 github_username: string | null;
 website_url: string | null;
 profile_image: string;
 profile_image_90: string;
};
export type Post = {
 type_of: string;
 id: number;
 title: string;
 description: string;
 readable_publish_date: string;
 slug: string;
 path: string;
 url: string;
 comments_count: number;
 collection_id: number | null;
 published_timestamp: string;
 positive_reactions_count: number;
 cover_image: string | null;
 social_image: string;
 canonical_url: string;
 created_at: string;
 edited_at: string;
 crossposted_at: string | null;
 published_at: string;
 last_comment_at: string;
 reading_time_minutes: number;
 tag_list: string[];
 tags: string;
 user: User;
};
export type PostDetails = Post & {
 body_html: string;
 body_markdown: string;
 tags: string[];
};

Enter fullscreen mode Exit fullscreen mode

I manually made thoses types and they maybe does not exactly match the actual API, feel free to update them.

Create the fetching functions

Next create a new file src/lib/devto/fetch.ts, it will contains the functions that will fetch the API. It is a good practice to separate them from your App to make them easily reusable.

// src/lib/devto/fetch.ts
import { notFound } from "next/navigation";
import { Post, PostDetails } from "./types";
export async function fetchPosts(): Promise<Post[]> {
 const res = await fetch(
 `https://dev.to/api/articles?username=${process.env.DEVTO_USERNAME}`,
 {
 next: { revalidate: 3 * 60 * 60 },
 }
 );
 if (!res.ok) notFound();
 return res.json();
}
export async function fetchPost(slug: string): Promise<PostDetails> {
 const res = await fetch(
 `https://dev.to/api/articles/${process.env.DEVTO_USERNAME}/${slug}`,
 {
 next: { revalidate: 3 * 60 * 60 },
 }
 );
 if (!res.ok) notFound();
 return res.json();
}

Enter fullscreen mode Exit fullscreen mode

Notice that we add the parameter revalidate: 3 * 60 * 60. By default the fetch function extended by Next.JS will cache everything. It will make your Blog blazingly fast but we also want to keep our Blog up to date. With this parameter we tell Next.JS to revalidate the cache every 3 hours.

notFound() acts like a return and will show the not-found.tsx page. More information here.

4. Create the render function

Now let's create a function to render the content of your Posts by using Remark, Rehype and all the plugins:

// src/lib/markdown.ts
import toc from "@jsdevtools/rehype-toc";
import rehypeHighlight from "rehype-highlight";
import rehypeSlug from "rehype-slug";
import rehypeStringify from "rehype-stringify";
import remarkRehype from "remark-rehype";
import remarkParse from "remark-parse";
import { unified } from "unified";
export function renderMarkdown(markdown: string): Promise<string> {
 return unified()
 .use(remarkParse)
 .use(remarkRehype)
 .use(rehypeHighlight, { ignoreMissing: true })
 .use(rehypeSlug)
 .use(rehypeStringify)
 .use(toc, {
 headings: ["h1", "h2", "h3"],
 })
 .process(markdown)
 .then((res) => res.toString());
}

Enter fullscreen mode Exit fullscreen mode

5. Create the pages

Now that you have everything in place to fetch your posts, it is time to create the pages!

The Posts page

It can't be simpler, simply use your fetchPosts function and show them:

// src/app/blog/page.tsx
import { fetchPosts } from "@/lib/devto/fetch";
export default async function Page() {
 const posts = await fetchPosts();
 return (
 <div className="grid grid-cols-1 md:grid-cols-2 gap-4 py-8">
 {posts.map((post) => (
 <Link href={`/blog/${post.slug}`}>{post.title}</Link>
 ))}
 </div>
 );
}

Enter fullscreen mode Exit fullscreen mode

The Post page

Create a new page with a dynamic segment for our slug src/app/blog/[slug]/page.tsx.

Use the parameters to fetch the post and use the renderMarkdown function to transform your Markdown into HTML.

You can also add generateMetadata to set the title and the description using the data of your Post.

// src/app/blog/[slug]/page.tsx
import 'highlight.js/styles/github-dark.css'; // Import your favorite highlight.js theme
import { fetchPost, fetchPosts } from "@/lib/devto/fetch";
import { renderMarkdown } from "@/lib/markdown";
export async function generateMetadata({
 params,
}: {
 params: { slug: string };
}) {
 const { title, description } = await fetchPost(params.slug);
 return {
 title,
 description,
 };
}
export default async function Page({ params }: { params: { slug: string } }) {
 const { body_markdown } = await fetchPost(params.slug);
 const content = await renderMarkdown(body_markdown);
 return (
 <>
 <article>
 <div dangerouslySetInnerHTML={{ __html: content }} />
 </article>
 </>
 );
}

Enter fullscreen mode Exit fullscreen mode

Notice that you are calling twice the fetchPost method, so are you fetching twice? No! It uses the cache, you can verify it when running the dev server, you should see cache: HIT.

And you know what is reaaaally cool?
Navigate to the list of your posts and hover the links, you should see in your console your /blog/[slug] pages pre-rendering to predict the user navigation 🔥

6. Going further


I hope that this post motivated you to build an incredible blog! Share your work in the comment section! 💬

Oh and if you want more content like this, follow me:


Continue lendo

DEV

Simplify Your Documentation Workflow with MkDocs: A Markdown-based Alternative to Sphinx
Introduction: Documentation is an essential aspect of software development, enabling developers to communicate effectively with users and fellow developers. Sphinx and MkDocs are two popular documentation...

Hoje, às 08:30

DEV

Is It Burnout or Job Dissatisfaction?
Once suspended, devteam will not be able to comment or publish posts until their suspension is removed. Once unsuspended, devteam will be able to comment and publish posts again. Once...

Hoje, às 07:00

Hacker News

Experiences Won't Make You Happier Than Possessions
You will be happier if you spend your money on experiences rather than possessions… or so says the modern truism, supposedly proven by psychological science. Researchers call it the “experience...

Hoje, às 06:57

DEV

How to print “Hello” in C without using a semicolon?
#include <stdio.h> int main() { if(printf("Hello World\n")){ } switch(printf("Hello World\n")){ } while(!printf("Hello World")){ } return 0; } Enter fullscreen mode Exit fullscreen...

Hoje, às 06:54

DEV

Perfect Number Program in C
For explanation watch video Perfect Number : In mathematics, a perfect number is a positive integer that is equal to the sum of its positive divisors, excluding the number itself. ex : 6 = 1,2,3 < 6...

Hoje, às 06:52

DEV

Left Shift Bitwise Operator in C Programming (Short Trick)
Read next Leveraging Cache to improve Web Performance Jakub Andrzejewski - May 8 How to develop an online code compiler using Java and Docker. Zakaria Maaraki - Apr...

Hoje, às 06:49

DEV

¡Adiós Options API, Hola Script Setup! La no tan nueva forma de programar en Vue 3
script setup proporciona una forma más concisa de utilizar la Composition API en Single File Components (SFCs). En su versión 3, Vue introdujo un nuevo sistema para organizar el código de los componentes...

Hoje, às 06:24

DEV

How to implement subscription model using LemonSqueezy in Next.js (13.4 stable app router)
Table of contents Introduction Getting Started Setup Next.js app User Registration Setup LemonSqueezy account How subscription works Implementing Subscription Creating a checkout Synchronization with...

Hoje, às 06:00

DEV

How to FIND the QUESTIONS that will be in an interview
Trying to figure out what exact areas to study/practice to prep for an upcoming tech interview can be stressful! In order to maximize your chances of success and ensure a strong performance during the...

Hoje, às 04:08

DEV

What you learning about this weekend?
Heyo 👋 What ya learning on this weekend? Whether you're sharpening your JS skills, making PRs to your OSS repo of choice 😉, sprucing up your portfolio, or writing a new post here on DEV, we'd like to hear...

Hoje, às 04:00