11 KiB
Hugo + PaperMod Customization Guide
This guide covers how to customize your Hugo portfolio site using the PaperMod theme.
Table of Contents
- Project Structure
- Development Workflow
- Configuration (hugo.yaml)
- Adding Content
- Creating New Sections
- Customizing Styles (CSS)
- Overriding Layouts
- Common Customizations
- Deployment
Project Structure
MyPortfolio/
├── archetypes/ # Templates for new content
├── assets/
│ └── css/
│ └── extended/
│ └── custom.css # YOUR custom CSS (this gets merged)
├── content/
│ ├── posts/ # Long-form articles
│ │ └── _index.md # Section config
│ ├── notes/ # Short notes/TILs
│ │ └── _index.md # Section config
│ └── search.md # Search page
├── layouts/
│ ├── index.html # Custom home page (no recent posts)
│ ├── partials/ # Override theme partials
│ │ ├── footer.html # Custom footer
│ │ └── templates/
│ │ └── schema_json.html # Fixed JSON-LD schema
│ ├── posts/
│ │ └── list.html # Custom posts list (compact style)
│ └── notes/
│ └── list.html # Custom notes list (compact style)
├── static/
│ └── files/ # Static files (CV.pdf, images, etc.)
├── themes/
│ └── PaperMod/ # Theme (git submodule - DON'T edit)
├── hugo.yaml # Main configuration
└── .gitignore # Ignores public/, etc.
Key Principle: Never Edit the Theme Directly
The themes/PaperMod/ folder is a git submodule. To customize:
- CSS: Add to
assets/css/extended/custom.css - Layouts: Copy the file from
themes/PaperMod/layouts/tolayouts/and modify - Config: Everything in
hugo.yaml
Development Workflow
Local Development
# Start dev server (watches for changes)
hugo serve
# With drafts visible
hugo serve -D
# Build for production (outputs to public/)
hugo build --minify
Why public/ is Gitignored
public/is generated output - it's rebuilt fresh every time- Your CI/CD (GitHub Actions) builds it during deployment
- Never commit build artifacts to git
Git Workflow
# After making changes
git add .
git commit -m "Add new post about X"
git push origin master # Triggers deployment
Configuration (hugo.yaml)
Key Sections
Site Basics
baseURL: "/" # Use "/" for relative URLs (works on localhost + prod)
relativeURLs: true # Makes all URLs relative
title: Saurav Dhakal
languageCode: en-us
theme: ["PaperMod"]
Menu (Header Navigation)
menu:
main:
- identifier: posts # Internal ID
name: Posts # Display name
url: /posts/ # URL path
weight: 10 # Order (lower = first)
- identifier: notes
name: Notes
url: /notes/
weight: 20
- identifier: tags
name: Tags
url: /tags/
weight: 30
- identifier: search
name: Search
url: /search/
weight: 40
Home Page Info
params:
homeInfoParams:
Title: "Hi there 👋, I'm Saurav!"
Content: >
I'm a software engineer...
Social Icons
params:
socialIcons:
- name: github
url: "https://github.com/yourusername"
- name: linkedin
url: "https://linkedin.com/in/yourusername"
- name: x
url: "https://x.com/yourusername"
Available icons: github, linkedin, x, twitter, email, rss, youtube, instagram, facebook, stackoverflow, etc.
Adding Content
New Post
hugo new posts/my-new-post.md
Or manually create content/posts/my-new-post.md:
---
title: "My New Post"
date: 2025-08-20
summary: "A brief description for the list view"
tags: ["tag1", "tag2"]
categories: ["Category"]
draft: false
ShowToc: true
TocOpen: false
---
Your content here...
New Note
Create content/notes/my-note.md:
---
title: "Quick TIL"
date: 2025-08-20
summary: "Today I learned about X"
tags: ["til"]
---
Short content here...
Front Matter Options
| Field | Description |
|---|---|
title |
Post title |
date |
Publication date (YYYY-MM-DD) |
summary |
Short description for list views |
tags |
Array of tags |
categories |
Array of categories |
draft |
If true, won't be published |
ShowToc |
Show table of contents |
TocOpen |
TOC expanded by default |
ShowReadingTime |
Override global setting |
ShowWordCount |
Override global setting |
cover.image |
Cover image path |
Creating New Sections
Example: Adding a "Projects" Section
- Add to menu in
hugo.yaml:
menu:
main:
# ... existing items
- identifier: projects
name: Projects
url: /projects/
weight: 25
- Create content directory:
mkdir -p content/projects
- Create section index
content/projects/_index.md:
---
title: "Projects"
description: "Things I've built"
---
- Add project pages
content/projects/my-project.md:
---
title: "My Cool Project"
date: 2025-01-15
summary: "A brief description"
tags: ["golang", "cli"]
---
Project description...
- (Optional) Custom layout - If you want projects to look different, create
layouts/projects/list.html
Customizing Styles (CSS)
Where to Add CSS
Only edit: assets/css/extended/custom.css
PaperMod automatically includes this file. You don't need to import it anywhere.
CSS Variables (Theme Colors)
PaperMod uses CSS variables. Override them in your custom.css:
:root {
/* Light mode */
--primary: #282828; /* Main text */
--secondary: #3c3836; /* Secondary text */
--tertiary: rgb(214, 214, 214); /* Borders, etc */
--theme: rgb(255, 255, 255); /* Background */
--entry: rgb(255, 255, 255); /* Card background */
}
:root[data-theme="dark"] {
/* Dark mode */
--primary: #fbf1c7;
--secondary: #ebdbb2;
--theme: #181818;
--entry: rgb(46, 46, 51);
}
Common CSS Customizations
Change fonts
@import url("https://fonts.googleapis.com/css2?family=Your+Font&display=swap");
body {
font-family: "Your Font", sans-serif;
}
Style links
main a {
text-decoration: underline;
text-decoration-color: var(--green);
}
main a:hover {
background-color: var(--green);
color: white;
}
Customize post cards
main .post-entry {
border: 2px solid #383838;
background-color: var(--entry);
border-radius: 8px;
}
Overriding Layouts
How Layout Override Works
Hugo looks for templates in this order:
layouts/(your overrides)themes/PaperMod/layouts/(theme defaults)
To Override a Template
- Find the file in
themes/PaperMod/layouts/ - Copy it to the same path in
layouts/ - Modify your copy
Common Files to Override
| File | Purpose |
|---|---|
layouts/partials/header.html |
Site header/nav |
layouts/partials/footer.html |
Site footer |
layouts/partials/post_meta.html |
Post metadata (date, read time) |
layouts/_default/list.html |
List pages (posts, tags) |
layouts/_default/single.html |
Individual post/page |
Example: Simpler Footer
Your layouts/partials/footer.html already overrides the theme's footer.
Section-Specific Layouts
Create layouts/SECTION_NAME/list.html for a custom list layout for that section.
Current custom layouts:
layouts/index.html- Home page with just intro + social icons (no recent posts)layouts/posts/list.html- Compact list:# Title+ date + 2-line descriptionlayouts/notes/list.html- Compact list:# Title+ date (no description)
Common Customizations
Disable Reading Time for a Section
In the section's _index.md:
---
title: "Notes"
ShowReadingTime: false
---
Or per-post in front matter.
Add a Static Page (About, Contact)
Create content/about.md:
---
title: "About"
layout: "single"
url: "/about/"
---
About me content...
Add to menu:
menu:
main:
- identifier: about
name: About
url: /about/
weight: 50
Add Favicon
-
Place favicon files in
static/:static/favicon.icostatic/favicon-16x16.pngstatic/favicon-32x32.pngstatic/apple-touch-icon.png
-
Update
hugo.yaml:
params:
assets:
favicon: "/favicon.ico"
favicon16x16: "/favicon-16x16.png"
favicon32x32: "/favicon-32x32.png"
apple_touch_icon: "/apple-touch-icon.png"
Enable Comments (Disqus)
params:
comments: true
disqusShortname: "your-disqus-shortname"
Add Google Analytics
Already configured in your hugo.yaml:
googleAnalytics: "G-XXXXXXXXXX"
Deployment
Current Setup (GitHub Actions → SSH/SCP)
Your .github/workflows/deploy.yml:
- Triggers on push to
master - Builds site with
hugo build --minify - Deploys
public/via SCP to your server
Required GitHub Secrets
Set these in your repo's Settings → Secrets:
SSH_HOST- Your server hostname/IPSSH_USER- SSH usernameSSH_KEY- Private SSH keySSH_PORT- SSH port (usually 22)
Alternative: GitHub Pages
If you want to use GitHub Pages instead:
- Change workflow to use
peaceiris/actions-gh-pages - Set
baseURLtohttps://yourusername.github.io/repo-name/
Alternative: Netlify/Vercel
These platforms auto-detect Hugo and build for you:
- Connect your GitHub repo
- Set build command:
hugo --minify - Set publish directory:
public
Quick Reference
Useful Commands
# Local dev
hugo serve -D # Serve with drafts
# Create content
hugo new posts/title.md # New post
hugo new notes/title.md # New note
# Build
hugo build --minify # Production build
# Debug
hugo config # Show full config
hugo list all # List all content
File Locations
| What | Where |
|---|---|
| Config | hugo.yaml |
| Custom CSS | assets/css/extended/custom.css |
| Posts | content/posts/ |
| Notes | content/notes/ |
| Static files | static/ |
| Layout overrides | layouts/ |
Troubleshooting
Links Go to Production URL in Dev
Fixed by setting:
baseURL: "/"
relativeURLs: true
Changes Not Showing
- Hard refresh browser (Ctrl+Shift+R)
- Clear
public/folder:rm -rf public/ - Restart hugo serve
Theme Not Loading
git submodule update --init --recursive
Search Not Working
Ensure hugo.yaml has:
outputs:
home:
- HTML
- RSS
- JSON # Required for search