Secure_Doc // Encryption_Active

Blog System Design

2 min read2025-12-31 12:12

Blog System Design

This document describes the detailed design for integrating a blog system into Next.js projects, supporting MDX/MD document parsing into static pages with multilingual support (en, zh-CN, ja).

Technology Stack

Using react-markdown with next-mdx-remote for MDX rendering - a lightweight and flexible solution.

Core Dependencies

PackagePurpose
react-markdownMarkdown rendering
next-mdx-remoteMDX remote/dynamic rendering
gray-matterParse frontmatter
rehype-highlightCode syntax highlighting
rehype-slugAdd IDs to headings
remark-gfmGitHub Flavored Markdown support

Advantages

  1. Lightweight - No extra build steps required
  2. Flexible - Can dynamically load content
  3. Mature & Stable - Widely used libraries
  4. Easy Integration - Works perfectly with Next.js App Router
  5. MDX Support - Use React components in Markdown

Directory Structure

project-root/
├── content/
│   └── blog/
│       ├── getting-started/
│       │   ├── en.mdx              # English version
│       │   ├── zh-CN.mdx           # Chinese version
│       │   ├── ja.mdx              # Japanese version
│       │   └── thumbnail.jpg       # Article thumbnail
│       └── [...more articles]/
│
├── src/
│   ├── app/
│   │   └── [lang]/
│   │       └── (main)/
│   │           └── blog/
│   │               ├── page.tsx           # Blog list page
│   │               └── [slug]/
│   │                   └── page.tsx       # Blog article detail page
│   │
│   ├── components/
│   │   └── blog/
│   │       ├── BlogCard.tsx              # Article card component
│   │       ├── BlogList.tsx              # Article list component
│   │       ├── BlogContent.tsx           # Article content renderer
│   │       └── MDXComponents.tsx         # MDX custom component mapping
│   │
│   └── lib/
│       └── blog/
│           ├── index.ts                  # Blog utility exports
│           ├── api.ts                    # Blog data fetching API
│           └── types.ts                  # Type definitions

MDX Article Format

Frontmatter Structure

---
title: "Getting Started with Next.js"
description: "A comprehensive guide to building modern web applications"
date: "2024-12-17"
author: "John Doe"
thumbnail: "/blog/getting-started/thumbnail.jpg"
published: true
---

# Getting Started with Next.js

Your article content here...

Supported Frontmatter Fields

FieldTypeRequiredDescription
titlestringArticle title
descriptionstringArticle summary/description
datestringPublish date (YYYY-MM-DD)
authorstringAuthor name
thumbnailstringThumbnail path
publishedbooleanWhether published (default true)

Core Implementation

Blog Data Fetching API

// src/lib/blog/api.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { BlogPost, BlogPostMeta } from './types'

const BLOG_DIR = path.join(process.cwd(), 'content/blog')

// Get all blog post metadata
export async function getAllPosts&#40;locale: string&#41;: Promise<BlogPostMeta[]> {
  const slugs = fs.readdirSync&#40;BLOG_DIR&#41;.filter&#40;name => {
    const stat = fs.statSync&#40;path.join&#40;BLOG_DIR, name&#41;&#41;
    return stat.isDirectory&#40;&#41;
  }&#41;

  const posts: BlogPostMeta[] = []

  for &#40;const slug of slugs&#41; {
    const filePath = path.join&#40;BLOG_DIR, slug, `${locale}.mdx`&#41;
    
    if &#40;!fs.existsSync&#40;filePath&#41;&#41; continue

    const fileContent = fs.readFileSync&#40;filePath, 'utf-8'&#41;
    const { data } = matter&#40;fileContent&#41;

    if &#40;data.published === false&#41; continue

    posts.push&#40;{
      slug,
      title: data.title,
      description: data.description,
      date: data.date,
      author: data.author,
      thumbnail: data.thumbnail,
      readingTime: calculateReadingTime&#40;fileContent, locale&#41;,
    }&#41;
  }

  return posts.sort&#40;&#40;a, b&#41; => 
    new Date&#40;b.date&#41;.getTime&#40;&#41; - new Date&#40;a.date&#41;.getTime&#40;&#41;
  &#41;
}

// Get single post
export async function getPostBySlug&#40;
  slug: string, 
  locale: string
&#41;: Promise<BlogPost | null> {
  const filePath = path.join&#40;BLOG_DIR, slug, `${locale}.mdx`&#41;
  
  if &#40;!fs.existsSync&#40;filePath&#41;&#41; return null

  const fileContent = fs.readFileSync&#40;filePath, 'utf-8'&#41;
  const { data, content } = matter&#40;fileContent&#41;

  return {
    slug,
    title: data.title,
    description: data.description,
    date: data.date,
    author: data.author,
    thumbnail: data.thumbnail,
    content,
    readingTime: calculateReadingTime&#40;content, locale&#41;,
  }
}

// Calculate reading time
function calculateReadingTime&#40;content: string, locale: string&#41;: number {
  const wordsPerMinute = locale === 'zh-CN' || locale === 'ja' ? 400 : 200
  const text = content.replace&#40;/---[\s\S]*?---/, ''&#41;
  const wordCount = locale === 'zh-CN' || locale === 'ja' 
    ? text.length 
    : text.split&#40;/\s+/&#41;.length
  return Math.max&#40;1, Math.ceil&#40;wordCount / wordsPerMinute&#41;&#41;
}

Type Definitions

// src/lib/blog/types.ts
export interface BlogPostMeta {
  slug: string
  title: string
  description: string
  date: string
  author?: string
  thumbnail?: string
  readingTime: number
}

export interface BlogPost extends BlogPostMeta {
  content: string
}

Blog List Page

// src/app/[lang]/&#40;main&#41;/blog/page.tsx
import { getAllPosts } from '@/lib/blog/api'
import { BlogList } from '@/components/blog/BlogList'

export default async function BlogPage&#40;{
  params,
}: {
  params: Promise<{ lang: string }>
}&#41; {
  const { lang } = await params
  const posts = await getAllPosts&#40;lang&#41;
  
  return &#40;
    <div className="container mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold mb-8">Blog</h1>
      <BlogList posts={posts} lang={lang} />
    </div>
  &#41;
}

MDX Content Renderer

// src/components/blog/BlogContent.tsx
'use client'

import { MDXRemote } from 'next-mdx-remote/rsc'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import rehypeSlug from 'rehype-slug'
import { BlogPost } from '@/lib/blog/types'
import { MDXComponents } from './MDXComponents'

interface BlogContentProps {
  post: BlogPost
  lang: string
}

export function BlogContent&#40;{ post, lang }: BlogContentProps&#41; {
  return &#40;
    <article className="container mx-auto px-4 py-12 max-w-4xl">
      <header className="mb-8">
        <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
        <div className="flex items-center gap-4 text-gray-600">
          <time dateTime={post.date}>
            {new Date&#40;post.date&#41;.toLocaleDateString&#40;lang, {
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            }&#41;}
          </time>
          {post.author && <span>by {post.author}</span>}
          <span>{post.readingTime} min read</span>
        </div>
      </header>

      <div className="prose prose-lg max-w-none">
        <MDXRemote
          source={post.content}
          options={{
            mdxOptions: {
              remarkPlugins: [remarkGfm],
              rehypePlugins: [rehypeHighlight, rehypeSlug],
            },
          }}
          components={MDXComponents}
        />
      </div>
    </article>
  &#41;
}

Installation

pnpm add react-markdown next-mdx-remote gray-matter remark-gfm rehype-highlight rehype-slug

Code Highlighting Styles

Import highlight.js CSS styles:

// src/app/[lang]/&#40;main&#41;/blog/layout.tsx
import 'highlight.js/styles/github-dark.css'

export default function BlogLayout&#40;{
  children,
}: {
  children: React.ReactNode
}&#41; {
  return children
}

Multilingual Configuration

Blog Translation Files

// public/locales/en/blog.json
{
  "title": "Blog",
  "subtitle": "Latest articles and updates",
  "readMore": "Read more",
  "readingTime": "{{minutes}} min read",
  "publishedOn": "Published on {{date}}",
  "noArticles": "No articles found",
  "backToBlog": "Back to Blog"
}
// public/locales/zh-CN/blog.json
{
  "title": "博客",
  "subtitle": "最新文章和更新",
  "readMore": "阅读更多",
  "readingTime": "{{minutes}} 分钟阅读",
  "publishedOn": "发布于 {{date}}",
  "noArticles": "暂无文章",
  "backToBlog": "返回博客"
}

Summary

This solution uses react-markdown + next-mdx-remote to implement a blog system, providing:

  • ✅ Lightweight implementation, no extra build steps
  • ✅ Full MDX support, can use React components
  • ✅ Complete multilingual support
  • ✅ Code syntax highlighting
  • ✅ Reading time estimation
  • ✅ SEO optimization (static generation + dynamic metadata)
  • ✅ Responsive design