"Maintain full data ownership while enjoying seamless community connectivity."
zlog began with a simple, exciting vision: "What if friends or hobbyist groups with shared interests could run their own blogs while subscribing to each other to build a collective feed together?"
- Co-created Networks: While each person runs their own blog, you can connect specific categories to aggregate posts from like-minded peers right on your own dashboard.
- Discovery through Flow: Discover new zloggers through shared posts on your blog, and let your own content travel to others' feeds, inviting new audiences to your space.
- Subscribe to Tastes: If another zlogger loves your category, they can subscribe to it and integrate your latest updates into their own blog in real-time.
"Where individual islands come together to form a continentβthis is the decentralized blog network zlog envisions."
| Centralized Platforms | Static Site Generators | ZLOG | |
|---|---|---|---|
| Data Ownership | Platform owns your data | You own files | You own everything |
| Cross-blog Subscription | Platform-dependent | Not built-in | Built-in Federation |
| RSS | Sometimes | Plugin needed | Native support |
| Comments | Third-party required | Third-party required | Built-in (SSO + Anonymous) |
| Setup Complexity | None | Build pipeline needed | docker compose up |
| Runs on Raspberry Pi | N/A | Possible | Yes, natively |
Personal blogs often become "isolated islands." No matter how great your content is, staying connected with peers or building a community usually requires moving into a "walled garden" (like Medium or Tumblr) or relying heavily on external tools like RSS readers and social media.
Centralized platforms offer connectivity at the cost of your data sovereignty and design freedom, while independent blogs offer freedom but suffer from isolation. "Is it possible to maintain my own space while staying effortlessly connected with the people I care about?" This is the question ZLOG aims to answer.
ZLOG transforms your blog into a community node by building subscription and federation right into the core engine. It creates a "decentralized community network" where independence and connectivity coexist.
- Co-created Community Feeds: Friends or hobbyist groups can run their own independent blogs. By subscribing to each other's categories, your blog becomes a shared social dashboard for your circle.
- Discovery through Flow: Your posts and subscribed posts from your peers live together in a single, unified feed. Visitors to your blog can naturally discover and jump to other amazing zloggers you follow.
- Sovereignty Meets Connectivity: All these interactions happen on your own hardware (Mac Mini, Raspberry Pi, etc.). You maintain 100% ownership of your data and design while enjoying the benefits of a connected network.
Independent yet interconnected β a decentralized blog network. That's ZLOG.
This is what makes ZLOG unique. There are two ways to subscribe to a ZLOG blog:
flowchart LR
subgraph methods [Two Subscription Methods]
direction TB
zlog[ZLOG-to-ZLOG Federation]
rss[RSS Feed]
end
subgraph zlogFeatures [ZLOG Federation]
direction TB
realtime[Real-time Webhook Delivery]
embedded[Posts Embedded in Your Blog]
bidirectional[Bidirectional Communication]
manage[Admin Dashboard Management]
end
subgraph rssFeatures [RSS Feed]
direction TB
universal[Any RSS Reader Compatible]
standard[Standard RSS 2.0 Format]
percategory[Per-category Feeds]
autodiscovery[Auto-discovery Support]
end
zlog --> zlogFeatures
rss --> rssFeatures
When two ZLOG instances connect, they form a live subscription with automatic content delivery.
sequenceDiagram
participant Admin as Blog A (Subscriber)
participant BlogB as Blog B (Publisher)
Note over Admin,BlogB: Phase 1 β Subscribe
Admin->>BlogB: GET /api/federation/categories
BlogB-->>Admin: List of public categories
Admin->>BlogB: POST /api/federation/subscribe
BlogB-->>Admin: Subscription confirmed
Admin->>Admin: Store subscription mapping
Note over Admin,BlogB: Phase 2 β Content Delivery (Automatic)
BlogB->>BlogB: Author publishes a new post
BlogB->>Admin: POST /api/federation/webhook
Note right of BlogB: { event: "post.published",<br/>post: { title, content, ... } }
Admin->>Admin: Store as remote post
Admin->>Admin: Display in local category
Note over Admin,BlogB: Phase 3 β Reader Experience
Note over Admin: Visitor opens Blog A's category
Admin->>Admin: Merge local + remote posts
Note over Admin: Remote posts show "View Original"<br/>link back to Blog B
-
Subscribe: From your admin dashboard, enter a remote blog URL, select a category to follow, and map it to one of your local categories. The subscription request is sent server-to-server (no CORS issues).
-
Automatic Delivery: When the publisher writes a new post, ZLOG sends a webhook to all subscribers. The post content (with properly resolved image URLs) is stored locally.
-
Seamless Display: Remote posts appear in your category listings alongside your own posts, sorted by date. Each remote post shows a "View Original" link to the source blog. Remote posts also appear in the "All" tab on the homepage.
-
Background Sync: A background worker automatically syncs all subscriptions periodically (default: every 15 minutes, configurable via
WEBHOOK_SYNC_INTERVAL). This ensures no posts are missed even if webhooks fail. The worker uses incremental sync (?since=lastSyncedAt) for efficiency. -
Manual Sync: You can also trigger a manual sync from the admin dashboard at any time to immediately fetch the latest posts.
Every ZLOG blog automatically generates RSS 2.0 feeds:
| Feed | URL | Description |
|---|---|---|
| Full Blog | /rss.xml |
Latest 20 posts across all categories |
| Per Category | /category/{slug}/rss.xml |
Latest 20 posts in a specific category |
- Auto-discovery:
<link rel="alternate">tag in HTML head for automatic detection by RSS readers - Standard format: Compatible with Feedly, Inoreader, NetNewsWire, and any RSS 2.0 reader
- RSS links are visible in the sidebar and on each category page
The admin dashboard provides a complete subscription management interface:
- My Subscriptions: View all categories you're subscribed to, with last sync time
- Add Subscription: Enter a remote blog URL β fetch categories β select & map to local category
- Manual Sync: One-click sync button to fetch latest posts from a subscription
- Auto Sync: Background worker syncs all subscriptions automatically (configurable interval)
- Unsubscribe: Remove subscriptions you no longer want
- Subscriber List: See which external blogs are subscribed to your categories
erDiagram
categories ||--o{ posts : contains
categories ||--o{ subscribers : "subscribed by"
categories ||--o{ categorySubscriptions : "subscribes to"
remoteBlogs ||--o{ remoteCategories : has
remoteCategories ||--o{ categorySubscriptions : "linked via"
remoteCategories ||--o{ remotePosts : contains
remotePosts }o--o| categories : "displayed in"
remoteBlogs ||--o{ remotePosts : "authored by"
categories {
text id PK
text name
text slug
text description
}
posts {
text id PK
text title
text slug
text content
text status
}
subscribers {
text id PK
text categoryId FK
text subscriberUrl
text callbackUrl
}
categorySubscriptions {
text id PK
text localCategoryId FK
text remoteCategoryId FK
text lastSyncedAt
}
remoteBlogs {
text id PK
text siteUrl
text blogTitle
}
remoteCategories {
text id PK
text remoteBlogId FK
text name
text slug
}
remotePosts {
text id PK
text remoteUri
text localCategoryId FK
text title
text content
}
- Markdown Editor with live preview (edit / split / preview modes)
- Toolbar Features: Format text, lists, and insert media with ease. Features multi-step Undo/Redo support.
- Post Templates: Save and reuse markdown templates for consistent writing workflows
- Media Insertion: Image paste & drag-and-drop, GIPHY Sticker Picker for adding expressive stickers
- Image alignment & dimensions syntax: Custom markdown
supports image alignment (left/right/center) and exact sizing - Cover image support with upload
- Custom embeds: YouTube, CodePen, CodeSandbox
- Code blocks with syntax highlighting (highlight.js) and auto-formatting (Prettier)
- Language label & copy button on code blocks
- Mermaid diagram rendering with click-to-zoom
- Draft / Publish workflow with post management in admin
- Responsive editor: mobile-optimized layout with stacked title/category/tags rows
- Configurable modes: SSO Only, Allow All (SSO + Anonymous), Anonymous Only, Disabled
- OAuth login: GitHub and Google for authenticated commenting
- Anonymous comments with password for edit/delete
- Advanced Pagination: Root-based pagination with "Load More" for high performance
- Federation Comments: View comments from remote blogs in real-time via server-side proxy
- Rich Display: Support for line breaks and automatic truncation ("Show more") for long comments
- Admin moderation: delete any comment with confirmation; auto-hiding deleted threads without active replies
- XSS protection: plain text only, server-side sanitization
- Post Likes: Anonymous "Heart" button for readers to show appreciation
- Real-time Updates: Live sync of like and comment counts without page refresh
- In-list Metrics: Like counts visible in the post list for popular content
- Light / Dark theme toggle with system preference detection
- Semantic theme tokens: CSS variable-based color system (primary, destructive, success) with full dark mode support
- Core Web Vitals: Optimized LCP with priority image loading for the first post
- Custom header & footer: background color, background image, adjustable height
- Responsive design: mobile-optimized with auto-shrinking header on scroll
- Glassmorphism effects on header/footer
- Print-Ready: highly optimized CSS for printing, gracefully hiding interactive elements and forcing clean black-and-white layouts even in dark mode.
- English (default) and Korean built-in
- Admin configurable: change language from settings
- All UI strings translated including dates, time-ago formatting
- RSS feeds (blog-wide and per-category)
- Sitemap.xml auto-generated
- robots.txt configured
- Open Graph & Twitter Card meta tags
- SEO settings in admin (description, OG image)
- Real-time Dashboard: Monitor unique visitors and total page views over the last 24 hours.
- Detailed Logs: View recent 24-hour visitor details including IP, Country, OS, Browser, and Referer.
- Federation Views: Securely track view counts when your posts are read on subscribed remote blogs without compromising reader privacy.
- Privacy-focused: Local storage only, no third-party trackers.
- Slack Integration: Receive real-time notifications via Slack webhook.
- Comment Alerts: Get notified when someone leaves a new comment or reply on your posts.
- Federation Alerts: Instantly know when another blog subscribes to your categories.
- i18n Support: Notification messages automatically adapt to your blog's default language.
- JWT authentication for admin with sliding session: tokens auto-renew on daily visits, so active admins stay logged in indefinitely
- Brute-force protection: escalating lockout (30s β 5m β 15m) after repeated failed login attempts
- Secret categories: password-protected categories hidden from public listings
- SSRF protection: federation URL validation blocks private/internal IPs
- XSS protection: server-side HTML sanitization on all user inputs
- PWA: installable, offline-capable with service worker
- Image optimization: Sharp-based resize/compress to WebP
- SQLite: zero-config database, single file backup
- RESTful API with Hono framework
- Background sync worker: automatic periodic federation sync with GC optimization
- Server-proxied federation: all cross-origin calls are server-to-server (no CORS dependency)
- Testing: Vitest + Testing Library for both server API and client UI components
- Performance: Built-in Lighthouse script to measure Core Web Vitals and provide context for AI-driven performance optimization
flowchart TB
subgraph client [Client β React 19 + Vite 7]
direction TB
FSD[Feature-Sliced Design]
UI[Radix UI + Tailwind CSS v4 + @zebra/core]
State[Zustand State Management]
MD[Unified Markdown Pipeline]
end
subgraph server [Server β Hono 4 + Node.js]
direction TB
API[RESTful API]
Auth[JWT + OAuth2]
Fed[Federation Engine]
Sync[Background Sync Worker]
Img[Sharp Image Processing]
end
subgraph db [Database β SQLite]
direction TB
Drizzle[Drizzle ORM]
WAL[WAL Mode]
end
subgraph external [External]
direction TB
OtherZlog[Other ZLOG Instances]
RSSReaders[RSS Readers]
OAuth[GitHub / Google OAuth]
end
client <-->|REST API| server
server <--> db
server <-->|Webhooks| OtherZlog
server -->|RSS XML| RSSReaders
server <-->|OAuth2| OAuth
# Clone the repository
git clone https://github.com/zebra0303/zlog.git
cd zlog
# Configure environment
cp .env.example .env
# Edit .env with your settings (admin email, password, site URL)
# Start with Docker Compose
docker compose up -d
# Access your blog at http://localhost:3000# Set your domain and start with Caddy reverse proxy
DOMAIN=yourblog.com docker compose --profile production up -d
# Caddy automatically obtains SSL certificates
# Access at https://yourblog.com# Install dependencies
npm install
# Development (hot reload)
npm run dev
# Production build
npm run build
npm run startZLOG is designed to run on resource-constrained devices:
# Works on Raspberry Pi 3/4/5
docker compose up -d
# Or build manually (memory-optimized build script included)
npm install
npm run build
npm run start| Variable | Description | Default |
|---|---|---|
ADMIN_EMAIL |
Admin login email | [email protected] |
ADMIN_PASSWORD |
Admin login password | admin1234 |
SITE_URL |
Public URL of your blog | http://localhost:3000 |
ADMIN_DISPLAY_NAME |
Author display name | Blog Owner |
JWT_SECRET |
Secret for JWT signing | change-me-... |
PORT |
Server port | 3000 |
WEBHOOK_SYNC_INTERVAL |
Federation sync interval (min) | 15 |
ALLOW_LOCAL_FEDERATION |
Allow localhost/private IPs | false |
GITHUB_CLIENT_ID |
GitHub OAuth App Client ID | β |
GITHUB_CLIENT_SECRET |
GitHub OAuth App Secret | β |
GOOGLE_CLIENT_ID |
Google OAuth Client ID | β |
GOOGLE_CLIENT_SECRET |
Google OAuth Client Secret | β |
VITE_GIPHY_API_KEY |
GIPHY API key for stickers | β |
zlog/
βββ client/ # Frontend (FSD Architecture)
β βββ src/
β βββ app/ # Entry point, router, providers
β βββ pages/ # Page components
β βββ widgets/ # Header, footer, sidebar
β βββ features/ # Auth, comments, theme
β βββ entities/ # PostCard, CategoryBadge
β βββ shared/ # UI components, API client, i18n, utilities
β βββ ui/__tests__/ # Component unit tests (Vitest + Testing Library)
βββ server/ # Backend (Hono)
β βββ src/
β βββ __tests__/ # API integration tests (Vitest)
β βββ db/ # Schema, migrations
β βββ routes/ # API routes
β βββ middleware/# Auth, error handlers
β βββ services/ # Business logic
β βββ lib/ # Utilities
βββ shared/ # Shared types
βββ Dockerfile
βββ docker-compose.yml
βββ Caddyfile
For developers who want to integrate with ZLOG's federation:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/federation/info |
Blog metadata |
GET |
/api/federation/categories |
Public categories |
GET |
/api/federation/categories/:id/posts |
Posts in a category |
GET |
/api/federation/posts/:id |
Single post detail (live verification) |
POST |
/api/federation/subscribe |
Subscribe to a category |
POST |
/api/federation/unsubscribe |
Unsubscribe |
POST |
/api/federation/webhook |
Receive content updates |
{
"event": "post.published",
"post": {
"id": "019c...",
"title": "Hello World",
"slug": "hello-world",
"content": "# Hello\n\nThis is my first post.",
"excerpt": "This is my first post.",
"coverImage": "/uploads/cover.webp",
"status": "published",
"createdAt": "2026-02-14T00:00:00.000Z",
"updatedAt": "2026-02-14T00:00:00.000Z"
},
"categoryId": "019c...",
"siteUrl": "https://publisher-blog.com"
}| Event | Trigger |
|---|---|
post.published |
New post published or draft β published |
post.updated |
Published post content updated |
post.deleted |
Post deleted or published β draft |
post.unpublished |
Post status changed from published |
- Live Demo: https://zlog.pe.kr
- GitHub: https://github.com/zebra0303/zlog
- License: MIT
ZLOG β Every story you care about, right on your blog.
