Skip to main content

Overview

The blog utilities module provides server-side functions for reading, parsing, and rendering markdown blog posts with gray-matter frontmatter and remark HTML processing. File: src/lib/blog.ts

Type Definitions

BlogPost

Base blog post metadata structure:
src/lib/blog.ts
export interface BlogPost {
  id: string;
  title: string;
  description: string;
  date: string;
  readTime: string;
  image: string;
  slug: string;
  author?: string;
  tags?: string[];
  category?: string;
  excerpt?: string;
  content?: string;
}
id
string
required
Unique identifier derived from filename (without .md extension)
title
string
required
Post title from frontmatter
description
string
required
Short description/summary of the post
date
string
required
Publication date in ISO format (YYYY-MM-DD)
readTime
string
required
Estimated reading time (e.g., “5 min read”)
image
string
required
Hero image path for the post
slug
string
required
URL-friendly identifier for the post
author
string
Post author name (optional)
tags
string[]
Array of topic tags (optional)
category
string
Post category (optional)
excerpt
string
Short excerpt or preview text (optional)
content
string
Full HTML content of the post (optional, populated by getPostData)

BlogPostWithContent

Extended interface that guarantees content is present:
src/lib/blog.ts
export interface BlogPostWithContent extends BlogPost {
  content: string;
}

Functions

getSortedPostsData()

Retrieves all blog posts sorted by date (newest first).
Function Signature
export function getSortedPostsData(): BlogPost[]
Returns: Array of BlogPost objects without content, sorted by date descending Usage:
src/app/blog/page.tsx
import { getSortedPostsData } from "@/lib/blog";

export default function BlogPage() {
  const posts = getSortedPostsData();
  
  return (
    <div>
      {posts.map(post => (
        <BlogPostCard key={post.id} post={post} />
      ))}
    </div>
  );
}
Implementation Details:
  • Reads all .md files from src/content/blog/
  • Parses frontmatter using gray-matter
  • Sorts by date (latest first)
  • Excludes post content for performance

getAllPostSlugs()

Retrieves slugs for all blog posts (used for static generation).
Function Signature
export function getAllPostSlugs(): { slug: string }[]
Returns: Array of objects with slug property Usage:
src/app/blog/post/[slug]/page.tsx
import { getAllPostSlugs } from "@/lib/blog";

export async function generateStaticParams() {
  return getAllPostSlugs();
}

getPostData()

Fetches a single blog post with full HTML content.
Function Signature
export async function getPostData(
  slug: string
): Promise<BlogPostWithContent | null>
slug
string
required
The post identifier (filename without .md extension)
Returns: BlogPostWithContent object with rendered HTML, or null if not found Usage:
src/app/blog/post/[slug]/page.tsx
import { getPostData } from "@/lib/blog";

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostData(params.slug);
  
  if (!post) {
    notFound();
  }
  
  return <BlogPostContent post={post} />;
}
Processing Pipeline:
1

Read markdown file

Loads the .md file from src/content/blog/{slug}.md
2

Parse frontmatter

Uses gray-matter to extract metadata and content
3

Convert to HTML

Uses remark + remark-html to process markdown
4

Apply custom styling

Calls applyCustomStyling() to add Spotify-themed CSS classes

getPostBySlug()

Retrieves post metadata without content (lighter alternative to getPostData).
Function Signature
export function getPostBySlug(slug: string): BlogPost | null
slug
string
required
The post identifier
Returns: BlogPost object without content, or null if not found Usage:
Usage
import { getPostBySlug } from "@/lib/blog";

const post = getPostBySlug("my-post-slug");
console.log(post?.title); // "My Post Title"

Custom Styling

The applyCustomStyling() function transforms plain HTML into Spotify-themed content:

Styling Features

Headers

Gradient text effects, border accents, and background highlights

Links

Spotify green with hover effects, underlines, and background transitions

Code Blocks

Dark background with green text, borders, and shadows

Images

Figure captions, hover zoom, rounded corners, and shadows

HTML Transformations

The styling function modifies these elements:
  • H1: Gradient text from green to light green
  • H2: Left border accent with background
  • H3: Smaller left border with padding
  • Paragraphs: Enhanced line height and tracking
  • Links: Internal vs external styling with transitions
  • Code: Inline and block code with Spotify theme
  • Bold/Italic: Gradient backgrounds and accent styling
  • Blockquotes: Border, gradient background, quotation marks
  • Lists: Custom bullets with Spotify green
  • Images: Figure wrapper with captions and hover effects
  • Horizontal Rules: Gradient line with glow

Dependencies

package.json
{
  "dependencies": {
    "gray-matter": "^4.0.3",
    "remark": "^15.0.1",
    "remark-html": "^16.0.1"
  }
}

Directory Structure

src/
├── content/
│   └── blog/
│       ├── post-one.md
│       ├── post-two.md
│       └── ...
└── lib/
    └── blog.ts
All blog utility functions run on the server side. They use Node.js fs module to read files and should not be called from client components.

Error Handling

The getPostData() function includes try-catch error handling:
Error Handling
try {
  const fileContents = fs.readFileSync(fullPath, "utf8");
  // ... processing
} catch (error) {
  console.error(`Error reading post ${slug}:`, error);
  return null;
}
If a post file doesn’t exist or can’t be read, getPostData() returns null. Always check for null before using the result.

Performance Considerations

  • getSortedPostsData(): Fast - only parses frontmatter, not content
  • getPostData(): Slower - processes full markdown to HTML with styling
  • Static Generation: Use getAllPostSlugs() + getPostData() in generateStaticParams() for optimal performance

Build docs developers (and LLMs) love