A minimalist, fast, trilingual (Persian / English / German) blog built with
Astro and Tailwind CSS v4. Persian (Farsi) is the default language with
full RTL support; English is available under /en/ and German under /de/.
- 🌐 Default Persian (fa) at
/, English (en) at/en/, German (de) at/de/ - 📍 Geo-aware language detection on the edge (Iran → fa, DE/AT/CH/LI → de, else en)
- 🌗 Dark / light theme toggle (persisted, no flash-of-wrong-theme)
↔️ Fully RTL-aware layout with logical CSS properties- ✍️ MDX + Markdown, Shiki syntax highlighting (dual dark/light themes)
- 📖 Auto table of contents (scroll-spy), yearly archive, and tag pages
- ⚡ Zero client-side JS by default — most pages are static HTML
- i18n with smart translation mapping — posts in
fa/,en/, andde/are linked with a sharedtranslationKey; the header language switcher jumps straight to the translated post (or the target language's blog listing). - Dark / light theme — a sun/moon toggle switches
data-themeon<html>, driven by CSS custom properties. Light/dark choice is saved inlocalStorageand system preference is followed until you choose. - Self-hosted variable fonts — Source Sans 3 (Latin) + Peyda
(Persian/Arabic) served locally, with
unicode-rangeso each script uses the right font automatically. - Scroll-spy Table of Contents — the blog post layout scans
h2/h3headings and highlights the active section in a sticky sidebar. - Year-grouped archive — posts are grouped by publication year.
- Tag pages — auto-generated
/tagswith a post-count grid. - One-click code copy — a hover button over every code block.
- Back-to-top button — appears after you scroll.
- Locale-aware 404 page — localized error page with contextual actions.
- Astro v7 — content-focused static site generator
- Tailwind CSS v4 — utility-first styling
(
@tailwindcss/vite+@tailwindcss/typography) - MDX — import components & interactive structures in posts
astro-icon— optimized SVG icons (Iconify MDI set)- Shiki — code syntax highlighting with dark/light themes
@astrojs/rss— RSS feeds for each locale
Requires Node.js ≥ 22.12.0.
npm install
npm run dev # http://localhost:4321Build and preview:
npm run build # → dist/
npm run preview
npx astro check # type-check .astro files.
├── astro.config.mjs # Astro config (site URL, integrations, i18n, shiki)
├── package.json # deps + npm scripts
├── wrangler.toml # Cloudflare Pages deploy config (optional)
├── public/ # static assets copied as-is (fonts, icons)
│ └── fonts/ # self-hosted Source Sans 3 + Peyda
└── src/
├── config.ts # 👤 site title, author bio, social links
├── content.config.ts # content-collection schemas (blog + pages)
├── components/ # Header, Footer, Profile, BaseHead
├── layouts/ # BaseLayout, BlogPost (with ToC / scroll-spy)
├── i18n/ # ui.ts (labels) + utils.ts (route helpers)
├── pages/ # file-based routes
│ └── [...lang]/ # localized routes (home, archive, tags, about, blog)
├── styles/
│ └── global.css # 🌗 theme tokens + Tailwind v4 entry
└── content/
├── blog/ # ✍️ posts: fa/, en/, and de/ sub-folders
│ ├── fa/ # Persian posts (default)
│ ├── en/ # English posts
│ └── de/ # German posts
└── pages/ # about page: fa/, en/, and de/
Set the site title, author bio, avatar, and social links here:
export const SITE_CONFIG = {
title: 'Aryan',
description: 'Worth sharing.',
url: 'https://your-domain.com', // ← update before deploying
};
export const AUTHOR = {
name: 'Aryan',
role: { fa: 'نویسنده | توسعهدهنده', en: 'Writer | Developer' },
bio: { fa: '…', en: '…' },
};
export const SOCIALS = [
{ label: 'GitHub', href: 'https://github.com/aryanriyahi', icon: 'mdi:github' },
];Icons come from the Iconify MDI set.
Set site to your real domain; it powers the RSS feed and canonical URLs:
export default defineConfig({
site: 'https://your-domain.com',
// …
});Edit nav/menu strings or add locales:
export const languages = { fa: 'فارسی', en: 'English', de: 'Deutsch' };
export const defaultLang = 'fa';
export const ui = {
fa: { 'nav.home': 'خانه', 'nav.archive': 'بایگانی', … },
en: { 'nav.home': 'Home', 'nav.archive': 'Archive', … },
de: { 'nav.home': 'Start', 'nav.archive': 'Blog', … },
};Adding a new language means three more spots: register the locale in
astro.config.mjs (i18n.locales), add a folder under
src/content/blog/<lang>/ (plus src/content/pages/<lang>/ for the about page),
and — because pagination isn't part of the [...lang] catch-all — copy
src/pages/en/blog/[...page].astro to src/pages/<lang>/blog/[...page].astro
with the new locale.
Color tokens are CSS custom properties toggled by data-theme and bridged
into Tailwind via @theme inline:
:root, :root[data-theme="dark"] { --bg-color: #161a28; --accent: #4a90e2; … }
:root[data-theme="light"] { --bg-color: #f7f8fb; --accent: #1d4ed8; … }
@theme inline {
--color-bg: var(--bg-color); /* → bg-bg, text-bg, … */
--color-accent: var(--accent); /* → bg-accent, text-accent, … */
}Edit the hex values to reskin the whole site (utilities and prose follow automatically).
Posts live in src/content/blog/<lang>/ (fa/, en/, de/). To write a
translated post, create one file in each language and link them with the
same translationKey:
src/content/blog/fa/my-post.md (frontmatter):
---
title: 'عنوان نوشته'
description: 'توضیح کوتاه'
pubDate: '2026-08-04'
tags: ['astro', 'writing']
translationKey: 'my-post' # must match the English file exactly
---src/content/blog/en/my-post.md (frontmatter):
---
title: 'My post'
description: 'Short summary'
pubDate: '2026-08-04'
tags: ['astro', 'writing']
translationKey: 'my-post' # must match the Persian file exactly
---src/content/blog/de/my-post.md (frontmatter):
---
title: 'Mein Beitrag'
description: 'Kurze Zusammenfassung'
pubDate: '2026-08-04'
tags: ['astro', 'writing']
translationKey: 'my-post' # must match the other files exactly
---The header language switcher uses translationKey to link between versions.
Required frontmatter: title, description, pubDate. tags are optional
but enable the tag pages.
This is a fully static site with an edge worker — deploy the dist/ output plus
src/worker.js (see wrangler.toml). Recommended
(this repo is set up for it):
| Host | Guide |
|---|---|
| Cloudflare Workers (static assets + edge worker) | docs/09-deploy-cloudflare.md |
| Netlify | docs/08-deploy-netlify.md |
| Vercel | docs/07-deploy-vercel.md |
All hosts: build command npm run build, publish directory dist, Node 22.
Before deploying, set site in astro.config.mjs to your real domain so the
RSS feed and canonical URLs are correct.
src/worker.jspowers the auto language redirect: visitors from Iran get Persian, visitors from German-speaking countries (DE / AT / CH / LI) get German, everyone else gets English, and a manual choice (thepreferredLangcookie set by the header switcher) is always respected. It runs on every request viarun_worker_firstinwrangler.toml. Seedocs/09-deploy-cloudflare.md.
The full beginner-friendly guide lives in docs/:
project overview, running locally, writing posts, customizing layout and
landing page, and deploying to Cloudflare Workers / Vercel / Netlify.
main— the default branch with all current work (Tailwind v4, docs, Cloudflare deployment, rebrand).feat/tailwind-rewrite— the older feature branch where the Tailwind rewrite was developed. It's already merged intomainand is safe to delete.
Released under the MIT License. Built with Astro and Tailwind CSS.