Building a bilingual static blog with Eleventy: structure, SEO and my mistakes
I wanted this site's blog to be simple and fast: every article is one Markdown file, the output is fully static, two languages (Persian and English), and correct SEO. Eleventy (11ty) exists exactly for this.
Structure
src/
fa/posts/*.md ← Persian articles
en/posts/*.md ← English articles
fa/index.njk ← Persian list (with pagination)
en/index.njk
_includes/ ← templates (base, post)
_data/site.js ← shared per-language names and strings
The build writes into the site folder, so a static host needs no build step.
One file per article
Each Markdown file starts with front matter:
---
title: "Article title"
description: "A unique description for search results"
date: 2026-10-24
slug: english-short-slug
altSlug: english-short-slug
tags: [post, tag]
---
A shared per-language data file builds the final URL from slug (e.g. /blog/<slug>/) and the other language's URL from altSlug. Those two values feed the canonical and hreflang tags.
Per-article SEO
- Unique
titleanddescription, a singleh1. link rel="canonical"to itself.hreflangfor the Persian, English andx-defaultversions.- Open Graph with a WebP image.
- Structured data:
BlogPostingandBreadcrumbListas JSON-LD. - Short English slug.
- sitemap and RSS generated automatically.
Mistakes I made myself
1. A colon in the description. In YAML, any value containing : must be quoted, or the build fails. Always quote titles and descriptions.
2. Persian comma in tags. [absence, sms] written with a Persian «،» becomes one tag, not two; YAML needs the ASCII comma.
3. The template engine eats braces. Eleventy processes Markdown with a template engine (Nunjucks) by default, so double curly braces around a name (like a template variable in a chat message example) lose their value and render empty. Wrap that section in the engine's raw tags.
4. The Jalali calendar. Instead of hand-rolling date conversion, I used a battle-tested library (jalaali-js) and compared its output against known dates (Persian website tips).
5. Broken links. After every build, a small script checks every internal href and src against the files on disk.
Pagination
Eleventy has built-in pagination: give it a collection and a size, and it generates /blog/, /blog/page/2/ and so on. Take the next/previous links from pagination.href.
When is it a good choice?
When the content is article-shaped, changes are not heavily dynamic, and you want high speed and SEO at near-zero cost. If you need comments, accounts or personalized content, you need a backend or a separate service.
Summary
One template, one Markdown file per article and a few data files are enough for a bilingual blog. For the speed side, see this guide.