SeoTax = Search + Taxonomies — built around the idea that readers should discover content effortlessly.
- Features
- Demo
- Requirements
- Installation
- Configuration
- Page Layouts
- Shortcodes
- Customization
- Contributing
- Dynamic Search — Fuse.js-powered fuzzy search with instant results; no extra static pages generated
- Advanced Taxonomy Filters — Filter by category, tag, or combination without a keyword
- Configurable List Sorting — Choose newest or oldest post lists, plus relevance sorting for search and optional sort controls
- 27-Language i18n — Client-side translation; no page reload, no per-language page duplication
- Dark Mode — Auto-detects system preference; toggleable with keyboard shortcut (
Cmd/Ctrl + Shift + S) - Responsive Design — Collapsible sidebar, overlay ToC on mobile, adaptive thumbnails
- Mac-style Code Blocks — Syntax highlighting (highlight.js), line numbers, copy button, language label
- Series Support — Velog-style series navigation across related posts
- SEO Optimized — Schema.org structured data, CLS < 0.01, optimized Core Web Vitals
- Lightweight Icons — IcoMoon subset font (~40 KB vs 800+ KB Font Awesome)
- PWA Ready — Service Worker with precache support
- Disqus Comments — With dark mode synchronization
- Reading Time — Calculated from text, images, code blocks, and tables
A single dynamic search page replaces hundreds of per-tag/per-category static pages.
Search is triggered via the / or s hotkey, or by clicking the search bar.
Supports 5 query types: keyword search, parent category, child category,
single tag, and multi-tag with AND/OR operators.
All 27 languages are served from a single HTML page. The language switcher in the toolbar translates UI text, dates, and ARIA labels instantly on the client side — no page navigation, no URL changes.
Switches between light and dark themes using CSS variables on a single
data-theme attribute. Persisted in localStorage. Disqus comments are
synchronized via iframe reload.
Mac-style window dots, line numbers (with copy exclusion), one-click copy with visual feedback, and a language label chip. Light theme uses Xcode colors; dark theme uses VS2015 colors.
| Desktop | Mobile |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- Hugo 0.158 or higher (extended version)
- Hugo Installation Guide
cd your-hugo-site
git submodule add https://github.com/minyeamer/hugo-seotax themes/seotaxThen set the theme in your configuration file:
theme: "seotax"Initialize Hugo modules if not already done:
hugo mod init github.com/your/repoAdd to your hugo.toml:
[module]
[[module.imports]]
path = 'github.com/minyeamer/hugo-seotax'Then fetch the module and run:
hugo mod get -u
hugo server --minifyhugo new site myblog && cd myblog
git init
git submodule add https://github.com/minyeamer/hugo-seotax themes/seotax
cp -R themes/seotax/exampleSite/* .
hugo server --minifyBelow is a full config.yaml example with all supported parameters:
baseURL: "https://example.com/"
title: "My Blog"
theme: "seotax"
defaultContentLanguage: "en"
languageCode: "en"
enableRobotsTXT: true
buildDrafts: false
buildFuture: false
buildExpired: false
enableEmoji: true
# Recommended: use a single dynamic search page instead of per-tag static pages
disableKinds: ["term"]
minify:
disableXML: true
minifyOutput: true
# SEO-friendly URL structure
permalinks:
posts: "/blog/:slugorcontentbasename/"
markup:
goldmark:
extensions:
typographer:
disable: true
renderer:
unsafe: true
highlight:
noClasses: false
codeFences: true
tableOfContents:
startLevel: 2
endLevel: 4
params:
author: "Your Name"
keywords: ["blog", "tech"]
posts:
section: "posts" # Content section for blog posts
sort: "newest" # ["newest" | "oldest" | "disabled"]
# Search
search:
enabled: true
sort: "newest" # ["relevance" | "newest" | "oldest" | "disabled"]
assets:
favicon: "/images/favicon.ico"
favicon16x16: "/images/favicon-16x16.png"
favicon32x32: "/images/favicon-32x32.png"
apple_touch_icon: "/images/apple-touch-icon.png"
safari_pinned_tab: "/images/safari-pinned-tab.svg"
opengraph: "/images/og.jpg"
# Sidebar menu
menu:
profileImage: "/images/profile.jpg" # Sidebar profile image
categories: true # Show category tree
recentPosts: true # Show recent posts list
social:
github: "https://github.com/username"
twitter: "https://x.com/username"
# Comments
comments:
enabled: true
provider: "giscus" # "giscus" | "disqus"
# disqus:
# shortname: "your-shortname"
# giscus:
# repo: "owner/repo"
# repoId: "R_kgDO..."
# category: "Comments"
# categoryId: "DIC_kwDO..."
# mapping: "pathname"
# inputPosition: "bottom"
# i18n translation directory
i18nDir: "themes/seotax/i18n"
# Image CLS prevention (optional)
# Store copies of remote images locally in assets/_images/ for build-time dimension extraction
images:
rootPath: "_images"
maxImageSize: 1920
roundedCorners: false
rotateLandscapeImages: true
# Enable Hugo portable links for relative Markdown links and images
portableLinks: true
# PWA Service Worker ("precache" to enable)
serviceWorker: "precache"
# Table of Contents
tableOfContents:
startLevel: 2
endLevel: 4
# Optional per-page ToC toggle default
toc:
enabled: true
# Analytics
GoogleAnalytics:
tagId: "G-XXXXXXXXXX"
# Search engine verification
searchEngine:
google:
siteVerificationTag: "google-site-verification-code"
naver:
siteVerificationTag: "naver-site-verification-code"
bing:
siteVerificationTag: "bing-site-verification-code"
yandex:
siteVerificationTag: "yandex-site-verification-code"
# Schema.org structured data
schema:
publisherType: "Person"
sameAs:
- "https://github.com/username"params.posts.sort controls the sort order of the home page.
The default sort uses / and /page/2/; the example site also provides
static archive pages at /list/newest/ and /list/oldest/, with additional
pages under /page/2/.
Set it to disabled to hide the sort control; the home page then uses newest-first
order, while a post-list page can still set its own params.sort.
Create matching list index files for each content language to enable the archive routes in your site:
# content/en/list/oldest/_index.md
title: "Oldest posts"
type: "post-list"
params:
sort: "oldest"Create the equivalent newest page and translated files for each additional language.
If params.sort is omitted in a post-list page, it falls back to params.posts.sort.
params.search.sort controls the default sort mode of /search.
Supported values are relevance, newest, oldest, and disabled.
disabled hides the sort control only; the search page continues to process
its default newest-first order and any sort query parameter.
- When the current sort matches
params.search.sort, the search page omits thesortquery parameter. - When the user selects a different sort, SeoTax writes
?sort=...into the search URL and reorders the current results on the client.
Set params.menu.categoryOrder to control the sidebar category order. Lower
weight values are shown first; categories without a configured weight follow
in alphabetical order.
params:
menu:
categoryOrder:
- name: "Guide"
weight: 10
- name: "Features"
weight: 20For a multilingual site, place the same setting under each language's params
to define language-specific names and ordering. The order is applied to both
parent and child categories.
Use params.images, not params.image.
rootPathsets the asset directory used to resolve local image dimensions during the build.maxImageSizelimits the resized image width used for CLS prevention.roundedCornersadds rounded styling to rendered Markdown images and theimageshortcode.rotateLandscapeImagesrotates landscape images in the image zoom viewer on narrow screens.
SeoTax supports Hugo's multilingual mode. When multiple languages are configured, a language selector appears in the sidebar menu.
languages:
en:
languageName: "English"
weight: 1
ko:
languageName: "한국어"
weight: 2In addition, the theme includes client-side i18n for UI elements (menus, labels, dates) across 27 languages. This works independently of Hugo's multilingual content system and requires no extra configuration.
The Hugo language menu opens a translated page when one exists. On localized taxonomy, search, and list pages without a page translation, it preserves the current path in the selected language and carries query parameters and hashes across. Search indexes and generated search assets are scoped per content language, so results do not mix between languages.
---
title: "My Post Title"
date: 2026-01-01
summary: "A brief description of the post."
categories: ["Parent Category", "Child Category"]
tags: ["tag1", "tag2"]
series: ["Series Name"]
cover:
image: "/images/cover.jpg"
---The theme uses a two-level category hierarchy.
The first item in categories is the parent; the second is the child.
Previous/next navigation prioritizes posts within the same child category,
then parent category, then all posts.
| Page | Description |
|---|---|
| Home | Post list with title, summary, date, categories, tags, and cover thumbnail. Pagination with 10 posts per page. |
| Single Post | Header with category breadcrumb, title, date, reading time, cover image, content, tags, prev/next navigation, and Disqus comments. |
| Search | Dynamic search page with keyword, category, and tag filters. Accessed via the search modal, category/tag chips, or the sidebar. |
| Categories | Two-level category tree with post counts. Up to 3 posts are shown per category with a "see more" link. |
| Tags | All tags displayed as clickable chips. |
SeoTax reuses the following shortcode components from Hugo Book.
| Shortcode | Description |
|---|---|
{{</* columns */>}} |
Multi-column flexbox layout with <---> separator and optional ratio parameter |
{{</* hint [info|success|warning|danger] */>}} |
Colored callout boxes |
{{</* mermaid */>}} |
Mermaid diagrams |
{{</* katex */>}} |
KaTeX math equations |
{{</* tabs */>}} |
Tabbed content panels |
{{</* details */>}} |
Collapsible content blocks |
Generates a rich link card by fetching Open Graph metadata from a URL.
{{</* bookmark url="https://example.com" */>}}Optional overrides: title, description, image, fetch (default true).
Renders CSV-formatted text as a styled HTML table.
{{</* data-table delimiter="," headers="1" file-name="data.csv" */>}}
Name,Age,City
Alice,30,Seoul
Bob,25,Tokyo
{{</* /data-table */>}}Supports align-center, enable-download, and custom class.
Enhanced image shortcode with click-to-zoom, automatic CLS prevention, and full layout control.
{{</* image src="/images/photo.jpg" alt="Description" caption="Figure 1" max-width="600px" */>}}Parameters: src, alt, caption, class, loading, align, href,
target, width, min-width, max-width, height, min-height,
max-height.
On mobile, landscape images rotate 90° when tapped for full-screen viewing.
Velog-style series navigation that groups posts sharing the same series front matter value.
{{</* series "My Tutorial Series" */>}}Optional second parameter: regex pattern to strip common prefixes from titles. Includes collapsible post list and prev/next links.
Create assets/css/_custom.scss in your site root to add or override styles:
// Example: change link color
:root {
--color-link: #0055bb;
}Key variables are defined in assets/css/variables/:
| File | Contents |
|---|---|
_colors.scss |
Color palette and theme mixins (theme-light, theme-dark) |
_defaults.scss |
Spacing, font sizes, border radii, breakpoints, z-indices |
_fonts.scss |
Font family definitions |
The theme uses an IcoMoon subset font with 26 icons.
Icon classes follow the pattern icon-*
(e.g., icon-search, icon-folder, icon-moon). To add more icons,
regenerate the subset font and update assets/css/main/icon.css.
seotax/
├── archetypes/ # Post templates
├── assets/
│ ├── css/ # SCSS source files
│ │ ├── main/ # Normalize, icons, utilities, print
│ │ ├── themes/ # Light/dark theme definitions
│ │ └── variables/ # Colors, defaults, fonts
│ ├── data/ # Build-time JSON (search index, categories, tags, i18n)
│ ├── js/
│ │ ├── core/ # Theme toggle, i18n, toolbar, sidebar
│ │ ├── partials/ # Reading time, ToC, image overlay, pagination
│ │ ├── search/ # Search engine, filters, rendering
│ │ └── shortcodes/ # Series, data-table, code block scripts
│ ├── main.scss # SCSS entry point
│ └── sw.js # Service Worker
├── i18n/ # 27 language YAML translation files
├── layouts/
│ ├── _markup/ # Render hooks (code blocks, images, links)
│ ├── _partials/ # Template partials (menu, header, footer, content)
│ ├── _shortcodes/ # Shortcode templates
│ ├── baseof.html # Base layout
│ ├── index.html # Home page
│ ├── list.html # List page
│ └── single.html # Single post page
├── static/ # Static assets (fonts, images)
└── theme.toml
The theme is optimized for Core Web Vitals:
| Metric | Score |
|---|---|
| CLS | 0.002 |
| TBT | 0 ms |
| Performance | 82+ |
Optimizations include: build-time image dimension extraction to prevent
layout shifts, subset icon font to minimize font loading impact, hybrid
server/client reading time calculation, and font-display: swap for zero FOIT.
Contributions are welcome. Primary goals:
- Keep it simple and focused on blog use cases
- Minimize JavaScript where CSS can solve the problem
- Maintain cross-browser compatibility
- Preserve the reader-first exploration experience
Feel free to open issues or pull requests on GitHub.










