Loading Local Images in Minimal Mistakes
I want to be able to load local images when the site is served locally. However, the URL format file:///Users/<username>/archive/image/20260218184820.png doesn’t work.
Why <img src="file:///Users/..."> won’t load?
Your page is being served over HTTP (e.g. http://localhost:4000). Modern browsers block pages from http(s)://… from loading local files via file://… for security (“cross-origin” / local file access). So even though the HTML is “correctly parsed”, the browser refuses to fetch it.
That means: a file:// URL will not work on a website (local dev server or deployed site). It only works when the page itself is also opened via file:// (and even then, policies vary).
Method 1: Copying files to project assets
The simplest method is to copy the images to <project-root>/assets/images/ and then refer to them using the standard syntax:
---
gallery:
- url: /assets/images/20260218184820.png
image_path: /assets/images/20260218184820.png
---

<img src="{{ site.url }}{{ site.baseurl }}/assets/images/filename.jpg" alt="">
{% include gallery %}However, this has the overhead of managing files manually, which can be tedious. The following methods give you better experience.
Method 2: Using symlinks
-
Create a symlink to the target data inside the site root. Say, if the local images reside in
~/Downloads/temp/archive/image/foo/you can create a symlink to it under<project-root>/_site/assets/archive/image/foo:cd <project-root>/_site/ mkdir -p assets/archive/image/ ln -s "~/Downloads/temp/archive/image/foo" "assets/archive/image/foo" -
Then you can refer to the images using the symlink like the following:
--- gallery: - url: /assets/archive/image/foo/20260218184820.png image_path: /assets/archive/image/foo/20260218184820.png - url: /assets/archive/image/foo/20260218184820.png image_path: /assets/archive/image/foo/20260218184820.png ---  {% include gallery %} -
The symlinks created this way are cleared every time the site is re-built. To make them persist we can write a plugin
_plugins/symlink_external_assets.rbto create them automatically according to the following front matter of a post:--- symlinks: - /assets/archive/image/foo ~/Downloads/temp/archive/image/foo - /assets/archive/video/foo ~/Downloads/temp/archive/video/foo ---symlink_external_assets.rb:require 'fileutils' require 'shellwords' Jekyll::Hooks.register :site, :post_read do |site| return if Jekyll.env == 'production' # Store the intended symlinks so we can recreate them in the _site folder later site.config['dynamic_symlinks'] ||= {} docs = site.pages + site.collections.values.flat_map(&:docs) docs.each do |doc| next unless doc.data['symlinks'].is_a?(Array) doc.data['symlinks'].each do |symlink_def| next unless symlink_def.is_a?(String) parts = Shellwords.split(symlink_def) next unless parts.size == 2 link_path_raw, target_path_raw = parts target_path = File.expand_path(target_path_raw) relative_link_path = link_path_raw.sub(%r{^/}, '') link_path = File.join(site.dest, relative_link_path) # Verify target exists unless File.exist?(target_path) Jekyll.logger.warn "Symlink Plugin:", "Skipped. Target does not exist: #{link_path} -> #{target_path}" next end # Save for the post_write phase site.config['dynamic_symlinks'][relative_link_path] = { target: target_path, link: link_path } end end end # Inject the symlinks directly into the final build folder Jekyll::Hooks.register :site, :post_write do |site| return if Jekyll.env == 'production' symlinks = site.config['dynamic_symlinks'] || {} symlinks.each do |relative_path, link| target_path = link[:target] link_path = link[:link] needs_creation = false # If the link already exists but is broken, recreate it. If it's not broken # or is a file or directory, leave it. Otherwise, create the link. if File.symlink?(link_path) if !File.exist?(link_path) File.unlink(link_path) needs_creation = true elsif File.readlink(link_path) != target_path # Link has valid target Jekyll.logger.warn "Symlink Plugin: Symlink already exists: #{link_path}" end elsif !File.exist?(link_path) needs_creation = true else # Link path is taken by a file or directory Jekyll.logger.warn "Symlink Plugin: Symlink path is taken: #{link_path}" end if needs_creation FileUtils.mkdir_p(File.dirname(link_path)) begin File.symlink(target_path, link_path) # Jekyll.logger.info "Symlink Plugin:", "Symlink created: #{link_path} -> #{target_path}" rescue => e Jekyll.logger.warn "Symlink Plugin:", "Failed to create symlink #{link_path}: #{e.message}" end end end end
Method 3: Serving the files locally with a web server
For example, you can serve the images locally with Python’s built-in web server:
-
Serve your image folder
cd ~/Downloads/temp/archive/image/foo/ python3 -m http.server 8123 --bind 127.0.0.1If you want the server to run in the background:
nohup python3 -m http.server 8123 --bind 127.0.0.1 >/tmp/imgserver.log 2>&1 &To stop it later:
lsof -iTCP:8123 -sTCP:LISTEN kill <PID> -
Use that URL in your post

You can serve from the directory ~/Downloads/temp/archive/image/foo/ but use a different mount path in the URL to refer to that directory, e.g., http://localhost:8123/igps/20260218184820.png. To achieve this we can write and run this python script:
cd ~/Downloads/temp/archive/image/foo/
python3 server.pySee also: server.py
Comments