Complete Deployment Guide for Hugo Blog on GitHub Pages #
★Publishing to GitHub #
◇1. Create GitHub Repository #
mywebsite
(for Hugo source code)yuuniji.github.io
(for generated static files)
◇2. Push mywebsite
Repository
#
git remote add origin https://github.com/yuuniji/mywebsite.git
git branch -M main
git add .
git commit -m "Initial commit"
git push -u origin main
◇3. Generate Static Files #
hugo -D
Static files are located in the public/
directory.
◇4. Push to yuuniji.github.io
#
cd public
git init
git remote add origin https://github.com/yuuniji/yuuniji.github.io.git
git checkout -b main
git add .
git commit -m "Deploy Hugo site"
git push -f origin main
★Configure GitHub Pages #
- Enter
yuuniji.github.io
repository Settings → Pages. - Select
main
branch, save, and wait for deployment to complete. - Visit
https://yuuniji.github.io
to view the blog.
★Automated Deployment (Optional) #
◇1. Add deploy.sh
to mywebsite
repository
#
nano deploy.sh
Paste the following content:
#!/bin/bash
hugo -D
cd public
git add .
git commit -m "Deploy: $(date)"
git push origin main
cd ..
Save and exit (press Ctrl + X
, then Y
, and press Enter).
◇2. Assign Execution Permissions #
chmod +x deploy.sh
◇3. Run Deployment Script #
./deploy.sh
★All Done! #
After updating the blog, just:
- Write articles in
mywebsite/
directoryhugo new posts/xxx.md
- Run
./deploy.sh
- Visit
https://yuuniji.github.io
to view updates
💡 Refer back to this guide if you have any questions! 🚀
★ My Website Structure #
- Categories(Fixed large categories)
- Tags(Keywords)
- Topics(Series/Themes)
Categories:
├── Language Acquisition
├── IT / Technology
├── Reading Notes
├── Life Records
└── Reflections & Essays
Tags:
├── Japanese, English, Grammar
├── Front-end, JavaScript, Cloud Computing
├── Philosophy, Economics, Psychology, History
├── Travel, 318 Sichuan-Tibet Highway, Photography
├── Time Management, Productivity Improvement...
Topics:
├── How to Master Japanese
├── Top 10 Books
├── 2025 Reading Log...
★Hugo Function Extension Guide #
◇Quotes #
Create quoteszh.html file and place it in the layouts/shortcodes/ directory of your Hugo site. Then, in content/../_index.md, reference {{\< quoteszh >}}
(remove \
).
<!-- layouts/shortcodes/quoteszh.html -->
<blockquote>
<p>{{ .Inner }}</p>
<footer>{{ .Get "source" }}</footer>
</blockquote>
<div id="quote" class="quote-box"></div>
<script>
const quotes = [
"",
"",
""
]
// 随机选择一个引语
const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
// 显示引语
document.getElementById('quote').innerHTML = randomQuote;
</script>
<style>
.quote-box {
max-width: 600px;
font-size: 0.8rem;
text-align: left;
.quote-box {
animation: fadeIn 1.5s ease-in-out;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
◇Add Website Runtime #
Add the following code to the newly created /layouts/partials/extend-footer.html
:
<span id="runtime_span"></span> <!-- 用于显示网站运行时间的容器 -->
<script type="text/javascript">
function show_runtime() {
// 每隔 1 秒执行一次 show_runtime,实现实时更新
setTimeout(show_runtime, 1000);
// 设置网站起始运行时间(2025年2月22日 00:00:00)
const startDate = new Date("2025/02/22 00:00:00");
// 获取当前时间
const now = new Date();
// 计算时间差(毫秒)
const diff = now.getTime() - startDate.getTime();
// 计算运行的天、小时、分钟、秒
const days = Math.floor(diff / (24 * 60 * 60 * 1000)); // 天数
const hours = Math.floor((diff % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)); // 小时
const minutes = Math.floor((diff % (60 * 60 * 1000)) / (60 * 1000)); // 分钟
const seconds = Math.floor((diff % (60 * 1000)) / 1000); // 秒
// 获取当前 HTML 页面设置的语言(由 Hugo 生成,如 <html lang="ja">)
const lang = document.documentElement.lang || "en"; // 默认英文
// 各语言的显示文本模板
const translations = {
zh: `网站已运行 ${days} 天 ${hours} 小时 ${minutes} 分 ${seconds} 秒`,
ja: `サイトは稼働してから ${days}日 ${hours}時間 ${minutes}分 ${seconds}秒`,
en: `Site has been running for ${days}d ${hours}h ${minutes}m ${seconds}s`
};
// 根据当前语言选择显示内容,若找不到则使用英文
const output = translations[lang] || translations["en"];
// 将显示内容写入页面中
document.getElementById("runtime_span").innerHTML = output;
}
// 初始化运行
show_runtime();
</script>
◇Giscus Comments #
Check params.toml
#
article.showComments = true
Prepare GitHub Repository #
- Login to your GitHub account.
- Open your blog repository (e.g.,
yourname/yourwebsite
). - Ensure that Discussions is enabled:
- Go to Settings → Features → Check Discussions.
- Go to the Discussions page and create a Discussion Category (e.g.,
General
).
Generate Giscus Embed Code #
Open Giscus configuration page: 👉 https://giscus.app
Configure as follows:
- Repository: Your blog repository (e.g.
yourname/yourblog
) - Repository ID / Category / Category ID: Automatically generated based on the information in your Discussions
- Discussion Mapping: It is recommended to select
pathname
- Reaction: Enable (👍, etc.)
- Input Position: bottom (comment box at the bottom)
- Theme:
preferred_color_scheme
(automatically adapt to light/dark colors)
Copy the generated <script>
code.
Insert Giscus code into Hugo template #
You can put the Giscus code into a separate file, for example: layouts/partials/giscus.html
<div id="giscus_container"></div>
<script src="https://giscus.app/client.js"
data-repo="yourname/yourblog"
data-repo-id="Your repo ID"
data-category="General"
data-category-id="Your category ID"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="zh-CN"
crossorigin="anonymous"
async>
</script>
Then in layouts/partials/comments.html
article template:
{{ partial "giscus.html" . }}
◇Music Player #
- A mini player fixed to the bottom-right corner of the screen
- Expandable/collapsible playlist
- Control buttons for play/pause, previous, and next
- Preserve playback state when switching between articles (using
localStorage
) - Frosted glass background design for the music player
Create file #
<!-- layouts/partials/musicplayer.html -->
<style scoped>
.music-player-container {
position: fixed;
bottom: 12px;
left: 12px;
z-index: 9999;
font-family: system-ui, sans-serif;
font-size: 13px;
}
.music-player-container .music-panel {
display: none;
margin-bottom: 6px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 12px 14px;
width: 200px;
max-height: 180px;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
}
.music-player-container .music-panel.show {
display: block;
}
.music-player-container .music-title {
font-size: 13px;
color: var(--color-primary-300);
font-weight: 500;
margin-bottom: 6px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.music-player-container .playlist {
list-style: none;
padding: 0;
margin: 0;
}
.music-player-container .playlist li {
padding: 4px 6px;
border-radius: 6px;
cursor: pointer;
color: var(--content);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
transition: all 0.2s ease;
}
.music-player-container .playlist li:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.music-player-container .playlist li.active {
background: rgba(255, 255, 255, 0.2);
color: var(--accent);
font-weight: 600;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.music-player-container .mini-player {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 100px;
display: flex;
gap: 8px;
padding: 6px 10px;
align-items: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
transition: all 0.3s ease;
}
.music-player-container .mini-player:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}
.music-player-container .mini-player button {
background: none;
border: none;
font-size: 16px;
color: var(--secondary);
cursor: pointer;
padding: 4px;
border-radius: 50%;
transition: all 0.2s ease;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.music-player-container .mini-player button.hidden {
display: none;
}
.music-player-container.hidden {
transform: translateX(-100%);
opacity: 0;
pointer-events: none;
}
.show-player-btn {
position: fixed;
bottom: 12px;
left: 12px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 40px;
height: 40px;
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 16px;
color: var(--secondary);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
transition: all 0.3s ease;
z-index: 9999;
}
.music-player-container.hidden + .show-player-btn {
display: flex;
}
.show-player-btn:hover {
background: rgba(255, 255, 255, 0.15);
transform: scale(1.1);
}
.music-player-container .mini-player button:hover {
color: var(--accent);
background: rgba(255, 255, 255, 0.1);
transform: scale(1.1);
}
.music-player-container .mini-player button:active {
color: var(--accent-active);
transform: scale(0.95);
}
.music-player-container audio {
display: none;
}
/* 滚动条美化 */
.music-player-container .music-panel::-webkit-scrollbar {
width: 4px;
}
.music-player-container .music-panel::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.music-player-container .music-panel::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
}
.music-player-container .music-panel::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
</style>
<div class="music-player-container" id="musicPlayerContainer">
<div class="music-panel" id="musicPanel">
<div class="music-title" id="musicTitle">Loading...</div>
<ul class="playlist" id="musicPlaylist"></ul>
</div>
<div class="mini-player" id="miniPlayer">
<button id="musicTogglePanel" title="Toggle Playlist">📂</button>
<button id="musicPrevBtn" title="Previous">⏮️</button>
<button id="musicPlayBtn" title="Play">▶️</button>
<button id="musicNextBtn" title="Next">⏭️</button>
<button id="musicHideBtn" title="Hide Player">👁️</button>
</div>
<audio id="musicAudio"></audio>
</div>
<div class="show-player-btn" id="showPlayerBtn" title="Show Music Player">🎧</div>
<script>
(function() {
'use strict';
// 命名空间前缀,避免全局冲突
const MUSIC_PLAYER_NS = 'HugoMusicPlayer_';
const base = "https://yuuniji.github.io/music/lofi_beats/";
const jsonURL = base + "songs.json";
const STORAGE_KEY = MUSIC_PLAYER_NS + "state";
const INTERACT_KEY = MUSIC_PLAYER_NS + "user_interacted";
const HIDDEN_KEY = MUSIC_PLAYER_NS + "hidden";
async function initMusicPanel() {
try {
const res = await fetch(jsonURL);
const songs = await res.json();
if (!songs.length) return;
let currentIndex = 0;
let isPlaying = false;
// 使用带命名空间的ID选择器
const audio = document.getElementById("musicAudio");
const playBtn = document.getElementById("musicPlayBtn");
const prevBtn = document.getElementById("musicPrevBtn");
const nextBtn = document.getElementById("musicNextBtn");
const toggleBtn = document.getElementById("musicTogglePanel");
const hideBtn = document.getElementById("musicHideBtn");
const showBtn = document.getElementById("showPlayerBtn");
const panel = document.getElementById("musicPanel");
const title = document.getElementById("musicTitle");
const playlist = document.getElementById("musicPlaylist");
// 检查元素是否存在
if (!audio || !playBtn || !prevBtn || !nextBtn || !toggleBtn || !hideBtn || !showBtn || !panel || !title || !playlist) {
console.warn('Music player elements not found');
return;
}
// 恢复保存的状态
const savedStateStr = localStorage.getItem(STORAGE_KEY);
if (savedStateStr) {
try {
const saved = JSON.parse(savedStateStr);
if (saved.index >= 0 && saved.index < songs.length) {
currentIndex = saved.index;
audio.currentTime = saved.time || 0;
// 恢复播放状态,如果之前是播放状态则继续播放
isPlaying = saved.isPlaying || false;
}
} catch (e) {
console.warn('Failed to parse saved music player state:', e);
}
}
// 恢复隐藏状态
const isHidden = localStorage.getItem(HIDDEN_KEY) === 'true';
if (isHidden) {
document.getElementById("musicPlayerContainer").classList.add('hidden');
}
// 构建播放列表
songs.forEach((song, index) => {
const li = document.createElement("li");
li.textContent = song.title;
li.onclick = function() {
currentIndex = index;
loadAndPlay(currentIndex);
localStorage.setItem(INTERACT_KEY, "true");
};
playlist.appendChild(li);
});
function highlight(index) {
const listItems = playlist.querySelectorAll("li");
listItems.forEach((li, idx) => {
li.classList.toggle("active", idx === index);
});
}
function loadAndPlay(index) {
audio.src = base + songs[index].file;
audio.currentTime = 0;
title.textContent = songs[index].title;
highlight(index); // 立即高亮
audio.play().then(() => {
isPlaying = true;
playBtn.textContent = "⏸️";
saveState();
}).catch((error) => {
console.warn('Failed to play audio:', error);
isPlaying = false;
playBtn.textContent = "▶️";
});
}
function saveState() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
index: currentIndex,
time: audio.currentTime,
isPlaying: !audio.paused
}));
} catch (e) {
console.warn('Failed to save music player state:', e);
}
}
// 初始化显示
audio.src = base + songs[currentIndex].file;
title.textContent = songs[currentIndex].title;
highlight(currentIndex);
// 根据恢复的状态决定是否播放
if (isPlaying) {
audio.play().then(() => {
updatePlayButton();
saveState();
}).catch((error) => {
console.warn('Failed to auto-play audio:', error);
isPlaying = false;
updatePlayButton();
});
} else {
audio.pause();
isPlaying = false;
updatePlayButton(); // 确保按钮状态正确
}
// 更新播放按钮状态的函数
function updatePlayButton() {
if (audio.paused) {
playBtn.textContent = "▶️";
isPlaying = false;
// 暂停状态时隐藏其他按钮
prevBtn.classList.add('hidden');
nextBtn.classList.add('hidden');
toggleBtn.classList.add('hidden');
hideBtn.classList.add('hidden');
} else {
playBtn.textContent = "⏸️";
isPlaying = true;
// 播放状态时显示所有按钮
prevBtn.classList.remove('hidden');
nextBtn.classList.remove('hidden');
toggleBtn.classList.remove('hidden');
hideBtn.classList.remove('hidden');
}
}
// 事件监听器
playBtn.onclick = function() {
if (audio.paused) {
audio.play().then(() => {
updatePlayButton();
saveState();
}).catch((error) => {
console.warn('Failed to play audio:', error);
updatePlayButton();
});
} else {
audio.pause();
updatePlayButton();
saveState();
}
};
prevBtn.onclick = function() {
currentIndex = (currentIndex - 1 + songs.length) % songs.length;
loadAndPlay(currentIndex);
};
nextBtn.onclick = function() {
currentIndex = (currentIndex + 1) % songs.length;
loadAndPlay(currentIndex);
};
toggleBtn.onclick = function() {
panel.classList.toggle("show");
};
hideBtn.onclick = function() {
document.getElementById("musicPlayerContainer").classList.add('hidden');
localStorage.setItem(HIDDEN_KEY, 'true');
};
showBtn.onclick = function() {
document.getElementById("musicPlayerContainer").classList.remove('hidden');
localStorage.setItem(HIDDEN_KEY, 'false');
};
// 音频事件监听
audio.ontimeupdate = saveState;
audio.onplay = function() {
updatePlayButton();
saveState();
};
audio.onpause = function() {
updatePlayButton();
saveState();
};
audio.onended = function() {
currentIndex = (currentIndex + 1) % songs.length;
loadAndPlay(currentIndex);
};
// 移除自动播放功能,用户需要手动点击播放
// 确保初始状态为暂停
updatePlayButton();
// 页面卸载时保存状态
window.addEventListener('beforeunload', function() {
saveState();
});
// 页面隐藏时也保存状态(移动设备切换应用时)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
saveState();
}
});
} catch (error) {
console.error('Failed to initialize music player:', error);
}
}
// 确保DOM加载完成后再初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMusicPanel);
} else {
initMusicPanel();
}
})();
</script>
In layout template #
Add the following code to the newly created /layouts/partials/extend-footer.html
:
{{ partial "musicplayer.html" . }}
Icon #
https://favicon.io/favicon-generator/
website/
├── static/
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── site.webmanifest
当然,以下是更精简且专业的英文笔记:
◇Sidenote Shortcode #
1. Usage #
-
Markdown:
{{\< sidenote label="☍" >}}Your note here{{\< /sidenote >}} (remove `\`).
-
e.g.: The term “sidenote" A small note that provides extra context without interrupting the flow. is often used in academic writing.
-
File:
layouts/shortcodes/sidenote.html
2. Shortcode (HTML) #
{{- $id := .Get "id" | default (printf "sn-%d" (.Page.Scratch.Get "sidenoteID" | default 1)) -}}
{{- $label := .Get "label" | default "☍" -}}
<span class="sidenote-block">
<label for="{{ $id }}-toggle" class="sidenote-label">{{ $label }}</label>
<input type="checkbox" id="{{ $id }}-toggle" class="sidenote-toggle" hidden>
<span class="sidenote">{{ .Inner }}</span>
</span>
{{- .Page.Scratch.Set "sidenoteID" ((.Page.Scratch.Get "sidenoteID" | default 1) | add 1) -}}
3. CSS #
/* assets/css/custom.css */
.sidenote-block {
position: relative;
display: inline-block;
}
.sidenote-label {
font-size: 0.8em;
vertical-align: super;
color: #888;
cursor: pointer;
user-select: none;
}
.sidenote {
display: none;
position: absolute;
left: 100%;
top: 0;
margin-left: 1rem;
width: 15rem;
font-size: 0.85em;
background: var(--sidenote-bg, #f9f9f9);
padding: 0.5rem;
border-left: 3px solid var(--sidenote-border, #ccc);
color: var(--sidenote-text, #333);
z-index: 10;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
}
.sidenote-toggle:checked ~ .sidenote {
display: inline-block;
}
/* 深色模式适配(适用于 prefers-color-scheme 或 Blowfish 的 .dark class) */
@media (prefers-color-scheme: dark) {
.sidenote {
--sidenote-bg: #1e1e1e;
--sidenote-text: #ddd;
--sidenote-border: #444;
}
.sidenote-label {
--sidenote-label-color: #aaa;
}
}
.dark .sidenote {
--sidenote-bg: #1e1e1e;
--sidenote-text: #ddd;
--sidenote-border: #444;
}
.dark .sidenote-label {
--sidenote-label-color: #aaa;
}
/* 手机端适配 */
@media (max-width: 768px) {
.sidenote {
position: relative;
left: auto;
top: auto;
margin-left: 0;
margin-top: 0.3rem;
width: 100%;
border-left: none;
border-top: 2px solid var(--sidenote-border, #ccc);
background: var(--sidenote-bg, #f2f2f2);
}
}