How to capture/generate revisions of page?

Preface

I am completely new to statically site generation (SSG). I have looked through the documentation, but I am not able to completely ascertain whether or not this is possible within the Hugo ecosystem or within scope of SSG.

Problem

As a writer, I may edit an existing page/post/piece for either clarity, fix typos, provide correction, or even complete redaction (in rare cases). For each of these actions (1...n), I want to capture each revision and make them available to the reader.

Case 0: Writer wants to make N edits to a published page

Given

  • Project initialized with hugo new project blog
  • Project revisioned with git
  • Post created with hugo new content content/posts/expose-on-corruption.md at 2026-07-19T12:00:00-00:00
  • Initial post committed to git repository by author at 2026-07-19T12:00:00-00:00 with SHA-1 of aaaa...
  • Post is not marked as a draft

When

  • Author makes an edit at 2026-07-19T13:01:00-00:00, commits changes, SHA-1 of bbbb...
    • edit: title changed to Expose on Corrupt Government Branch
  • Author makes an edit at 2026-07-19T13:30:00-00:00, commits changes, SHA-1 of cccc...
    • edit: add key figures, events, update timeline
  • Author makes an edit at 2026-07-19T15:00:00-00:00, commits changes, SHA-1 of dddd...
    • edit: update sources

Then

  • hugo should generate 4 pages of the same post and provide stable URLs for each revision
    • {baseUrl}/2026/07/19/120000Z/aaaa.../expose-on-corruption/
    • {baseUrl}/2026/07/19/120000Z/bbbb.../expose-on-corruption/
    • {baseUrl}/2026/07/19/120000Z/cccc.../expose-on-corruption/
    • {baseUrl}/2026/07/19/120000Z/dddd.../expose-on-corruption/
  • hugo should provide option for authors to include a revision section in document so reader can navigate between them

Hi

I had the similar need. Here is the method I use :

  • I commit with a message
  • in my CI i call a schell script :
    • make a directory data/commits
    • find all the markdown files
    • for each create a json file with all the commits of this file
  • in my layouts, I read the file in data/commits corresponding to the markdown file and I display the information.

Hera are the codes as is.

Script

mkdir -p data/commits

find content -name "*.md" | while read line; do
    contentline=${line#"content/"};
    filename=$(echo -n $contentline | md5sum | cut -f 1 -d " ");
    log=$(git log --pretty=format:'{%n  "commit": "%H",%n  "author": "%aN",%n  "commit_date": "%ci",%n  "message": "%s"%n},' "$line" | perl -pe 'BEGIN{print "["}; END{print "]\n"}' | perl -pe 's/},]/}]/');
     echo $log > "data/commits/$filename.json";
done

Here the part of the template

{{ $currentPage := . }}
{{ $jsonFile := "" }}
{{ with $currentPage.File }}
    {{ $currentPagemd5 := md5 $currentPage.File.Path }}
    {{ $currentPagemd5ext := (print $currentPagemd5 ".json")}}
    {{ $files := readDir "data/commits/" }}
    {{if gt (len $files ) 0}}
        <div class="bord-bas"></div>
        <div>
        <details>
            <summary>Changements de la page</summary>
            <div>
            {{ range $files }}
                {{ if eq .Name $currentPagemd5ext }}
                    {{ $jsonFile := $currentPagemd5 }}
                    {{ $data := index $.Site.Data.commits $jsonFile }}
                    <ul>
                    {{ range $data }}
                        <li>
                            {{ dateFormat "02/01/2006" .commit_date }} : {{ trim .message "\n" }} ({{ .author }})
                        </li>
                    {{ end }}
                    </ul>
                {{ end }}
            {{ end }}
            </div>
        </details>
        </div>
    {{ end }}
{{ end }}

Hope this helps

You can do this with a content adapter that queries the GitHub API. Two API approaches:

  1. Get a list of the last N commits by querying the GitHub API, then query the GitHub API for each commit to get the content of the given file path.
  2. Get a list of the last N commits using the GitInfo.Ancestors method, then query the GitHub API for each commit to get the content of the given file path.

The result of the file query looks like this, where the content is base64 encoded.

I’m familiar with the GitHub API; presumably you can do the same with GitLab, Bitbucket, etc. Regardless of approach, the number of API calls may exceed the rate limit in the absence of a personal access token. Note that, in its default configuration, Hugo indefinitely caches the result of each API call, reducing the number of calls during subsequent builds.

Having said that…

Also note that, given two commits and a file path, you can show a GitHub side-by-side comparison with something like this:

https://github.com/<owner>/<repo>/compare/A..B#diff-C

Where A is the first commit hash, B is the second commit hash, and C is the sha256 hash of the file path.
For example:

https://github.com/gohugoio/hugoDocs/compare/c057d865f32a73b17012fed98e95e490c4b2441b..9dfab8fc8eea4367dbe257193d289fa5a38b4553#diff-e211a3cda8585b58ab096118583518b7aa5778c2037839bf35c7c56876fac4b8

You can also use abbreviated commit hashes:
https://github.com/gohugoio/hugoDocs/compare/c057d86..9dfab8f#diff-e211a3cda8585b58ab096118583518b7aa5778c2037839bf35c7c56876fac4b8


If you want to avoid API calls and don’t want to provide links to GitHub UI’s, use @Welsh’s approach (a pre-build script) in conjunction with a content adapter.

Hi @jmooring

I don’t use content adapter, because I didn’t know them when I code :wink:

But it’s a feature I want to implement.

I just want to explain my need which leads me not ot use API. I use the version info in order to display the evolution of a post through the commit messages, in the footer of the post.

As I use this snippet in many projects (private and professional), I just wanted a solution which is not dependant of the repositiory (Github, Gitlab…).