Adding a “Moments” or social feed page to a static blog is a long-discussed topic. Previous solutions were either cumbersome to set up or inconvenient to use. To solve this problem definitively, I recently experimented with using GitHub Issues – requiring just two simple steps to create a dynamically updatable feed page.
Core Concept
Use GitHub Issues as a publishing platform for feed content, then reference those issues on a new static blog page. GitHub Actions monitors issue changes to trigger blog updates automatically.
Advantages:
- Simple setup and maintenance – Built on GitHub. Any issues can be resolved with AI assistance.
- Mobile-friendly – GitHub’s mobile app allows pinning issues to the home screen for quick posting, similar to WeChat Moments.
- Secure and scalable – Fully controlled content with unlimited capacity and easy migration.
Disadvantages: None identified yet.
Implementation Guide
Step 1: Create a Feed Page in Your Blog Template
For Hugo, create a file like moments.html
in the layouts
folder, using resources.GetRemote
to fetch content from a GitHub Issue (e.g., https://api.github.com/repos/microsoft/vscode/issues/519/comments
).
Adapt a static page template with your theme settings.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| {{ define "main" }}
<article class="main-article">
{{ $url := "https://api.github.com/repos/microsoft/vscode/issues/519/comments" }}
{{ $opts := dict
"headers" (dict "User-Agent" "Hugo Static Site Generator")
}}
{{ with resources.GetRemote $url $opts }}
{{ if and .Content (ne .Content "") }}
{{ $comments := .Content | transform.Unmarshal (dict "format" "json") }}
<div class="moments-feed">
{{ range $comments }}
<article class="moment-article">
<header class="article-header">
<div class="article-details">
<footer class="article-time">
<div>
{{ .created_at | time.Format "2006-01-02 15:04" }}</time>
</div>
</footer>
</div>
</header>
<section class="article-content">
<div class="moment-content">
{{ .body | markdownify }}
</div>
</section>
</article>
{{ else }}
<p>No updates yet</p>
{{ end }}
</div>
{{ else }}
<p class="no-content">Empty content</p>
{{ end }}
{{ else }}
<p class="error">⚠️ Failed to load comments</p>
{{ end }}
</article>
|
Step 2: Set Up an Update Trigger
Note: Private repositories block external image access in issues. Use a public repository.
Option A: Blog and Feed in the Same Repo
Add this to your deployment workflow:
1
2
3
| on:
issue_comment:
types: [created, edited]
|
Option B: Separate Repositories
- Create a public repository dedicated to feed content.
- Add an
issue.yml
file under .github/workflows
as a trigger. - Generate a
Personal access token
with repo
permissions in GitHub settings, then add it to the feed repo’s Secrets
as PAT
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| name: Trigger Empty Commit on Issue Update
on:
issue_comment:
types: [created, edited]
jobs:
trigger-empty-commit:
runs-on: ubuntu-latest
steps:
- name: Check if the comment is on the target issue
id: check-issue
run: |
if [ "${{ github.event.issue.number }}" != "1" ]; then
echo "should_trigger=false" >> $GITHUB_OUTPUT
else
echo "should_trigger=true" >> $GITHUB_OUTPUT
fi
- name: Trigger empty commit in user.github.io
if: steps.check-issue.outputs.should_trigger == 'true'
uses: actions/github-script@v6
env:
PAT: ${{ secrets.PAT }}
with:
script: |
const { execSync } = require('child_process');
const repo = 'user/user.github.io'; // Replace with your Hugo repo
const token = process.env.PAT;
try {
const repoUrl = `https://x-access-token:${token}@github.com/${repo}.git`;
console.log(`Cloning ${repo}...`);
execSync(`git clone ${repoUrl}`);
process.chdir('user.github.io'); // Replace with your Hugo repo
execSync('git config user.name "github-actions[bot]"');
execSync('git config user.email "4189+github-actions[bot]@users.noreply.github.com"');
execSync('git commit --allow-empty -m "Trigger update from moments/issues/1"');
execSync(`git push ${repoUrl} master`);
console.log('✅ Empty commit pushed successfully!');
} catch (error) {
console.error('❌ Error:', error.message);
process.exit(1);
}
|