A simple, embeddable blog engine for React applications with support for both local MDX files and Supabase database as content sources.
- 🔥 Multiple Content Sources: Support for both local MDX files and Supabase database, more can be added in the future
- 📝 MDX Support: Rich content with React components in markdown
- 👤 Author Management: Built-in author profiles with social links
- 🎨 Customizable: Tailwind CSS styling with customizable components
- 🔍 SEO Ready: Metadata generation for blog posts
- 📱 Responsive: Mobile-friendly design out of the box
npm install react-blog-module
# or
yarn add react-blog-module
# or
pnpm add react-blog-modulenpm install react react-dom @supabase/supabase-js gray-matter next-mdx-remote lucide-react tailwindcss class-variance-authority clsx tailwind-merge tailwindcss-animate react-syntax-highlighterIf you want to use Supabase as a content source, create a table in your Supabase database:
CREATETABLEblog_posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
title TEXTNOT NULL,
description TEXT,
author TEXTNOT NULL,
content TEXTNOT NULL,
draft BOOLEAN DEFAULT false,
dateTIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Add RLS policies as neededALTERTABLE blog_posts ENABLE ROW LEVEL SECURITY;
-- Example: Allow public read access to published posts
CREATE POLICY "Public can read published posts"ON blog_posts
FOR SELECT USING (draft = false);Create a blog configuration:
import{createBlogConfig,BlogAuthorInfo}from'react-blog-module';import{createClient}from'@supabase/supabase-js';// Set up Supabase clientconstsupabase=createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!,process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!);// Define authorsconstauthors: BlogAuthorInfo[]=[{name: 'John Doe',avatar: '/avatars/john.jpg',bio: 'Software engineer and technical writer',social: {twitter: 'johndoe',github: 'johndoe',linkedin: 'johndoe'}}];// Create blog configurationexportconstblogConfig=createBlogConfig('My Blog',// title'A blog about tech and code',// descriptionauthors,// authors arraysupabase,// Supabase client'/blog',// basePath (optional)'content/blog'// contentPath (optional));Create a content directory for your MDX files:
content/blog/
├── my-first-post.mdx
├── another-post.mdx
└── ...
Example MDX file (content/blog/my-first-post.mdx):
---title: "My First Blog Post"date: "2024-01-15"description: "This is my first blog post using the React Blog Module"author: "John Doe"draft: false---# Welcome to My Blog
This is the content of my first blog post. I can use **markdown** and even React components!
## Code Example```javascriptfunctionhello() {
console.log('Hello, world!');
}import{BlogPostsPage,getMetadata}from'react-blog-module';import{blogConfig}from'./blog-config';exportdefaultfunctionBlogPage(){return<BlogPostsPageconfig={blogConfig}/>;}// For Next.js metadataexportasyncfunctiongenerateMetadata(){returnawaitgetMetadata(blogConfig);}import{SingleBlogPost,generateBlogMetadata,generateNextStaticParams}from'react-blog-module';import{blogConfig}from'../blog-config';interfaceProps{params: Promise<{slug: string}>;}exportdefaultfunctionBlogPost({ params }: Props){return<SingleBlogPostparams={params}config={blogConfig}/>;}// For Next.jsexportasyncfunctiongenerateMetadata({ params }: Props){returnawaitgenerateBlogMetadata({ params,config: blogConfig});}exportasyncfunctiongenerateStaticParams(){returnawaitgenerateNextStaticParams(blogConfig);}import{createDatabasePost,getDatabasePosts}from'react-blog-module';import{blogConfig}from'./blog-config';// Create a new postconstnewPost=awaitcreateDatabasePost(blogConfig.supabase,{title: 'New Post from Database',description: 'This post is stored in Supabase',author: 'John Doe',content: '# Hello from the database!\n\nThis content is stored in Supabase.',draft: false});// Get all database postsconstposts=awaitgetDatabasePosts(blogConfig.supabase);The blog engine supports both file-based and database content sources:
- Database posts take priority over file-based posts with the same slug
- File-based posts are loaded from the
contentPathdirectory - Posts from both sources are merged and sorted by date
import{mdxComponents}from'react-blog-module';// Extend default componentsconstcustomComponents={
...mdxComponents,h1: (props: any)=><h1className="custom-h1"{...props}/>,CustomComponent: ({ children }: {children: React.ReactNode})=>(<divclassName="custom-wrapper">{children}</div>)};// Use in SingleBlogPost<MDXRemotesource={content}components={customComponents}/>The components use Tailwind CSS classes. Make sure your project has Tailwind configured and includes the necessary classes, or override the styles as needed.
BlogConfig- Main configuration objectBlogAuthorInfo- Author information structureBlogPost- Blog post data structureDatabasePost- Database post structure
createBlogConfig()- Create blog configurationgetAllPosts()- Get all posts from both sourcesgetPostBySlug()- Get single post by slugcreateDatabasePost()- Create new database postgetDatabasePosts()- Get all database postsgenerateBlogMetadata()- Generate SEO metadata
BlogPostsPage- Blog posts listing pageSingleBlogPost- Individual blog post pageAuthorCard- Author information componentCodeBlock- Syntax-highlighted code blocks
- React 19+
- TypeScript (recommended)
- Tailwind CSS
- Supabase (optional, for database posts)
MIT
Contributions are welcome! Please feel free to submit issues and pull requests.