npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hizikyas/blog-system

v0.1.1

Published

A plug-and-play Next.js blog system with Supabase integration

Readme

@hizikyas/blog-system

A plug-and-play Next.js blog system with Supabase integration. Features include post management, reactions (likes/dislikes), comments, reporting system, and admin functionality.

Features

  • 📝 Post Management: Create, read, update, delete blog posts with Markdown support
  • 👍 Reactions: Like/dislike system with IP-based tracking (no auth required)
  • 💬 Comments: Anonymous or named commenting system
  • 🚨 Reporting: Report inappropriate posts or comments
  • 👨‍💼 Admin Panel: Protected admin functionality with RLS policies
  • 🎨 Responsive Design: Mobile-friendly with Tailwind CSS styling
  • Optimistic Updates: Smooth user experience with SWR
  • 🔒 Security: Row Level Security (RLS) policies for data protection

Installation

npm i @hizikyas/blog-system

Peer Dependencies

Make sure you have these installed in your Next.js project:

npm install next react react-dom @supabase/supabase-js

Quick Setup

1. Database Setup

Run the below SQL script in your Supabase SQL editor:

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Posts table
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  excerpt TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Reactions table (handles likes/dislikes)
CREATE TABLE reactions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
  type VARCHAR(7) CHECK (type IN ('like', 'dislike')),
  user_ip INET NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(post_id, user_ip, type)
);

-- Comments table
CREATE TABLE comments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
  content TEXT NOT NULL,
  author_name TEXT DEFAULT 'Anonymous',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Reports table
CREATE TABLE reports (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
  comment_id UUID REFERENCES comments(id) ON DELETE CASCADE,
  reason TEXT NOT NULL,
  reporter_ip INET,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Add is_admin column to auth.users (run this if using Supabase Auth)
-- ALTER TABLE auth.users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE;

-- RLS Policies
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE reactions ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE reports ENABLE ROW LEVEL SECURITY;

-- Posts policies
CREATE POLICY "Public read access for posts" ON posts FOR SELECT USING (true);
CREATE POLICY "Admin write access for posts" ON posts FOR ALL USING (
  EXISTS (
    SELECT 1 FROM auth.users 
    WHERE id = auth.uid() AND is_admin = true
  )
);

-- Reactions policies
CREATE POLICY "Public read access for reactions" ON reactions FOR SELECT USING (true);
CREATE POLICY "Public insert access for reactions" ON reactions FOR INSERT WITH CHECK (true);
CREATE POLICY "Users can delete their own reactions" ON reactions FOR DELETE USING (true);

-- Comments policies
CREATE POLICY "Public read access for comments" ON comments FOR SELECT USING (true);
CREATE POLICY "Public insert access for comments" ON comments FOR INSERT WITH CHECK (true);

-- Reports policies
CREATE POLICY "Admin read access for reports" ON reports FOR SELECT USING (
  EXISTS (
    SELECT 1 FROM auth.users 
    WHERE id = auth.uid() AND is_admin = true
  )
);
CREATE POLICY "Public insert access for reports" ON reports FOR INSERT WITH CHECK (true);

-- Indexes for performance
CREATE INDEX idx_posts_slug ON posts(slug);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
CREATE INDEX idx_reactions_post_id ON reactions(post_id);
CREATE INDEX idx_comments_post_id ON comments(post_id);
CREATE INDEX idx_comments_created_at ON comments(created_at DESC);

-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

-- Trigger for posts updated_at
CREATE TRIGGER update_posts_updated_at BEFORE UPDATE ON posts
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

2. Environment Variables

Add to your .env.local:

NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

3. Create IP Detection Endpoint

Create pages/api/get-ip.js (Pages Router) or app/api/get-ip/route.js (App Router):

// Pages Router
export default function handler(req, res) {
  const forwarded = req.headers["x-forwarded-for"];
  const ip = forwarded ? forwarded.split(/, /)[0] : req.connection.remoteAddress;
  res.status(200).json({ ip });
}

// App Router
export async function GET(request) {
  const forwarded = request.headers.get("x-forwarded-for");
  const ip = forwarded ? forwarded.split(/, /)[0] : request.ip;
  return Response.json({ ip });
}

4. Setup Provider

Wrap your app with the BlogProvider:

// _app.js (Pages Router) or layout.js (App Router)
import { createClient } from '@supabase/supabase-js'
import { BlogProvider } from '@hizikyas/blog-system'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

export default function App({ Component, pageProps }) {
  return (
    <BlogProvider supabaseClient={supabase}>
      <Component {...pageProps} />
    </BlogProvider>
  )
}

Usage Examples

Display Blog Posts

import { PostList } from '@hizikyas/blog-system'

export default function BlogPage() {
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Blog Posts</h1>
      <PostList page={1} limit={10} />
    </div>
  )
}

Single Post View

import { Post } from '@hizikyas/blog-system'
import { useRouter } from 'next/router'

export default function PostPage() {
  const router = useRouter()
  const { slug } = router.query

  return (
    <div className="container mx-auto px-4 py-8">
      <Post slug={slug} />
    </div>
  )
}

Admin Panel

import { CreatePostForm, EditPostForm } from '@hizikyas/blog-system'

export default function AdminPage() {
  return (
    <div className="container mx-auto px-4 py-8">
      <CreatePostForm onSuccess={() => console.log('Post created!')} />
    </div>
  )
}

Components

Core Components

  • <PostList /> - Displays paginated list of posts
  • <Post slug="post-slug" /> - Single post view with reactions and comments
  • <ReactionButtons postId={id} /> - Like/dislike buttons with counts
  • <CommentSection postId={id} /> - Comment form and list
  • <ReportButton targetId={id} type="post|comment" /> - Report functionality

Admin Components

  • <CreatePostForm /> - Create new posts (admin only)
  • <EditPostForm postId={id} /> - Edit existing posts (admin only)

Hooks

Data Fetching

  • usePosts(page, limit) - Fetch paginated posts
  • usePost(slug) - Fetch single post by slug
  • useReactions(postId) - Get reaction counts and user's reaction
  • useComments(postId) - Fetch comments for a post

Mutations

  • useCreatePost() - Create, update, delete posts (admin only)
  • useReportContent() - Report posts or comments

Admin Setup

1. Enable Admin Access

In your Supabase dashboard, update a user to be admin:

UPDATE auth.users 
SET is_admin = true 
WHERE email = '[email protected]';

2. Admin Routes

Create protected admin routes:

import { useBlogContext } from '@hizikyas/blog-system'
import { CreatePostForm } from '@hizikyas/blog-system'

export default function AdminDashboard() {
  const { isAdmin } = useBlogContext()

  if (!isAdmin) {
    return <div>Access denied</div>
  }

  return (
    <div>
      <h1>Admin Dashboard</h1>
      <CreatePostForm />
    </div>
  )
}

Customization

Styling

The components use Tailwind CSS classes. You can override styles by:

  1. Using the className prop on components
  2. Creating custom CSS classes
  3. Using Tailwind's utility classes

Rate Limiting

The system includes basic rate limiting for reactions (1 per IP per post). For production, consider implementing additional rate limiting at the API level.

TypeScript Support

Full TypeScript support with exported types:

import type { 
  Post, 
  Comment, 
  Reaction, 
  CreatePostData 
} from '@hizikyas/blog-system'

Security

  • Row Level