On this post, I will be demonstrating quick and easy way of creating static websites from your obsidian notes. This is possible with Hugo which is an open-source framework for static site generation.
Hugo basically turns markdown files to html, so you can still follow along even if you don’t use obsidian and skip the obsidian related parts.
Since I am on arch, some commands may not work for you. If that is the case refer the original documentation.
Prerequisites for Hugo:
Install Hugo:
sudo pacman -S hugoCreate new site using Hugo (this creates the site under a folder):
hugo new site blogInitialize git repo:
cd blog/
git initDownload a theme (I am using Blowfish, this is optional but recommended:
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfishFollow the theme documentation and make necessary changes for your website.
To test the site:
hugo server --disableFastRender --noHTTPCacheSince we are using Obsidian, we need to copy notes from the obsidian folder to the website folder.
I placed notes that I will use for my website under a new folder called posts in the obsidian vault.
We can achieve this with the following rsync command:
rsync -av --delete "src-path" "dest-path"If there is an image on the note, it will not be parsed but only the wikilink for the image will be shown. We need to copy the image to the website folder.
First we need to create an images folder under the static folder.
mkdir static/imagesThen we can run this python script and copy the images to our newly created images folder. Replace paths for your directories and make sure that they are correct otherwise the script won’t work. Create this script as images.py.
import os
import re
import shutil
# Paths
posts_dir = "your-post-dir"
attachments_dir = "your-image-dir"
static_images_dir = "your-static-website-image-dir"
base_url_path = "" # Empty for root-hosted site
# Step 1: Process each markdown file in the posts directory
for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
filepath = os.path.join(posts_dir, filename)
with open(filepath, "r") as file:
content = file.read()
# Step 2: Find all image links in Obsidian format 
images = re.findall(r'!\[\[([^]]*\.png)\]\]', content)
# Step 3: Replace image links and ensure URLs are correctly formatted
for image in images:
# Prepare the Markdown-compatible link with %20 replacing spaces
markdown_image = f"})"
content = content.replace(f"![[{image}]]", markdown_image)
# Step 4: Copy the image to the Hugo static/images directory if it exists
image_source = os.path.join(attachments_dir, image)
if os.path.exists(image_source):
shutil.copy(image_source, static_images_dir)
# Step 5: Write the updated content back to the markdown file
with open(filepath, "w") as file:
file.write(content)
print("Markdown files processed and images copied successfully.")Run rsync command before running the image script.
Run the script:
python3 images.pyYou should see the images being copied from your obsidian vault to the static/images folder.
We need to create 2 more files. These will be used for creating custom deployment configuration on Cloudflare Workers.
Create a wrangler.toml file at the root of your website folder, then paste the following content:
name = "blog" # use the same name from the workers page
compatibility_date = "2026-03-01" # change this to current date
[build]
command = "./build.sh"
[assets]
directory = "./public"
not_found_handling = "404-page"Create a new script called build.sh, again at the root of your website folder and paste this:
#!/usr/bin/env bash
main() {
HUGO_VERSION=0.153.3 # change this to your hugo version
export TZ="Europe/Istanbul" # change this to your timezone
# Install Hugo
echo "Installing Hugo v${HUGO_VERSION}..."
curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
tar -xf "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
cp hugo /opt/buildhome
rm LICENSE README.md hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
# Verify installed version
echo "Verifgying Hugo installation..."
echo Go: "$(go version)"
echo Hugo: "$(hugo version)"
echo Node: "$(node --version)"
# Clone themes repository
echo "Cloning themes repository - archie..."
git submodule update --init --recursive
git config core.quotepath false
# Build the site
echo "Building the site..."
hugo --gc --minify
}
set -euo pipefail
main "$@"Make this script executable:
chmod +x build.shBefore using Cloudflare workers make sure that you have the latest version of your site available in your remote repo.
Now that we are done with copying our notes from obsidian to website folder, we can publish it under our domain using Cloudflare Workers Platform.
After logging in to Cloudflare, go to Build -> Compute & AI -> Workers & Pages. Click New Deployment on top-right and select github and finish authorization. Select the website repo and click deploy. This may take some time or the build may fail. If there are any errors, look at the logs and press re-build after fixing the error and pushing the fixed version to the remote repo.
If the build is complete then congrats your site is ready to be used. As the last step, you can configure custom domain to point the site to your own domain name. Go to Settings -> Add -> Custom Domain and enter your domain there.

Now you can visit your domain and see your static website.
As a bonus this is the script that combines the process of copying notes, images from the obsidian vault and pushing changes to the remote repo. Create this script at the root of your website folder, you can name it whatever you want, make it executable and run it.
Don’t forget to change directory paths and the repo address.
#!/bin/bash
set -euo pipefail
# Change to the script's directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Set variables for Obsidian to Hugo copy
sourcePath="src-path"
destinationPath="dest-path"
# Set GitHub Repo
myrepo="your-remote-repo-address"
# Check for required commands
for cmd in git rsync python3 hugo; do
if ! command -v $cmd &> /dev/null; then
echo "$cmd is not installed or not in PATH."
exit 1
fi
done
# Step 1: Check if Git is initialized, and initialize if necessary
if [ ! -d ".git" ]; then
echo "Initializing Git repository..."
git init
git remote add origin $myrepo
else
echo "Git repository already initialized."
if ! git remote | grep -q 'origin'; then
echo "Adding remote origin..."
git remote add origin $myrepo
fi
fi
# Step 2: Sync posts from Obsidian to Hugo content folder using rsync
echo "Syncing posts from Obsidian..."
if [ ! -d "$sourcePath" ]; then
echo "Source path does not exist: $sourcePath"
exit 1
fi
if [ ! -d "$destinationPath" ]; then
echo "Destination path does not exist: $destinationPath"
exit 1
fi
rsync -av --delete "$sourcePath" "$destinationPath"
# Step 3: Process Markdown files with Python script to handle image links
echo "Processing image links in Markdown files..."
if [ ! -f "images.py" ]; then
echo "Python script images.py not found."
exit 1
fi
if ! python3 images.py; then
echo "Failed to process image links."
exit 1
fi
# Step 4: Add changes to Git
echo "Staging changes for Git..."
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to stage."
else
git add .
fi
# Step 5: Commit changes with a dynamic message
commit_message="New Blog Post on $(date +'%Y-%m-%d %H:%M:%S')"
if git diff --cached --quiet; then
echo "No changes to commit."
else
echo "Committing changes..."
git commit -m "$commit_message"
fi
# Step 6: Push all changes to the main branch
echo "Deploying to GitHub Main..."
if ! git push origin main; then
echo "Failed to push to main branch."
exit 1
fi
echo "All done! Site synced, processed, committed, built, and deployed."References:
