How To Migrate From WordPress To The Eleventy Static Site Generator

About The Author

Scott Dawson lives in Trumansburg, New York. He’s a web designer and developer and enjoys writing, acting, creating art, and making music. Scott is a front-end … More about Scott ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

If you’re a designer or developer with intermediate knowledge of HTML and JavaScript, and know your way around GitHub and the command line, this tutorial is for you. We’re going to walk step-by-step through converting a WordPress site into a static site generated from Markdown.

Eleventy is a static site generator. We’re going to delve into why you’d want to use a static site generator, get into the nitty-gritty of converting a simple WordPress site to Eleventy, and talk about the pros and cons of managing content this way. Let’s go!

What Is A Static Site Generator?

I started my web development career decades ago in the mid-1990s when HTML and CSS were the only things you needed to get a website up and running. Those simple, static websites were fast and responsive. Fast forward to the present day, though, and a simple website can be pretty complicated.

In the case of WordPress, let’s think through what it takes to render a web page. WordPress server-side PHP, running on a host’s servers, does the heavy lifting of querying a MySQL database for metadata and content, chooses the right versions of images stored on a static file system, and merges it all into a theme-based template before returning it to the browser. It’s a dynamic process for every page request, though most of the web pages I’ve seen generated by WordPress aren’t really that dynamic. Most visitors, if not all, experience identical content.

Static site generators flip the model right back to that decades-old approach. Instead of assembling web pages dynamically, static site generators take content in the form of Markdown, merge it with templates, and create static web pages. This process happens outside of the request loop when users are browsing your site. All content has been pre-generated and is served lightning-fast upon each request. Web servers are quite literally doing what they advertise: serving. No database. No third-party plugins. Just pure HTML, CSS, JavaScript, and images. This simplified tech stack also equates to a smaller attack surface for hackers. There’s a little server-side infrastructure to exploit, so your site is inherently more secure.

Leading static site generators are feature-rich, too, and that can make a compelling argument for bidding adieu to the tech stacks that are hallmarks of modern content management systems.

If you’ve been in this industry for a while, you may remember Macromedia’s (pre-Adobe) Dreamweaver product. I loved the concept of library items and templates, specifically how they let me create consistency across multiple web pages. In the case of Eleventy, the concepts of templates, filters, shortcodes, and plugins are close analogs. I got started on this whole journey after reading about Smashing’s enterprise conversion to the JamStack. I also read Mathias Biilmann & Phil Hawksworth’s free book called Modern Web Development on the JAMstack and knew I was ready to roll up my sleeves and convert something of my own.

Why Not Use A Static Site Generator?

Static site generators require a bit of a learning curve. You’re not going to be able to easily pass off editorial functions to input content, and specific use cases may preclude you from using one. Most of the work I’ll show is done in Markdown and via the command line. That said, there are many options for using static site generators in conjunction with dynamic data, e-commerce, commenting, and rating systems.

You don’t have to convert your entire site over all at once, either. If you have a complicated setup, you might start small and see how you feel about static site generation before putting together a plan to solve something at an enterprise scale. You can also keep using WordPress as a best-in-class headless content management system and use an SSG to serve WordPress content.

How I Chose Eleventy As A Static Site Generator

Do a quick search for popular static site generators and you’ll find many great options to start with: Eleventy, Gatsby, Hugo, and Jekyll were leading contenders on my list. How to choose? I did what came naturally and asked some friends. Eleventy was a clear leader in my Twitter poll, but what clinched it was a comment that said “@eleven_ty feels very approachable if one doesn’t know what one is doing.” Hey, that’s me! I can unhappily get caught up in analysis paralysis. Not today… it felt good to choose Eleventy based on a poll and a comment. Since then, I’ve converted four WordPress sites to Eleventy, using GitHub to store the code and Netlify to securely serve the files. That’s exactly what we’re going to do today, so let’s roll up our sleeves and dive in!

Getting Started: Bootstrapping The Initial Site

Eleventy has a great collection of starter projects. We’ll use Dan Urbanowicz’s eleventy-netlify-boilerplate as a starting point, advertised as a “template for building a simple blog website with Eleventy and deploying it to Netlify. Includes Netlify CMS and Netlify Forms.” Click “Deploy to netlify” to get started. You’ll be prompted to connect Netlify to GitHub, name your repository (I’m calling mine smashing-eleventy-dawson), and then “Save & Deploy.”

With that done, a few things happened:

  1. The boilerplate project was added to your GitHub account.
  2. Netlify assigned a dynamic name to the project, built it, and deployed it.
  3. Netlify configured the project to use Identity (if you want to use CMS features) and Forms (a simple contact form).
Netlify’s initial deployment screen
This is Netlify’s screen that shows our initial deployment is completed. (Large preview)

As the screenshot suggests, you can procure or map a domain to the project, and also secure the site with HTTPS. The latter feature was a really compelling selling point for me since my host had been charging an exorbitant fee for SSL. On Netlify, it’s free.

I clicked Site Settings, then Change Site Name to create a more appropriate name for my site. As much as I liked jovial-goldberg-e9f7e9, elizabeth-dawson-piano is more appropriate. After all, that’s the site we’re converting! When I visit elizabeth-dawson-piano.netlify.app, I see the boilerplate content. Awesome!

Eleventy Netlify Boilerplate with no customizations
Our site has been built and is now ready for customizations. (Large preview)

Let’s download the new repository to our local machine so we can start customizing the site. My GitHub repository for this project gives me the git clone command I can use in Visual Studio Code’s terminal to copy the files:

Then we follow the remaining instructions in the boilerplate’s README file to install dependencies locally, edit the _data/metadata.json file to match the project and run the project locally.

  • npm install @11ty/eleventy
  • npm install
  • npx eleventy --serve --quiet

With that last command, Eleventy launches the local development site at localhost:8080 and starts watching for changes.

Preserving WordPress Posts, Pages, And Images

The site we’re converting from is an existing WordPress site at elizabethrdawson.wordpress.com. Although the site is simple, it’d be great to leverage as much of that existing content as possible. Nobody really likes to copy and paste that much, right? WordPress makes it easy using its export function.

WordPress Export Content screen
WordPress lets you export content and images. (Large preview)

Export Content gives me a zip file containing an XML extract of the site content. Export Media Library gives me a zip file of the site’s images. The site that I’ve chosen to use as a model for this exercise is a simple 3-page site, and it’s hosted on Wordpress.com. If you’re self-hosting, you can go to Tools > Export to get the XML extract, but depending on your host, you may need to use FTP to download the images.

If you open the XML file in your editor, it’s going to be of little use to you. We need a way to get individual posts into Markdown, which is the language we’re going to use with Eleventy. Lucky for us, there’s a package for converting WordPress posts and pages to Markdown. Clone that repository to your machine and put the XML file in the same directory. Your directory listing should look something like this:

WordPress XML directory listing
Directory listing for WordPress-export-to-markdown including WordPress' XML file. (Large preview)

If you want to extract posts from the XML, this will work out of the box. However, our sample site has three pages, so we need to make a small adjustment. On line 39 of parser.js, change “post” to “page” before continuing.

Code snippet showing changes in wordpress-export-to-markdown
Configure wordpress-export-to-markdown to export pages, not posts. (Large preview)

Make sure you do an “npm install” in the wordpress-export-to-markdown directory, then enter “node index.js” and follow the prompts.

That process created three files for me: welcome.md, about.md, and contact.md. In each, there’s front matter that describes the page’s title and date, and the Markdown of the content extracted from the XML. ‘Front matter’ may be a new term for you, and if you look at the section at the top of the sample .md files in the “pages” directory, you’ll see a section of data at the top of the file. Eleventy supports a variety of front matter to help customize your site, and title and date are just the beginning. In the sample pages, you’ll see this in the front matter section:

eleventyNavigation:
  key: Home
  order: 0

Using this syntax, you can have pages automatically added to the site’s navigation. I wanted to preserve this with my new pages, so I copied and pasted the content of the pages into the existing boilerplate .md files for home, contact, and about. Our sample site won’t have a blog for now, so I’m deleting the .md files from the “posts” directory, too. Now my local preview site looks like this, so we’re getting there!

Local website preview after customizing content
Now that we’ve customized some content, our local environment shows the current state of the site. (Large preview)

This seems like a fine time to commit and push the updates to GitHub. A few things happen when I commit updates. Upon notification from GitHub that updates were made, Netlify runs the build and updates the live site. It’s the same process that happens locally when you’re updating and saving files: Eleventy converts the Markdown files to HTML pages. In fact, if you look in your _site directory locally, you’ll see the HTML version of your website, ready for static serving. So, as I navigate to elizabeth-dawson-piano.netlify.app shortly after committing, I see the same updates I saw locally.

Adding Images

We’ll use images from the original site. In the .eleventy.js file, you’ll see that static image assets should go in the static/img folder. Each page will have a hero image, and here’s where front matter works really well. In the front matter section of each page, I’ll add a reference to the hero image:

hero: `/static/img/performance.jpg`

Eleventy keeps page layouts in the _includes/layouts folder. base.njk is used by all page types, so we’ll add this code just under the navigation since that’s where we want our hero image.

{% if (hero) %}
<img class="page-hero" src="{{ hero }}" alt="Hero image for {{ title }}" />
{% endif %}

I also included an image tag for the picture of Elizabeth on the About page, using a CSS class to align it and give it proper padding. Now’s a good time to commit and see exactly what changed.

Embedding A YouTube Player With A Plugin

There are a few YouTube videos on the home page. Let’s use a plugin to create Youtube’s embed code automatically. eleventy-plugin-youtube-embed is a great option for this. The installation instructions are pretty clear: install the package with npm and then include it in our .eleventy.js file. Without any further changes, those YouTube URLs are transformed into embedded players. (see commit)

Using Collections And Filters

We don’t need a blog for this site, but we do need a way to let people know about upcoming events. Our events — for all intents and purposes — will be just like blog posts. Each has a title, a description, and a date.

There are a few steps we need to create this new collection-based page:

  • Create a new events.md file in our pages directory.
  • Add a few events to our posts directory. I’ve added .md files for a holiday concert, a spring concert, and a fall recital.
  • Create a collection definition in .eleventy.js so we can treat these events as a collection. Here’s how the collection is defined: we gather all Markdown files in the posts directory and filter out anything that doesn’t have a location specified in the front matter.
eleventyConfig.addCollection("events", (collection) =>
    collection.getFilteredByGlob("posts/*.md").filter( post => {
        return ( item.data.location ? post : false );
    })
);
  • Add a reference to the collection to our events.md file, showing each event as an entry in a table. Here’s what iterating over a collection looks like:
<table>
    <thead>
        <tr>
            <th>Date</th>
            <th>Title</th>
            <th>Location</th>
        </tr>    
    </thead>
    <tbody>
        {%- for post in collections.events -%}
        <tr>
            <td>{{ post.date }}</td>
            <td><a href="{{ post.url }}">{{ post.data.title }}</a></td>
            <td>{{ post.data.location }}</td>
        </tr>    
        {%- endfor -%}
    </tbody>
</table>

However, our date formatting looks pretty bad.

Table with unformatted dates
Our date formats could use some work. (Large preview)

Luckily, the boilerplate .eleventy.js file already has a filter titled readableDate. It’s easy to use filters on content in Markdown files and templates:

{{ post.date | readableDate }}

Now, our dates are properly formatted! Eleventy’s filter documentation goes into more depth on what filters are available in the framework, and how you can add your own. (see: commit)

Polishing The Site Design With CSS

Okay, so now we have a pretty solid site created. We have pages, hero images, an events list, and a contact form. We’re not constrained by the choice of any theme, so we can do whatever we want with the site’s design… the sky is the limit! It’s up to you to make your site performant, responsive, and aesthetically pleasing. I made some styling and markup changes to get things to our final commit.

Completed website
Our website conversion is complete. (Large preview)

Now we can tell the world about all of our hard work. Let’s publish this site.

Publishing The Site

Oh, but wait. It’s already published! We’ve been working in this nice workflow all along, where our updates to GitHub automatically propagate to Netlify and get rebuilt into fresh, fast HTML. Updates are as easy as a git push. Netlify detects the changes from git, processes markdown into HTML, and serves the static site. When you’re done and ready for a custom domain, Netlify lets you use your existing domain for free. Visit Site Settings > Domain Management for all the details, including how you can leverage Netlify’s free HTTPS certificate with your custom domain.

Advanced: Images, Contact Forms, And Content Management

This was a simple site with only a few images. You may have a more complicated site, though. Netlify’s Large Media service allows you to upload full-resolution images to GitHub, and stores a pointer to the image in Large Media. That way, your GitHub repository is not jam-packed with image data, and you can easily add markup to your site to request optimized crops and sizes of images at request time. I tried this on my own larger sites and was really happy with the responsiveness and ease of setup.

Remember that contact form that was installed with our boilerplate? It just works. When you submit the contact form, you’ll see submissions in Netlify’s administration section. Select “Forms” for your site. You can configure Netlify to email you when you get a new form submission, and you can also add a custom confirmation page in your form’s code. Create a page in your site at /contact/success, for example, and then within your form tag (in form.njk), add action="/contact/success" to redirect users there once the form has been submitted.

The boilerplate also configures the site to be used with Netlify’s content manager. Configuring this to work well for a non-technical person is beyond the scope of the article, but you can define templates and have updates made in Netlify’s content manager sync back to GitHub and trigger automatic redeploys of your site. If you’re comfortable with the workflow of making updates in markdown and pushing them to GitHub, though, this capability is likely something you don’t need.

Further Reading

Here are some links to resources used throughout this tutorial, and some other more advanced concepts if you want to dive deeper.

Smashing Editorial (ra, yk, il)