Skip to main content
DeepakNess DeepakNess

Moving from WordPress to 11ty

I have been using WordPress for a long time for my personal website. But these days, I love writing and taking notes inside VS Code so thought to move my personal blog from WordPress to the 11ty SSG.

The issue was, I had over 130 blog posts and manually converting them to Markdown and moving them to 11ty wasn't possible. But... I finally got it working, and you're reading this blog post on my new 11ty blog.

So... let me explain the entire process from the start. Here's a video that I recorded:

You can continue below, if you prefer to read instead.

1. Started with a 11ty starter

Initially, I thought of building the site from the scratch, but later I decided to use the official 11ty starter. I chose the starter theme and didn't build from scratch because it has a lot of featured already built-in, such as:

If I started from the scratch then it would have taken a lot more time than it did to customize the starter theme as per my requirements.

2. Customizing the starter theme

Once the theme was finalized, I started customizing and tweaking a few things in the theme. I didn't change a lot of things, though. In fact, let me explain what customizations I did.

Added my photo in the header

Adding the photo in the header was the most tricky part, because the pre-installed 11ty Image plugin was automatically optimizing the image and changing the image URL, and the image was broken in production. I spent a lot of time debugging the issue, and finally settled with adding eleventy:ignore in the HTML, as shown below:

<a href="/" class="home-link">
    <img eleventy:ignore src="/img/deepakness.jpg" alt="DeepakNess" />
    DeepakNess
</a>

I could also have removed the image transform plugin, but I didn't want to as it's an amazing tool to optimize images.

Adding the footer navigation was easy, but I had to try multiple different icon community plugins. I finally settled with eleventy-plugin-phosphoricons as it had all social media and multiple different icons available. Below is how it looks in action:

Phosphor icons in use

Actually, I used Lucide icons as well, but didn't have the new icon for X (Twitter) so I had to look for an alternative, and Phosphor Icons was the best.

Other minor customizations

Apart from the above major ones, I did some other minor customizations, like:

3. Moving blog posts from WordPress

I was all set to use the wordpress-export-to-markdown tool for converting all my posts to Markdown. But then I discovered Zach's new video where he demonstrates moving 100s of posts from WordPress to Markdown by using the command line tool @11ty/import.

npx @11ty/import wordpress https://deepakness.com/ --output=content/blog --assetrefs=colocate

I just ran the above terminal command, and all my posts were copied as Markdowns into multiple sub-folders with associated images co-located in those folders. Amazing!

But there was an issue!

Messy frontmatter of imported WordPress posts

The frontmatters of the imported WordPress posts were messy. I didn't want authors and metadata and only wanted title, date, and tags from it. I asked ChatGPT for a Python script that can clean the frontmatter of all .md files across multiple sub-folders, and I got it working on the very first try. Below is the Python code, if you're interested:

import os
import yaml
import frontmatter
from pathlib import Path
from datetime import datetime

def clean_frontmatter(file_path):
    print(f"\nReading file: {file_path}")
    
    # Read the markdown file
    try:
        post = frontmatter.load(file_path)
        print("Current frontmatter:", dict(post.metadata))
        
        # Extract only needed frontmatter fields
        cleaned_metadata = {
            'title': post.get('title', ''),
            'date': post.get('date', ''),
            'tags': post.get('tags', [])
        }
        
        print("Cleaned frontmatter:", cleaned_metadata)
        
        # Create new post with cleaned frontmatter
        new_post = frontmatter.Post(
            content=post.content,
            **cleaned_metadata
        )
        
        # Write back to file
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(frontmatter.dumps(new_post))
            
        print(f"✅ Successfully cleaned frontmatter")
        
    except Exception as e:
        print(f"❌ Error processing file: {str(e)}")

def process_markdown_files(directory):
    # Convert to absolute path if it's relative
    base_dir = Path(directory).resolve()
    print(f"Looking for .md files in: {base_dir}")
    
    # Check if directory exists
    if not base_dir.exists():
        print(f"❌ Directory not found: {base_dir}")
        return
    
    # Count files
    md_files = list(base_dir.rglob('*.md'))
    print(f"Found {len(md_files)} markdown files")
    
    if len(md_files) == 0:
        print("No markdown files found! Please check the directory path.")
        return
    
    # Process each file
    for file_path in md_files:
        clean_frontmatter(file_path)

if __name__ == "__main__":
    # Use absolute path to content/blog/blog
    current_dir = Path(__file__).parent
    blog_directory = current_dir / "content" / "blog" / "blog"
    
    print("Starting frontmatter cleanup...")
    print(f"Script location: {current_dir}")
    print(f"Target directory: {blog_directory}")
    
    process_markdown_files(blog_directory)

Also, the import tool had created an additional blog folder inside the content/blog folder. But that wasn't an issue, I manually copied all sub-folders to the correct folder.

I was surprised that I didn't get even a single error during the cleaning process. AI is getting a lot better at coding these days.

4. Making the website live

I pushed the code to a private GitHub repo and deployed the website on Netlify. The process was straightforward because I already know the process, but the first build time took around 4 minutes which was unusually high. Upon investigating, I found that the Eleventy image plugin transforms all images into multiple different formats during the build process and that takes a lot of time. The more number of images you have, it will take more time.

But the problem was, all images were getting transformed on every build which was unnecessary. I came across this repo while researching, but adding the suggested code in the netlify.toml didn't help me.

Now, the build process is taking approx. 2:50 mins. but it should take even lesser time. When I remove the .avif version and only keep the .webp version, the build process only takes around 30 seconds. It could work, but it'd be better if AVIF version of images are also present.

So... I am still figuring out how to properly set up the cache plugin, and will update this section as I discover more.

I also had to add some 301 redirects as I deleted a few pages that I didn't want anymore. For this, I used the Netlify _redirects file approach.

5. Next steps

While I am pretty much satisfied with how the entire set up is, I still have a few things planned to do. Such as:

That's it, for now!