ساخت وبلاگ و قالب اختصاصی با MkDocs¶
MkDocs یک مولد سایت ایستا است که محتوای نوشتهشده با Markdown را به یک وبسایت سریع و قابلانتشار تبدیل میکند. اگر دوست دارید بهجای درگیرشدن با دیتابیس، پنل مدیریت و نگهداری یک CMS، نوشتههایتان را کنار کد و در Git نگه دارید، MkDocs انتخاب ساده و قدرتمندی است.
در این راهنما یک وبلاگ واقعی میسازیم، ساختار آن را میشناسیم، قابلیت بلاگ را فعال میکنیم و در پایان با HTML، CSS و JavaScript یک قالب اختصاصی به آن میدهیم.
چرا MkDocs؟¶
MkDocs در اصل برای مستندات فنی ساخته شده، اما با قالب Material و افزونهٔ Blog میتواند یک وبلاگ کامل هم باشد. خروجی آن مجموعهای از فایلهای HTML، CSS و JavaScript است؛ بنابراین برای نمایش سایت به اجرای Python یا دیتابیس روی سرور نیاز ندارید.
چند ویژگی مهم آن عبارتاند از:
- نوشتن محتوا با Markdown؛
- ساخت سایت ایستا، سریع و کمهزینه؛
- جستوجوی داخلی بدون سرویس خارجی؛
- پشتیبانی از دستهبندی، برچسب، بایگانی و RSS؛
- نگهداری تاریخچهٔ مطالب با Git؛
- امکان شخصیسازی کامل HTML و CSS؛
- انتشار ساده روی GitHub Pages، GitLab Pages، Netlify یا یک سرور شخصی.
MkDocs انتخاب خوبی برای وبلاگهای فنی، دفترچههای شخصی و سایتهایی است که محتوایشان نسبت به امکانات تعاملی اهمیت بیشتری دارد. اگر به دیدگاه کاربران، عضویت، فروشگاه یا ویرایشگر آنلاین نیاز دارید، احتمالاً یک CMS انتخاب مناسبتری خواهد بود.
پیشنیازها¶
برای شروع به Python 3 و ابزار pip نیاز داریم. ابتدا نسخههای نصبشده را بررسی کنید:
بهتر است وابستگیهای هر پروژه را در یک محیط مجازی جدا نگه داریم. یک پوشه برای پروژه بسازید و وارد آن شوید:
محیط مجازی را ایجاد و فعال کنید:
در Windows PowerShell دستور فعالسازی متفاوت است:
پس از فعالشدن محیط، MkDocs Material را نصب کنید:
نصب را با دستور زیر بررسی کنید:
ایجاد اولین پروژه¶
دستور mkdocs new فایلهای اولیه را میسازد. چون از قبل داخل پوشهٔ پروژه هستیم، نقطه را بهعنوان مسیر مقصد میدهیم:
ساختار اولیه شبیه نمونهٔ زیر خواهد بود:
فایل mkdocs.yml مرکز تنظیمات سایت است و پوشهٔ docs محتوای سایت را نگه میدارد. هر فایل Markdown معمولاً به یک صفحهٔ HTML تبدیل میشود.
تنظیمات ابتدایی را به این شکل بنویسید:
site_name: وبلاگ من
site_url: https://example.com/
site_description: یادداشتهایی دربارهٔ فناوری و نرمافزار
site_author: نام نویسنده
theme:
name: material
language: fa
direction: rtl
اکنون سرور توسعه را اجرا کنید:
سایت بهصورت پیشفرض روی آدرس http://127.0.0.1:8000 در دسترس است. MkDocs تغییر فایلها را تشخیص میدهد و صفحهٔ مرورگر را بهروز میکند.
ساختار پیشنهادی برای یک وبلاگ¶
پیش از افزودن محتوا بهتر است ساختاری داشته باشیم که با بزرگشدن سایت همچنان مرتب بماند:
my-mkdocs-blog/
├── docs/
│ ├── index.md
│ ├── about.md
│ ├── blog/
│ │ ├── index.md
│ │ └── posts/
│ │ └── 2026/
│ │ └── first-post.md
│ ├── assets/
│ │ ├── images/
│ │ └── favicon.svg
│ ├── stylesheets/
│ │ └── extra.css
│ └── javascripts/
│ └── extra.js
├── overrides/
│ └── main.html
├── mkdocs.yml
└── requirements.txt
تقسیم پستها بر اساس سال، ماه یا هفته اجباری نیست، اما در وبلاگی با تعداد نوشتههای زیاد پیداکردن فایلها را آسان میکند. آدرس نهایی پست میتواند با مقدار slug مستقل از محل فایل باقی بماند.
فعالکردن قابلیت بلاگ¶
قالب Material یک افزونهٔ داخلی برای بلاگ دارد. آن را در mkdocs.yml فعال کنید:
plugins:
- search
- blog:
blog_dir: blog
post_dir: "{blog}/posts"
archive: true
archive_name: بایگانی
categories: true
pagination_per_page: 8
post_readtime: true
- tags
گزینههای بالا کارهای زیر را انجام میدهند:
blog_dirصفحهٔ اصلی بلاگ را مشخص میکند؛post_dirمحل فایلهای پست را تعیین میکند؛archiveصفحههای بایگانی زمانی را میسازد؛categoriesدستهبندی مطالب را فعال میکند؛pagination_per_pageتعداد پستهای هر صفحه را محدود میکند؛post_readtimeزمان تقریبی مطالعه را نمایش میدهد.
صفحهٔ docs/blog/index.md را هم ایجاد کنید:
---
title: نوشتهها
description: تازهترین مطالب وبلاگ
---
# نوشتهها
یادداشتها، تجربهها و آموزشهای من.
صفحهٔ فهرست بلاگ بهطور خودکار خلاصهٔ پستها، تاریخ، دستهبندی و صفحهبندی را نمایش میدهد.
نوشتن اولین پست¶
یک فایل Markdown در مسیر پستها بسازید:
هر پست با بخشی به نام front matter آغاز میشود. این بخش میان دو خط --- قرار میگیرد و اطلاعاتی مانند تاریخ، آدرس، دستهبندی و برچسبها را تعریف میکند:
---
draft: false
date:
created: 2026-07-25
updated: 2026-07-26
slug: first-post
categories:
- آموزش
tags:
- لینوکس
- وب
---
# اولین نوشتهٔ من
این متن در صفحهٔ فهرست بلاگ بهعنوان خلاصه نمایش داده میشود.
<!-- more -->
## ادامهٔ مطلب
متنی که بعد از نشانهٔ `more` قرار میگیرد در صفحهٔ کامل پست نمایش داده میشود.
گزینهٔ draft مشخص میکند نوشته منتشر شود یا همچنان پیشنویس بماند. مقدار slug آدرس خوانا و ثابتی برای پست میسازد. تاریخ updated اختیاری است و تنها زمانی لازم است که تغییر مهمی در مطلب ایجاد شده باشد.
تنظیم منوی سایت¶
منوی اصلی با گزینهٔ nav در mkdocs.yml تعریف میشود:
nav:
- خانه: index.md
- نوشتهها: blog/index.md
- یادداشتها:
- لینوکس: notes/linux.md
- زیرساخت: notes/infrastructure.md
- دربارهٔ من: about.md
تورفتگی در YAML معنا دارد؛ بنابراین برای آن از فاصله استفاده کنید و سراغ tab نروید. خطای کوچک در تورفتگی ممکن است تنظیمات را نامعتبر کند یا ساختار منو را تغییر دهد.
قابلیتهای مفید Markdown¶
MkDocs Material چند افزونهٔ کاربردی Markdown دارد. نمونهٔ زیر مجموعهای مناسب برای شروع است:
markdown_extensions:
- admonition
- attr_list
- md_in_html
- tables
- toc:
permalink: true
- pymdownx.details
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
پس از فعالسازی میتوانید کد را همراه با نام فایل نمایش دهید:
برای نکتهها و هشدارها نیز میتوان از admonition استفاده کرد:
!!! warning "پیش از انتشار"
خروجی سایت را با حالت strict بسازید تا لینکها و تنظیمات مشکلدار پیدا شوند.
افزودن قالب اختصاصی¶
ظاهر Material را میتوان در سه سطح تغییر داد:
- تنظیم رنگها و قابلیتها در
mkdocs.yml؛ - افزودن CSS و JavaScript اختصاصی؛
- بازنویسی بخشهایی از قالب با فایلهای Jinja.
بهتر است ابتدا با تنظیمات و CSS شروع کنید و تنها زمانی سراغ override بروید که به تغییر ساختار HTML نیاز دارید.
تنظیم امکانات و رنگها¶
نمونهٔ زیر یک قالب تیره با جستوجو، دکمهٔ بازگشت به بالا و کپیکردن کد میسازد:
theme:
name: material
language: fa
direction: rtl
features:
- navigation.instant
- navigation.instant.progress
- navigation.sections
- navigation.top
- search.suggest
- search.highlight
- content.code.copy
palette:
scheme: slate
primary: custom
accent: custom
نام custom اجازه میدهد رنگها را با متغیرهای CSS خودمان تعریف کنیم.
افزودن CSS اختصاصی¶
فایل docs/stylesheets/extra.css را بسازید و آن را در تنظیمات معرفی کنید:
حالا میتوان رنگها، فونت و اجزای صفحه را تغییر داد:
@import url("https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700&display=swap");
:root,
[data-md-color-scheme="slate"] {
--md-primary-fg-color: #101412;
--md-accent-fg-color: #39ff88;
--md-default-bg-color: #080b0a;
--md-default-fg-color: #e8f0ea;
}
body,
.md-typeset,
.md-nav,
.md-header {
font-family: "Vazirmatn", Tahoma, sans-serif;
}
.md-typeset {
line-height: 1.9;
}
.md-typeset pre,
.md-typeset code {
direction: ltr;
text-align: left;
}
.md-typeset a {
color: var(--md-accent-fg-color);
}
در یک سایت RTL نباید جهت کد، آدرس، دستور shell یا مقدارهای فنی را راستبهچپ کرد. تعیین direction: ltr برای بلوکهای کد جلوی بههمریختن آنها را میگیرد.
بازنویسی قالب HTML¶
قالب Material از Jinja استفاده میکند. برای توسعهٔ آن ابتدا پوشهٔ overrides را به تنظیمات معرفی کنید:
سپس فایل overrides/main.html را بسازید. لازم نیست کل قالب را کپی کنید؛ قالب پایه را extend و فقط block مورد نظر را بازنویسی کنید:
{% extends "base.html" %}
{% block extrahead %}
<meta name="theme-color" content="#080b0a">
<meta property="og:type" content="website">
<meta property="og:title" content="{{ page.title }} — {{ config.site_name }}">
<meta
property="og:description"
content="{{ config.site_description }}"
>
{% endblock %}
{% block footer %}
<footer class="custom-footer">
<span>© ۲۰۲۶ نام سایت</span>
<a href="#top">بازگشت به بالا ↑</a>
</footer>
{% endblock %}
استفاده از blockهای قالب بهجای کپیکردن کامل فایلهای Material باعث میشود بهروزرسانیهای آینده کمتر قالب شما را بشکنند.
افزودن JavaScript¶
برای رفتارهای کوچک سمت مرورگر، فایل docs/javascripts/extra.js را ایجاد و آن را به تنظیمات اضافه کنید:
اگر قابلیت navigation.instant فعال باشد، تنها گوشدادن به رویداد DOMContentLoaded کافی نیست؛ زیرا MkDocs هنگام جابهجایی میان صفحهها همهٔ سند را دوباره بارگذاری نمیکند. از observable داخلی Material استفاده کنید:
document$.subscribe(() => {
document.querySelectorAll("pre code").forEach((block) => {
block.setAttribute("data-ready", "true");
});
});
کدی که داخل document$.subscribe قرار میگیرد پس از هر بار تغییر صفحه اجرا میشود.
تصویر، favicon و فایلهای ثابت¶
تصویرها را میتوان در docs/assets/images نگه داشت و با Markdown نمایش داد:
برای دسترسپذیری و SEO، توضیح تصویر را خالی نگذارید. favicon و لوگو نیز از تنظیمات قالب معرفی میشوند:
فایلهایی که داخل docs قرار دارند همراه خروجی سایت کپی میشوند؛ بنابراین فایل خصوصی، رمز، token یا پیکربندی حساس را در این پوشه نگذارید.
دستهبندی، برچسب و RSS¶
دستهبندی برای گروههای اصلی محتوا و برچسب برای موضوعات جزئیتر مناسب است. بهتر است تعداد دستهها محدود و نامگذاری آنها یکدست باشد. برای نمونه، «لینوکس» و «Linux» را همزمان بهعنوان دو برچسب جدا استفاده نکنید.
برای ساخت RSS افزونهٔ مربوط را نصب کنید:
سپس آن را به تنظیمات بیفزایید:
plugins:
- search
- blog
- tags
- rss:
match_path: blog/posts/.*
date_from_meta:
as_creation: date.created
as_update: date.updated
مقدار درست site_url برای تولید لینکهای کامل در RSS، canonical URL و نقشهٔ سایت ضروری است.
مدیریت وابستگیها¶
وابستگیهای پروژه را در requirements.txt ثبت کنید تا ساخت سایت روی دستگاه یا سرور دیگری تکرارپذیر باشد:
نصب همهٔ وابستگیها با یک دستور انجام میشود:
خود پوشهٔ محیط مجازی و خروجی تولیدشده را در Git نگه ندارید. یک فایل .gitignore مناسب میتواند چنین محتوایی داشته باشد:
محیط مجازی را جابهجا یا کپی نکنید؛ scriptهای اجرایی آن معمولاً مسیر مطلق محیط را در خود دارند. در مسیر جدید، محیط را دوباره بسازید و وابستگیها را از requirements.txt نصب کنید.
بررسی و ساخت نسخهٔ نهایی¶
در زمان نوشتن از سرور توسعه استفاده کنید:
پیش از انتشار، سایت را با حالت strict بسازید:
این دستور ابتدا خروجی قبلی را پاک میکند، سایت را در پوشهٔ site میسازد و هشدارها را بهعنوان خطا در نظر میگیرد. محتوای پوشهٔ site همان چیزی است که باید روی وبسرور منتشر شود.
برای دیدن جزئیات بیشتر هنگام پیداکردن خطا میتوانید حالت verbose را فعال کنید:
موارد زیر را پیش از انتشار بررسی کنید:
- همهٔ لینکهای داخلی و تصویرها باز شوند؛
- عنوان و توضیح هر صفحه مناسب باشد؛
- صفحه در موبایل و نمایشگر بزرگ خوانا باشد؛
- کدها در حالت RTL بههم نریزند؛
- جستوجو نتیجهٔ مورد انتظار را پیدا کند؛
- هیچ فایل یا اطلاعات حساسی وارد خروجی نشده باشد.
انتشار روی سرور شخصی¶
پس از build تنها پوشهٔ site را به وبسرور منتقل کنید:
سپس Nginx یا Caddy را طوری تنظیم کنید که همان پوشه را بهصورت static ارائه دهد. اجرای mkdocs serve روی سرور production لازم نیست و نباید آن را مستقیماً در معرض اینترنت قرار داد.
یک تنظیم سادهٔ Nginx میتواند شبیه نمونهٔ زیر باشد:
server {
listen 80;
server_name example.com;
root /var/www/my-blog;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
در محیط واقعی HTTPS را نیز با ابزاری مانند Certbot یا سرویسدهندهٔ مورد استفاده فعال کنید.
انتشار خودکار با Forgejo Actions¶
اگر مخزن روی Forgejo قرار دارد، میتوان همان فرایند فعلی rsync را بعد از هر push روی شاخهٔ main اجرا کرد. Forgejo فایلهای workflow را از مسیر .forgejo/workflows میخواند و برای اجرای آن به یک runner فعال نیاز دارد.
ابتدا در تنظیمات repository مطمئن شوید Actions فعال است و در بخش Actions > Runners حداقل یک runner با label مورد استفادهٔ شما، برای مثال docker، در دسترس است.
ساخت کلید مخصوص deployment¶
بهتر است برای CI یک کلید جدا و بدون passphrase بسازید تا مجبور نباشید کلید شخصی خودتان را در Forgejo قرار دهید:
کلید عمومی را به کاربری که alias فعلی pars-intelligent با آن متصل میشود اضافه کنید:
اتصال و مجوز نوشتن در مسیر مقصد را پیش از ساخت workflow آزمایش کنید:
ssh -i forgejo-deploy-key pars-intelligent \
'test -d /opt/front/mehdimj/html && test -w /opt/front/mehdimj/html'
برای جلوگیری از تأیید تعاملی fingerprint، کلید میزبان را نیز ثبت کنید. مقدار خروجی دستور زیر را پس از مقایسهٔ fingerprint با مقدار معتبر سرور نگه دارید:
اگر pars-intelligent فقط یک alias در ~/.ssh/config است، برای ssh-keyscan باید hostname یا IP واقعی سرور را وارد کنید.
تعریف secretها در Forgejo¶
در صفحهٔ repository به Settings > Actions > Secrets بروید و secretهای زیر را بسازید:
DEPLOY_HOST: hostname یا IP واقعی سرور؛DEPLOY_USER: نام کاربر SSH مربوط به alias فعلی؛DEPLOY_PORT: پورت SSH، معمولاً22؛DEPLOY_SSH_KEY: محتوای کامل فایل خصوصیforgejo-deploy-key؛DEPLOY_KNOWN_HOSTS: خروجی تأییدشدهٔ دستورssh-keyscan.
کلید خصوصی را میتوانید برای کپیکردن در secret نمایش دهید:
فایل خصوصی را هرگز commit نکنید. پس از ثبت secret و اطمینان از داشتن نسخهٔ امن، آن را از محیط کاری خود پاک کنید یا در محل امنی نگه دارید.
ساخت workflow¶
فایل .forgejo/workflows/deploy.yml را ایجاد کنید:
name: Deploy MkDocs
on:
push:
branches:
- main
jobs:
deploy:
runs-on: docker
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
steps:
- name: دریافت کد پروژه
uses: https://data.forgejo.org/actions/checkout@v6
- name: نصب ابزارهای لازم
run: |
apt-get update
apt-get install -y --no-install-recommends \
python3 \
python3-venv \
rsync \
openssh-client
- name: ساخت سایت
run: |
python3 -m venv .venv-ci
.venv-ci/bin/python -m pip install --upgrade pip
.venv-ci/bin/python -m pip install -r requirements.txt
.venv-ci/bin/python -m mkdocs build --clean --strict
- name: آمادهسازی SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
run: |
install -d -m 700 "$HOME/.ssh"
printf '%s\n' "$DEPLOY_SSH_KEY" > "$HOME/.ssh/deploy_key"
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/deploy_key"
chmod 600 "$HOME/.ssh/known_hosts"
- name: انتشار با rsync
run: |
rsync -av --delete \
-e "ssh -i $HOME/.ssh/deploy_key -p $DEPLOY_PORT" \
site/ \
"$DEPLOY_USER@$DEPLOY_HOST:/opt/front/mehdimj/html/"
اگر label مربوط به runner شما docker نیست، مقدار runs-on را دقیقاً با label نمایشدادهشده در تنظیمات Forgejo جایگزین کنید. runner همچنین باید بتواند به سرور pars-intelligent روی پورت SSH دسترسی شبکهای داشته باشد.
این workflow ابتدا build سختگیرانه را اجرا میکند؛ در نتیجه اگر ساخت سایت شکست بخورد، مرحلهٔ rsync اجرا نمیشود. گزینهٔ --delete فایلهایی را که دیگر در خروجی جدید وجود ندارند از مسیر مقصد حذف میکند، پس کاربر deployment را فقط به همین مسیر محدود کنید و مقدار مقصد را بدون بررسی تغییر ندهید.
پس از commit و push فایل workflow، نتیجهٔ اجرا در زبانهٔ Actions مخزن دیده میشود:
git add .forgejo/workflows/deploy.yml
git commit -m "Add Forgejo deployment workflow"
git push origin main
خطاهای رایج¶
دستور mkdocs پیدا نمیشود¶
ابتدا مطمئن شوید محیط مجازی فعال است:
اگر محیط مجازی از پوشهٔ دیگری کپی یا جابهجا شده، آن را دوباره بسازید:
deactivate 2>/dev/null || true
python3 -m venv --clear .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
تغییر CSS دیده نمیشود¶
مسیر فایل را در extra_css بررسی و cache مرورگر را پاک کنید:
لینک صفحه در خروجی خراب است¶
در Markdown تا جای ممکن به فایل منبع لینک دهید تا MkDocs آن را هنگام build بررسی کند:
اجرای build با --strict لینکها و تنظیمات مشکوک را زودتر آشکار میکند.
YAML خوانده نمیشود¶
از فاصله بهجای tab استفاده و ساختار فایل را با دقت بررسی کنید. رشتههایی که شامل کاراکترهای خاص هستند بهتر است داخل کوتیشن قرار بگیرند:
چند اصل برای نگهداری بهتر¶
یک وبلاگ MkDocs وقتی در درازمدت خوشدست میماند که چند اصل ساده رعایت شود:
- محتوا، قالب و تنظیمات را در Git نگه دارید.
- آدرس پستهای منتشرشده را بدون دلیل تغییر ندهید.
- افزونهها را محدود و وابستگیها را نسخهبندی کنید.
- تغییرهای قالب را کوچک نگه دارید و از override کامل قالب بپرهیزید.
- پیش از هر انتشار، build سختگیرانه اجرا کنید.
- خروجی
siteرا از فایلهای منبع و اطلاعات خصوصی جدا نگه دارید. - خوانایی و سرعت را بر جلوههای نمایشی سنگین ترجیح دهید.
جمعبندی¶
MkDocs راهی ساده برای تبدیل فایلهای Markdown به وبلاگی سریع، مرتب و قابلنسخهبندی است. محتوا در فایلهای متنی باقی میماند، ظاهر سایت با Material و CSS شکل میگیرد و خروجی نهایی بدون دیتابیس روی تقریباً هر سرویس میزبانی قابل انتشار است.
برای شروع لازم نیست از همان روز اول قالب پیچیدهای بسازید. ابتدا ساختار محتوا، نوشتن پست و فرایند انتشار را پایدار کنید؛ سپس رنگ، فونت، صفحهٔ اصلی و اجزای قالب را مرحلهبهمرحله تغییر دهید. بهترین قالب، قالبی است که خواندن را آسان کند و اجازه دهد تمرکز اصلی روی خود نوشتهها باقی بماند.