A simple blog built for developers. Fast to write in, easy to customize, and pleasant to read.
HopLog is a developer-friendly blog built with Next.js 16 and Bun. It focuses on the things developers actually care about: fast startup, markdown-based writing, keyboard-friendly navigation, clean theming, and straightforward customization.
It is also heavily inspired by the simplicity of Jekyll-style blogging: file-based posts, frontmatter-driven content, and a workflow that feels natural to developers.
- Fast by Default: Built on Next.js 16 (App Router) and Tailwind CSS 4 for quick load times and a responsive editing/viewing experience.
- Clean Blog UI: A simple visual style that keeps attention on your posts instead of the chrome around them.
- Category-Aware Post Feed: The home page filters posts by category and loads additional pages incrementally via
/api/posts. - Recursive Content Routing: Nested markdown files under
content/posts/automatically map to nested/posts/...routes. - Keyboard-First Navigation: Built-in command palette (⌘+⇧+P) and global hotkeys for seamless navigation.
- Git-Integrated Activity: Real-time GitHub contribution sync and writing density visualization.
- Private Post Support: Hide drafts or internal notes from routes, metadata, and sitemaps using frontmatter flags.
- Multilingual UI: Interface translations are available for English, Korean, Japanese, and Chinese.
-
Clone the repository
git clone https://github.com/rapidrabbit76/hoplog.git cd hoplog -
Install dependencies
bun install
-
Configure your profile
cp content/profile.example.yml content/profile.yml
Edit
content/profile.ymlto set your name, bio, social links, experience, and skills. -
Launch development server
bun dev
Add your markdown files anywhere under content/posts/. HopLog supports deep nesting, making it easy to organize tutorials and series.
Examples:
content/posts/getting-started.md→/posts/getting-startedcontent/posts/tutorial/advanced.md→/posts/tutorial/advanced
Each post uses YAML frontmatter for configuration:
---
title: "Your Post Title"
date: "2026.03.11"
category: ["Engineering", "Architecture"]
excerpt: "A brief summary for SEO and lists."
image: "/api/images/cover.jpg" # Optional cover image
fontFamily: "'Noto Sans KR', sans-serif" # Optional custom font
fontUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap" # Optional font link
visibility: "private" # Optional: set to 'private' to hide post
---Use visibility: "private" to hide a post from the public site (lists, sitemaps, metadata, and direct access).
public: false is also supported for compatibility with older content, but visibility: "private" is the recommended format going forward.
Edit content/config.yml to manage site-wide metadata, hero content, typography, and title templates. Root SEO settings are managed in content/seo.yml.
You can also enable ga, metaPixel, and sentry from content/config.yml. Each provider has its own enabled flag, so you can turn them on or off independently. Provider values are resolved from env vars such as GA_MEASUREMENT_ID, META_PIXEL_ID, and SENTRY_DSN.
Post pages render an icon-only sharing row at the bottom of each article when sharing: in content/config.yml contains at least one provider. The array order controls button order.
copyLink copies the current URL and briefly swaps its icon to a check mark for feedback.
Supported providers are:
twitterfacebooklinkedincopyLink
HopLog always has a built-in local search path. The bundled content/config.yml is prewired for Meilisearch, but search still falls back to local title/excerpt matching until the required host/key env vars are available. To use Meilisearch fully, keep search.provider as meilisearch in content/config.yml, configure MEILISEARCH_HOST, MEILISEARCH_SEARCH_KEY, and MEILISEARCH_ADMIN_KEY, then run bun run search:sync to publish your posts into the configured index.
Define themes as individual YAML files in content/themes/. They are loaded automatically and exposed in the Command Palette (⌘+⇧+P).
Interface strings are isolated in messages/*.json files. This allows for easy translation without touching the application logic. Supported locales include:
en(English)ko(Korean)ja(Japanese)zh(Chinese)
For a guided walkthrough, see the tutorial posts under content/posts/tutorial/.
- Analytics setup tutorial:
/posts/tutorial/site-configuration
- Metadata Generation: Automatically generates
sitemap.xmlandrobots.txt. - Custom Error Pages: Dedicated
404and500experiences are included. - Media Support: Images can be served via the
/api/images/[...path]endpoint. - Tech Stack: Next.js 16 (React 19), Tailwind CSS 4 (OKLCH), Bun, Zustand, and Remark/Rehype.
docker compose up -dOn first run, a blog/ directory is created and seeded with default content (posts, themes, config, FAQ). Edit files in blog/ to customize your site. On subsequent runs, existing content is preserved.
profile.yml is not included in the image. If missing, it is automatically created from profile.example.yml at startup.
All variables are optional and read at runtime — no image rebuild needed to change them.
| Variable | Description |
|---|---|
GA_MEASUREMENT_ID |
Google Analytics measurement ID |
META_PIXEL_ID |
Meta Pixel ID |
SENTRY_DSN |
Sentry DSN |
SENTRY_ENVIRONMENT |
Sentry environment name |
Search works out of the box with a built-in local index. Meilisearch is not required.
To enable it:
docker compose --profile search up -dThen set search.provider to meilisearch in blog/config.yml and sync:
docker compose exec app bun run search:syncWhen using Meilisearch, these additional variables apply:
| Variable | Description |
|---|---|
MEILISEARCH_HOST |
Meilisearch endpoint URL |
MEILISEARCH_SEARCH_KEY |
Public search key |
MEILISEARCH_ADMIN_KEY |
Admin key |
| Command | Description |
|---|---|
bun dev |
Start the development server |
bun run lint |
Run ESLint checks |
bun run build |
Create a production build |
bun start |
Start the production server |
bun run test:unit |
Run unit tests (Vitest) |
bun run test:e2e |
Run end-to-end tests (Playwright) |
bun run search:sync |
Sync posts to Meilisearch index |
bun run import:velog -- <username> |
Import posts from Velog |
Licensed under the MIT License. Contributions are welcome!
