You can get a pretty good sense of it just by looking at the package.json
. So here it is:
{
"dependencies": {
"@radix-ui/react-avatar": "^1.1.1",
"clsx": "^2.1.1",
"gray-matter": "^4.0.3",
"next": "15.0.0",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"zenn-content-css": "^0.1.157",
"zenn-markdown-html": "^0.1.157"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
I am using Next.js. While Astro is also a viable option for static generation, I chose Next.js for several reasons:
I am using Biome for formatting and linting.
I don't use any libraries for UI components, but I may occasionally rely on a headless component library like Radix UI.
I am using Tailwind CSS. Personally, I find it hard to go back to other styling methods. Even for small sites, using Tailwind CSS significantly changes the development experience (mainly because I'm used to it and enjoy working with it).
I manage markdown files in a separate repository. I retrieve files using the GitHub API.
For example, here's how it looks for a list:
const res = await fetch(
`https://api.github.com/repos/username/repository/contents/directoryName`,
{
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
}
);
const resPosts = await res.json();
const mdFiles = resPosts.filter((article: any) => article.name.endsWith(".md"));
return (
<ul>
{mdFiles.map((file: any) => (
<li key={file.name}>{file.name}</li>
))}
</ul>
);
I retrieve the content of files using the GitHub API. Additionally, I embed frontmatter within the markdown, which I parse using gray-matter.
The frontmatter includes information such as the content's title and publication date, and I convert the markdown to HTML using zenn-markdown-html.
Here's how it looks:
const res = await fetch(
`https://api.github.com/repos/username/repository/contents/directoryName/${id}.md`,
{
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
}
);
const resPost = await res.json();
// The content is base64 encoded, so we decode it and convert it to utf-8
const content = Buffer.from(resPost.content, "base64").toString("utf-8");
const frontmatter = extractFrontmatter(content);
const post: {
id: string;
title: string;
publishedAt: string;
content: string;
} = {
id: resPost.name.replace(".md", ""),
title: frontmatter.data.title,
publishedAt: frontmatter.data.publishedAt,
content: frontmatter.content,
};
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: markdownToHtml(post.content) }} />
</div>
);
To apply the Zenn markdown style, I import the CSS in the layout.
By the way, Zenn is a well-known platform in Japan for posting technical articles, and I'm using its theme.
import "./globals.css";
import "zenn-content-css"; // Apply Zenn markdown styles
...
I use Vercel for deployment. It's very convenient.