Complete Guide to Migrating from Hexo to Hugo
Introduction Hexo and Hugo are both excellent static site generators, but due to performance considerations or personal preference, you might want to migrate from Hexo to Hugo. This article will document the complete migration process in detail, including configuration, article conversion, multilingual support, and more. Preparation Before starting the migration, you need to install Hugo and understand the basic site creation process. Here are the official documentation links, which are recommended to follow: 1. Install Hugo Hugo provides multiple installation methods, including pre-compiled binaries and package managers. Based on your operating system, please refer to the official installation documentation: Hugo Official Installation Guide 2. Create a Hugo Site After installing Hugo, you can create a new Hugo site using the following command: hugo new site your-site-name cd your-site-name For more detailed information about creating a site, please refer to: Hugo Quick Start Guide 3. Install a Theme Hugo has a rich selection of themes to choose from. You can browse and select a suitable theme on the Hugo Themes page. The basic steps to install a theme are as follows: git init git submodule add https://github.com/theme-author/theme-name.git themes/theme-name Then set the theme in your configuration file: echo "theme = 'theme-name'" >> hugo.toml Site Configuration Create a hugo.toml configuration file: baseURL = 'https://www.mfun.ink/' defaultContentLanguage = 'zh-cn' title = "Mengboy's Blog" theme = 'PaperMod' hasCJKLanguage = true # Google Analytics configuration GoogleAnalytics = "G-xxxxxxx" # Please replace with your Google Analytics ID # Add permalink configuration to make link format compatible with Hexo [permalinks] post = '/:year/:month/:day/:contentbasename/' # PaperMod theme parameters [params] # Set environment to production, which is important for Google Analytics and AdSense loading env = "production" # Author information author = "mengboy" # Main sections mainSections = ['post'] defaultTheme = "auto" ShowReadingTime = true ShowShareButtons = true ShowPostNavLinks = true ShowBreadCrumbs = true ShowCodeCopyButtons = true ShowRssButtonInSectionTermList = true disableSpecial1stPost = false disableScrollToTop = false hideMeta = false hideFooter = false # Website favicon settings [params.assets] favicon = "/images/site/favicon.png" favicon16x16 = "/images/site/favicon.png" favicon32x32 = "/images/site/favicon.png" apple_touch_icon = "/images/site/favicon.png" # Google AdSense configuration, requires custom extension or theme support googleAdsense = "ca-pub-xxxxxxxxxx" # Please replace with your Google AdSense publisher ID googleAdsenseSlot = "xxxxxxxx" # Please replace with your Google AdSense ad unit ID # Sidebar configuration [params.profileMode] enabled = false title = "mengboy" subtitle = "Recording Learning and Life" imageUrl = "images/site/favicon.png" imageWidth = 120 imageHeight = 120 # Social icons [[params.socialIcons]] name = "github" url = "https://github.com/mengboy" # Search settings [params.fuseOpts] isCaseSensitive = false shouldSort = true location = 0 distance = 1000 threshold = 0.4 minMatchCharLength = 0 keys = ["title", "permalink", "summary", "content"] # Allow HTML [markup.goldmark.renderer] unsafe = true # Ignore HTML warnings ignoreWarnings = ['raw-html'] ignoreLogs = ['warning-goldmark-raw-html'] [languages] [languages.zh-cn] contentDir = 'content' languageName = '简体中文' weight = 10 title = "Mengboy's Blog" # Chinese homepage information [languages.zh-cn.params] [languages.zh-cn.params.homeInfoParams] Title = "记录学习与生活点滴" # Chinese menu [languages.zh-cn.menu] [[languages.zh-cn.menu.main]] identifier = "home" name = "首页" url = "/" weight = 10 [[languages.zh-cn.menu.main]] identifier = "categories" name = "分类" url = "/categories/" weight = 20 [[languages.zh-cn.menu.main]] identifier = "tags" name = "标签" url = "/tags/" weight = 30 [[languages.zh-cn.menu.main]] identifier = "archives" name = "归档" url = "/archives/" weight = 40 [[languages.zh-cn.menu.main]] identifier = "about" name = "关于我" url = "/about/" weight = 50 [languages.en] contentDir = 'content/english' languageName = 'English' weight = 20 title = "Mengboy's Blog" # English homepage information [languages.en.params] [languages.en.params.homeInfoParams] Title = "Recording Learning and Life" # English menu [languages.en.menu] [[languages.en.menu.main]] identifier = "home" name = "Home" url = "/" weight = 10 [[languages.en.menu.main]] identifier = "categories" name = "Categories" url = "/en/categories/" weight = 20 [[languages.en.menu.main]] identifier = "tags" name = "Tags" url = "/en/tags/" weight = 30 [[languages.en.menu.main]] identifier = "archives" name = "Archives" url = "/en/archives/" weight = 40 [[languages.en.menu.main]] identifier = "about" name = "About Me" url = "/en/about/" weight = 50 Create Content Directories mkdir -p content/post mkdir -p content/english/post # Directory for English articles Create Basic Pages About Page mkdir -p content/about Create Chinese about page content/about/index.md: --- title: "关于我" date: 2023-06-01T12:00:00+08:00 --- 这是关于我的页面内容... Create English about page content/english/about/index.md: --- title: "About Me" date: 2023-06-01T12:00:00+08:00 --- This is about me page... You can create other basic pages using the about page as a reference. Write the Conversion Script Create a Python script to convert Hexo articles to Hugo format: #!/usr/bin/env python3 # Convert Hexo Markdown files to Hugo format import os import re import datetime import shutil from pathlib import Path # Setup paths hexo_posts_dir = "hexo/source/_posts" hugo_posts_dir = "hugo/content/post" hexo_img_dir = "hexo/source/images" hugo_static_dir = "hugo/static/images" # Ensure target directories exist os.makedirs(hugo_posts_dir, exist_ok=True) os.makedirs(hugo_static_dir, exist_ok=True) # Copy image folder def copy_images(): if os.path.exists(hexo_img_dir): print(f"Copying image files from {hexo_img_dir} to {hugo_static_dir}") # Copy folder contents for item in os.listdir(hexo_img_dir): s = os.path.join(hexo_img_dir, item) d = os.path.join(hugo_static_dir, item) if os.path.isdir(s): shutil.copytree(s, d, dirs_exist_ok=True) else: shutil.copy2(s, d) # Process YAML front-matter def convert_front_matter(content): # Extract front-matter match = re.match(r'^---\s+(.*?)\s+---\s*', content, re.DOTALL) if not match: return content front_matter = match.group(1) rest_content = content[match.end():] # Convert tags and categories new_front_matter = [] categories = [] tags = [] date = None for line in front_matter.strip().split('\n'): line = line.strip() # Process date if line.startswith('date:'): date_str = line[5:].strip() try: date = datetime.datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') new_front_matter.append(f'date: {date.strftime("%Y-%m-%dT%H:%M:%S+08:00")}') # Add lastmod new_front_matter.append(f'lastmod: {date.strftime("%Y-%m-%dT%H:%M:%S+08:00")}') except ValueError: new_front_matter.append(line) # Process tags elif line.startswith('tags:'): tags_str = line[5:].strip() if tags_str.startswith('[') and tags_str.endswith(']'): # Format is tags: [tag1, tag2] tags = [tag.strip(' "\'') for tag in tags_str[1:-1].split(',')] else: # Might be multi-line format continue # Process categories elif line.startswith('categories:'): cats_str = line[11:].strip() if cats_str.startswith('[') and cats_str.endswith(']'): # Format is categories: [cat1, cat2] categories = [cat.strip(' "\'') for cat in cats_str[1:-1].split(',')] else: # Might be multi-line format continue # Copy other lines elif not (line.startswith('- ') and ('tags:' in front_matter or 'categories:' in front_matter)): new_front_matter.append(line) # Add categories and tags if categories: new_front_matter.append(f'categories: {categories}') if tags: new_front_matter.append(f'tags: {tags}') # Add slug if 'slug:' not in front_matter: filename = os.path.splitext(os.path.basename(content))[0] new_front_matter.append(f'slug: "{filename}"') # Combine new front-matter new_content = '---\n' + '\n'.join(new_front_matter) + '\n---\n\n' + rest_content return new_content # Convert Markdown content def convert_markdown_content(content): # Replace image links content = re.sub(r'!\[(.*?)\]\((.*?)/images/(.*?)\)', r'', content) # Handle the "more" marker content = re.sub(r'<!--\s*more\s*-->', ' ', content) return content # Main conversion function def convert_posts(): print(f"Starting to convert posts from {hexo_posts_dir} to {hugo_posts_dir}") files = os.listdir(hexo_posts_dir) for file in files: if not file.endswith('.md'): continue source_file = os.path.join(hexo_posts_dir, file) target_file = os.path.join(hugo_posts_dir, file) print(f"Converting: {source_file} -> {target_file}") # Read source file with open(source_file, 'r', encoding='utf-8') as f: content = f.read() # Convert content content = convert_front_matter(content) content = convert_markdown_content(content) # Write to target file with open(target_file, 'w', encoding='utf-8') as f: f.write(content) # Detect language and create multilingual files def handle_multilingual(): print("Processing multilingual articles...") for file in os.listdir(hugo_posts_dir): if not file.endswith('.md'): continue filepath = os.path.join(hugo_posts_dir, file) # Read file content with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # Check if there's a language marker lang_match = re.search(r'language:\s*[\'"]?([a-z\-]+)[\'"]?', content) if lang_match: lang = lang_match.group(1) if lang.startswith('en'): # Create English version en_file = os.path.splitext(file)[0] + '.en.md' en_filepath = os.path.join(hugo_posts_dir, en_file) # Remove language marker and write to new file content = re.sub(r'language:\s*[\'"]?[a-z\-]+[\'"]?\n', '', content) with open(en_filepath, 'w', encoding='utf-8') as f: f.write(content) # Delete original file os.remove(filepath) print(f" Created English article: {en_filepath}") if __name__ == "__main__": copy_images() convert_posts() handle_multilingual() print("Conversion complete!") Run the Conversion Script cd ~/dev/book python3 convert_hexo_to_hugo.py Create a Start Script Create start.sh: ...