I’m currently thinking about migrating my Jekyll blog with ~500 posts to Hugo and before I start that adventure I’d like to confirm I’m able to do something first. I searched around and didn’t find a definitive answer.
With my current site in Jekyll, images are adjacent to js and css when it comes to where they are located on disk. For example I have an image located at:
_assets/images/hello.jpg
Which I reference in Jekyll’s template with {% image hello.jpg class:media-object %}.
This allows me to then cache these images forever with nginx because if the content changes then a new digested file name gets created so it gets automatically invalidated.
I’d like to do the same thing with Hugo but it would be very important that:
The file name is the same that was created with Jekyll, for example it would be hello-$md5.jpg. Using a query string param wouldn’t work for my use case.
The image must be located alongside other images in their own images directory, not nested in content/blog/my-post/hello.jpg, if that ends up being in static/images instead of assets/images that’s fine, as long as they all exist in 1 place.
With the resource option, the image never shows up but assets/images/foobar.jpg exists. Is there anything else I need to configure to make that work? That call as is produces an empty string
With the static option, is there a way to make the digest a part of the file name? Using a query string won’t work for my use case
I know I can likely extract the file extension from $img but the file won’t exist on disk
The resource code should work. Resources have to be published explicitely by invoking a special method in the example code .Permalink. See Publish | Hugo
Think the static option won’t work as you need the md5 in the filename. Static files are copied verbatim
Oh, I took their example exactly how it was and only edited the file name. It never renders the tag, just an empty string.
If it helps I’m running hugo v0.128.2-de36c1a95d28595d8243fd8b891665b069ed0850 linux/amd64 BuildDate=2024-07-04T08:13:25Z VendorInfo=gohugoio.
When I do {{ $styles := resources.Get "/css/app.css" }} and then reference its $styles.RelPermalink, this works. It seems to only not pick up images. I also adjusted the images path to add a leading / like css has and it also doesn’t work.
Ok, it’s working now. I forgot I modified my assetDir to be the output of where esbuild copies files. Now it’s being picked up and fingerprinted.
However, the files are being created with a filename.fingerprint.extension as seen by this path http://localhost:1313/images/hello.4a90c0d74632b4933ab2c5f62e94be2c.jpg.
Given I have thousands of images with filename-fingerprint.extension is there anything I can modifiy at the Hugo level to use - instead of . to separate the file name and extension? This way none of the hot links to my images on the internet will break. I didn’t see this as a configurable option for fingerprint.
after fingerprinting the resource has a property .Data.Integrity where you have all the other needed information (see the fingerprint Usage link above)
build the new filename
copy and publish
regarding feature request: you may give it a try - there’s a category feature here where you can post your request and see what happens.
Thanks. I was able to get this working in the most basic sense, basically copy / pasting your solution and using hard coded values.
It does indeed fingerprint them with the custom file format and produce the file on disk.
Thanks a lot.
The next steps for me will be to make it more dynamic such as turning it into a partial to support a custom file name and also attempt to extract the extension from the file name since not all images are pngs.
Speaking of which, Hugo makes it easy to get the extension with {{ $ext := path.Ext .Name }} which returns .jpg (with the dot) but how would you modify {{ $basePath := replaceRE (.*).jpg$ "$1" .Name }} to use $ext? The docs don’t have any examples of this and Google is coming up empty.
The print statement was easy enough to modify {{ $targetFullPath := printf "%s-%s%s" $basePath $sha256 $ext }}.
Here’s an end to end solution. A lot of this was done by @irkode in previous replies, I just abstracted it out into a partial and appended in some of my existing image partial logic.
Keep in mind I just started using Hugo so I don’t really know if this is the best way to do things but:
Which can then be used in your content markdown file like this:
{{< image src="hello.jpg" >}}
Here is the new custom image.html partial:
{{ $fingerprintDelimiter := "-" }}
{{ $src := print "/images/" .src }}
{{ $height := .height }}
{{ $width := .width }}
{{ $class := .class }}
{{ with resources.Get $src }}
<!-- Calculate SHA -->
{{ $sha256 := sha256 .Content }}
<!-- Get the file extension -->
{{ $ext := path.Ext .Name }}
<!-- Remove extension by removing everything after the last dot -->
{{ $pathWithoutExt := replaceRE `\.[^.]+$` "" .Name }}
<!-- Remove leading / -->
{{ $pathWithoutExt = strings.TrimLeft "/" $pathWithoutExt }}
<!-- Generate filename -->
{{ $fullPathWithFingerprint := printf "%s%s%s%s" $pathWithoutExt $fingerprintDelimiter $sha256 $ext }}
<!-- Copy to destination (virtually) -->
{{ with resources.Copy $fullPathWithFingerprint . }}
<!-- Publish to destination by creating the link -->
<img src="{{ .Permalink }}" {{ with $class }} class="{{ $class }}"{{ end }} height="{{ $width | default .Height }}" width="{{ $width | default .Width }}" alt="{{ index ((split .Name "/") | last 1) 0 }}" >
<!-- For debugging purposes so you can see the file name, remove this as needed -->
<p>{{ .Name }} => {{ $fullPathWithFingerprint }}</p>
{{ end }}
{{ end }}
The shortcode and how you call it didn’t change, but the above solution produces file names with basefile-fingerprint.ext instead of the original basefile.fingerprint.ext.