Skip to content

自定义配置

本指南介绍如何自定义 Hugo 网站的外观、行为和功能,从简单样式调整到高级布局修改。

概览

网站支持多层次自定义:

  • 配置:站点设置、参数与行为
  • 样式:颜色、字体、间距与视觉设计
  • 布局:页面结构与模板修改
  • 内容:自定义内容类型与组织方式
  • 功能:JavaScript 特性与交互

站点配置

基础配置

主配置文件位于 config/_default/

config/
├── _default/
│   ├── config.toml      # Hugo 核心设置
│   ├── params.toml      # 主题参数
│   ├── menus.en.toml    # 英文导航
│   └── menus.zh.toml    # 中文导航

核心设置

编辑 config/_default/config.toml

baseURL = "https://your-domain.com"
title = "你的站点标题"
description = "你的站点描述"

# 语言设置
DefaultContentLanguage = "zh"
hasCJKLanguage = true

# 启用功能
enableGitInfo = true
pygmentsUseClasses = true

[params]
author = "你的名字"
email = "[email protected]"

主题参数

config/_default/params.toml 自定义主题行为:

# 站点品牌
[params]
header_title = "你的站点"
description = "你的描述"
logo = "images/logo.svg"
brand_icon = "fas fa-terminal"

# 功能
enable_comment = true
anchored = true
top_header = true
top_header_content = "欢迎信息"

# 分析
google_analytics_id = "UA-XXXXXXXX-X"

# 搜索
[search]
enable = true
provider = "wowchemy"

导航菜单

config/_default/menus.*.toml 配置导航:

# 中文菜单 (menus.zh.toml)
[[main]]
name = "首页"
url = "/"
weight = 1

[[main]]
name = "博客"
url = "/blog/"
weight = 2

[[main]]
name = "书籍"
url = "/book/"
weight = 3
hasChildren = true

  [[main]]
  parent = "书籍"
  name = "Kubernetes 手册"
  url = "/book/kubernetes-handbook/"
  weight = 1

视觉自定义

颜色方案

assets/scss/_variables.scss 自定义颜色:

// 主色
$primary-color: #007bff;
$secondary-color: #6c757d;
$success-color: #28a745;
$warning-color: #ffc107;
$danger-color: #dc3545;

// 背景色
$body-bg: #ffffff;
$card-bg: #f8f9fa;
$navbar-bg: #ffffff;

// 文字色
$text-color: #333333;
$text-muted: #6c757d;
$link-color: #007bff;
$link-hover-color: #0056b3;

// 暗色模式
$dark-bg: #1a1a1a;
$dark-card-bg: #2d2d2d;
$dark-text: #ffffff;
$dark-text-muted: #cccccc;

排版

配置字体与排版:

// 字体族
$font-family-sans-serif: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
$font-family-monospace: "JetBrains Mono", "Fira Code", Consolas, monospace;
$font-family-serif: "Merriweather", Georgia, serif;

// 字号
$font-size-base: 1rem;
$font-size-lg: 1.125rem;
$font-size-sm: 0.875rem;

// 标题
$h1-font-size: 2.5rem;
$h2-font-size: 2rem;
$h3-font-size: 1.75rem;
$h4-font-size: 1.5rem;
$h5-font-size: 1.25rem;
$h6-font-size: 1rem;

// 行高
$line-height-base: 1.6;
$line-height-sm: 1.4;
$line-height-lg: 1.8;

自定义 CSS

assets/scss/_custom.scss 添加自定义样式:

// 自定义组件样式
.custom-hero {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 4rem 0;

  h1 {
    font-size: 3rem;
    font-weight: 700;
    margin-bottom: 1rem;
  }

  p {
    font-size: 1.25rem;
    opacity: 0.9;
  }
}

// 自定义按钮样式
.btn-custom {
  background: linear-gradient(45deg, #ff6b6b, #ee5a24);
  border: none;
  color: white;
  padding: 0.75rem 2rem;
  border-radius: 50px;
  font-weight: 600;
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 20px rgba(0,0,0,0.1);
  }
}

// 自定义卡片样式
.card-custom {
  border: none;
  border-radius: 15px;
  box-shadow: 0 5px 15px rgba(0,0,0,0.08);
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 15px 35px rgba(0,0,0,0.1);
  }
}

暗色模式

配置暗色模式样式:

// 暗色模式变量
:root {
  --bg-color: #{$body-bg};
  --text-color: #{$text-color};
  --card-bg: #{$card-bg};
  --border-color: #{$border-color};
}

[data-theme="dark"] {
  --bg-color: #{$dark-bg};
  --text-color: #{$dark-text};
  --card-bg: #{$dark-card-bg};
  --border-color: #{$dark-border-color};
}

// 组件中使用变量
.card {
  background-color: var(--card-bg);
  color: var(--text-color);
  border-color: var(--border-color);
}

布局自定义

页面模板

layouts/ 目录下覆盖默认模板:

layouts/
├── _default/
│   ├── baseof.html      # 基础模板
│   ├── single.html      # 单页模板
│   └── list.html        # 列表页模板
├── blog/
│   └── single.html      # 博客文章模板
├── book/
│   ├── single.html      # 书籍页面模板
│   └── list.html        # 书籍列表模板
└── partials/
    ├── header.html      # 头部
    ├── footer.html      # 底部
    └── sidebar.html     # 侧边栏组件

自定义页面模板

为特定内容创建自定义模板:

<!-- layouts/blog/single.html -->
{{ define "main" }}
<article class="blog-post">
  <header class="post-header">
    <h1 class="post-title">{{ .Title }}</h1>
    <div class="post-meta">
      <time datetime="{{ .Date.Format "2006-01-02" }}">
        {{ .Date.Format "January 2, 2006" }}
      </time>
      {{ with .Params.categories }}
        <span class="categories">
          {{ range . }}
            <a href="{{ "/categories/" | relURL }}{{ . | urlize }}/" class="category">{{ . }}</a>
          {{ end }}
        </span>
      {{ end }}
    </div>
  </header>

  {{ if .Params.image }}
    <div class="post-image">
      <img src="{{ .Params.image | relURL }}" alt="{{ .Title }}" />
    </div>
  {{ end }}

  <div class="post-content">
    {{ .Content }}
  </div>

  <footer class="post-footer">
    {{ with .Params.tags }}
      <div class="tags">
        {{ range . }}
          <a href="{{ "/tags/" | relURL }}{{ . | urlize }}/" class="tag">#{{ . }}</a>
        {{ end }}
      </div>
    {{ end }}
  </footer>
</article>
{{ end }}

自定义 Partial

layouts/partials/ 创建可复用组件:

<!-- layouts/partials/custom-hero.html -->
<section class="hero custom-hero">
  <div class="container">
    <div class="row align-items-center">
      <div class="col-lg-6">
        <h1>{{ .title | default "欢迎" }}</h1>
        <p>{{ .description | default "你的描述" }}</p>
        {{ if .button_text }}
          <a href="{{ .button_url | default "#" }}" class="btn btn-custom">
            {{ .button_text }}
          </a>
        {{ end }}
      </div>
      {{ if .image }}
        <div class="col-lg-6">
          <img src="{{ .image | relURL }}" alt="{{ .title }}" class="img-fluid" />
        </div>
      {{ end }}
    </div>
  </div>
</section>

在模板中调用 partial:

{{ partial "custom-hero.html" (dict "title" "我的标题" "description" "我的描述" "button_text" "立即开始" "button_url" "/getting-started/") }}

内容类型自定义

自定义 Archetype

archetypes/ 创建内容模板:

# archetypes/tutorial.md
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
categories:
- "教程"
tags:
- "tutorial"
description: ""
difficulty: "beginner"  # 初级、中级、高级
duration: "30 分钟"
prerequisites:
- "基础知识..."
learning_objectives:
- "学会如何..."
- "理解相关概念..."
resources:
- name: "资源 1"
  url: "https://example.com"
draft: true
---

## 概述

简要介绍本教程内容和目标。

## 前置条件

开始前请确保:

- [ ] 完成基础设置
- [ ] 了解相关概念

## 步骤

### 步骤 1:准备工作

详细说明第一步。

### 步骤 2:实施

具体实施步骤。

## 总结

总结教程要点和下一步行动。

## 相关资源

- [相关教程 1](link)
- [相关教程 2](link)

自定义 Front Matter

为特定内容类型定义自定义 front matter 字段:

# 产品页面示例
---
title: "产品名称"
price: "¥99"
features:
- "功能 1"
- "功能 2"
specifications:
  weight: "1.2kg"
  dimensions: "30x20x10cm"
  color: "黑色"
gallery:
- "image1.jpg"
- "image2.jpg"
reviews:
  rating: 4.5
  count: 127
---

JavaScript 自定义

自定义脚本

assets/js/custom.js 添加自定义 JS:

// 自定义功能
document.addEventListener('DOMContentLoaded', function() {
  // 锚点平滑滚动
  document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function (e) {
      e.preventDefault();
      const target = document.querySelector(this.getAttribute('href'));
      if (target) {
        target.scrollIntoView({
          behavior: 'smooth',
          block: 'start'
        });
      }
    });
  });

  // 自定义搜索
  const searchInput = document.querySelector('#custom-search');
  if (searchInput) {
    searchInput.addEventListener('input', function() {
      const query = this.value.toLowerCase();
      const items = document.querySelectorAll('.searchable-item');

      items.forEach(item => {
        const text = item.textContent.toLowerCase();
        item.style.display = text.includes(query) ? 'block' : 'none';
      });
    });
  }

  // 主题切换
  const themeToggle = document.querySelector('#theme-toggle');
  if (themeToggle) {
    themeToggle.addEventListener('click', function() {
      const currentTheme = document.documentElement.getAttribute('data-theme');
      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

      document.documentElement.setAttribute('data-theme', newTheme);
      localStorage.setItem('theme', newTheme);
    });
  }
});

// 通知工具函数
function showNotification(message, type = 'info') {
  const notification = document.createElement('div');
  notification.className = `notification notification-${type}`;
  notification.textContent = message;

  document.body.appendChild(notification);

  setTimeout(() => {
    notification.classList.add('show');
  }, 100);

  setTimeout(() => {
    notification.classList.remove('show');
    setTimeout(() => {
      document.body.removeChild(notification);
    }, 300);
  }, 3000);
}

// 自定义分析追踪
function trackEvent(category, action, label) {
  if (typeof gtag !== 'undefined') {
    gtag('event', action, {
      event_category: category,
      event_label: label
    });
  }
}

交互组件

创建交互组件:

// 自定义手风琴组件
class CustomAccordion {
  constructor(element) {
    this.element = element;
    this.triggers = element.querySelectorAll('.accordion-trigger');
    this.init();
  }

  init() {
    this.triggers.forEach(trigger => {
      trigger.addEventListener('click', (e) => {
        e.preventDefault();
        this.toggle(trigger);
      });
    });
  }

  toggle(trigger) {
    const content = trigger.nextElementSibling;
    const isOpen = trigger.classList.contains('active');

    // 关闭其他项
    this.triggers.forEach(t => {
      if (t !== trigger) {
        t.classList.remove('active');
        t.nextElementSibling.style.maxHeight = null;
      }
    });

    // 切换当前项
    if (isOpen) {
      trigger.classList.remove('active');
      content.style.maxHeight = null;
    } else {
      trigger.classList.add('active');
      content.style.maxHeight = content.scrollHeight + 'px';
    }
  }
}

// 初始化手风琴
document.addEventListener('DOMContentLoaded', function() {
  document.querySelectorAll('.custom-accordion').forEach(accordion => {
    new CustomAccordion(accordion);
  });
});

高级自定义

自定义 Shortcode

layouts/shortcodes/ 创建自定义 shortcode:

<!-- layouts/shortcodes/pricing-table.html -->
<div class="pricing-table">
  {{ range .Site.Data.pricing }}
    <div class="pricing-plan {{ if .featured }}featured{{ end }}">
      <h3 class="plan-name">{{ .name }}</h3>
      <div class="plan-price">
        <span class="currency">{{ .currency }}</span>
        <span class="amount">{{ .price }}</span>
        <span class="period">{{ .period }}</span>
      </div>
      <ul class="plan-features">
        {{ range .features }}
          <li>{{ . }}</li>
        {{ end }}
      </ul>
      <a href="{{ .signup_url }}" class="btn btn-primary">{{ .button_text }}</a>
    </div>
  {{ end }}
</div>

数据文件

data/ 目录创建数据文件:

# data/pricing.yaml
- name: "基础版"
  price: 9
  currency: "¥"
  period: "/月"
  features:
    - "5 个项目"
    - "10GB 存储"
    - "邮件支持"
  button_text: "立即开始"
  signup_url: "/signup/basic"

- name: "专业版"
  price: 29
  currency: "¥"
  period: "/月"
  featured: true
  features:
    - "无限项目"
    - "100GB 存储"
    - "优先支持"
    - "高级分析"
  button_text: "升级专业"
  signup_url: "/signup/pro"

自定义构建流程

用自定义脚本扩展构建流程:

// scripts/custom-build.js
const fs = require('fs');
const path = require('path');

// 图片处理
function processImages() {
  const imagesDir = path.join(__dirname, '../static/images');
  const outputDir = path.join(__dirname, '../static/images/processed');

  // 创建输出目录
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  // 图片处理逻辑(示例)
  console.log('正在处理图片...');
  // 此处添加图片处理代码
}

// 内容生成
function generateSitemap() {
  const contentDir = path.join(__dirname, '../content');
  const pages = [];

  // 扫描内容目录
  function scanDirectory(dir) {
    const files = fs.readdirSync(dir);
    files.forEach(file => {
      const filePath = path.join(dir, file);
      const stat = fs.statSync(filePath);

      if (stat.isDirectory()) {
        scanDirectory(filePath);
      } else if (file.endsWith('.md')) {
        const relativePath = path.relative(contentDir, filePath);
        pages.push(relativePath);
      }
    });
  }

  scanDirectory(contentDir);

  // 生成 sitemap
  const sitemap = pages.map(page => {
    const url = page.replace(/\.md$/, '/').replace(/\\/g, '/');
    return `https://your-domain.com/${url}`;
  }).join('\n');

  fs.writeFileSync(path.join(__dirname, '../static/sitemap.txt'), sitemap);
  console.log(`已生成 ${pages.length} 个页面的 sitemap`);
}

// 执行自定义构建任务
processImages();
generateSitemap();

package.json 添加:

{
  "scripts": {
    "build": "node scripts/custom-build.js && hugo --environment production --minify",
    "build:custom": "node scripts/custom-build.js"
  }
}

性能优化

资源优化

config/_default/config.toml 配置资源处理:

[build]
postCSS = ["postcss.config.js"]

[imaging]
resampleFilter = "lanczos"
quality = 75
anchor = "smart"

[minify]
  [minify.tdewolff]
    [minify.tdewolff.html]
      keepWhitespace = false
    [minify.tdewolff.css]
      keepCSS2 = true
    [minify.tdewolff.js]
      keepVarNames = false

缓存策略

Configure caching headers:

# netlify.toml or _headers file
[[headers]]
  for = "*.css"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

[[headers]]
  for = "*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

[[headers]]
  for = "*.woff2"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

Testing Customizations

Local Testing

Test your customizations locally:

# Start development server
hugo server

# Test with different environments
hugo server --environment production

# Test build process
npm run build

# Test on different devices
hugo server --bind 0.0.0.0 --port 1313

Validation

Validate your customizations:

# Check HTML validity
npm run test:html

# Check CSS
npm run test:css

# Check JavaScript
npm run test:js

# Check accessibility
npm run test:a11y

Troubleshooting

Common Issues

Styles Not Loading

Problem: Custom CSS doesn't appear on the site

Solutions:

  1. Check file paths in SCSS imports
  2. Verify build process includes CSS compilation
  3. Clear browser cache
  4. Check for CSS syntax errors

JavaScript Errors

Problem: Custom JavaScript doesn't work

Solutions:

  1. Check browser console for errors
  2. Verify script loading order
  3. Ensure DOM elements exist before accessing them
  4. Check for JavaScript syntax errors

Layout Issues

Problem: Custom layouts don't render correctly

Solutions:

  1. Check template syntax
  2. Verify template file locations
  3. Check for missing partials or data
  4. Test with different content types

Examples

Here are some examples of what you can achieve with customization:

Example: Creating a Promotional Banner

You can use a custom partial to create a promotional banner that appears on top of your homepage.

  1. Create the partial (layouts/partials/promo-banner.html):

    <div class="promo-banner">
      <p>🎉 Special Offer! Get 20% off on all books. <a href="/store">Shop Now</a></p>
    </div>
    
  2. Add styling (assets/scss/_custom.scss):

    .promo-banner {
      background-color: var(--primary-color);
      color: white;
      text-align: center;
      padding: 0.5rem;
    }
    
  3. Include the partial in layouts/_default/baseof.html:

    <body>
      {{ partial "promo-banner.html" . }}
      {{ partial "header.html" . }}
      ...
    </body>
    

Example: Adding a "Last Updated" Date

Automatically display the last modification date on your blog posts.

  1. Enable enableGitInfo in config/_default/config.toml:

    enableGitInfo = true
    
  2. Modify the template (layouts/blog/single.html):

    <div class="post-meta">
      ...
      {{ if .GitInfo }}
        <span>Last updated on {{ .GitInfo.AuthorDate.Format "January 2, 2006" }}</span>
      {{ end }}
    </div>
    

Next Steps

After customizing your site:


Need help? Check our Troubleshooting Guide or Contributing Guidelines.