كيفية بناء مدونة بلا خوادم مع نطاق مخصص وواجهة إدارة باستخدام Hugo + GitHub + Decap CMS + Cloudflare Pages
كنت أشغّل هذا الموقع سابقاً على نطاق مخصص مربوط بالخدمة القديمة من Google Sites (زمن Google Apps). أوقفت Google تلك الخدمة في 2021، فظل عنوان https://www.ixam.net معطلاً فترة طويلة. في النهاية قررت أن أعيد استغلال النطاق. أردت حلاً بلا خوادم لا يتقيد بمنصة قد تختفي فجأة، وهكذا بدأت رحلة إعادة البناء برفقة ChatGPT.
كنت أظن أن ChatGPT سيجعل المهمة سهلة، لكن الواقع مختلف. نسيت متطلبات أساسية، وتعقّدنا في دائرة مغلقة أثناء الاستكشاف، واضطررت إلى مطالعة مستودعات مثل https://github.com/patrickgrey/website/ لأجد الطريق. كان جهداً شاقاً لكنه اكتمل كعمل مشترك بيني وبين الذكاء الاصطناعي.
الإحساس كان أشبه بتوجيه زميل موهوب لكنه يسيء الفهم كثيراً والعودة به إلى المسار الصحيح ─ وهو دليل على أن الذكاء الاصطناعي وصل إلى مرحلة نافعة حقاً. لكن كلما تعقدت الأمور، ازداد الإحباط من الأخطاء المتكررة.
الخلاصة بالنسبة لي: بدلاً من إجبار الذكاء الاصطناعي على تذكر كل الخلفية في محادثة واحدة، من الأفضل التوقف مؤقتاً، تلخيص الوضع بنفسك، ثم فتح جلسة جديدة بتعليمات واضحة.
ومع كل ذلك، لا أعتقد أنني كنت لأحقق هذا القدر من البحث والتجارب والتعلم بمفردي. إنتاجية الذكاء الاصطناعي التوليدي مذهلة.
الأهداف
- إتاحة المدونة على
https://www.example.com/
- تسجيل الدخول إلى DecapCMS عبر
https://www.example.com/admin/
وإنشاء المقالات - حفظ المقالات الجديدة في GitHub ثم نشرها تلقائياً إلى Cloudflare
- إبقاء التكاليف التشغيلية الإضافية صفراً (رسوم تسجيل النطاق خارج هذا النطاق)
0. المتطلبات المسبقة وهيكل المجلدات
0-1. المتطلبات
-
يفترض أن النطاق (مثل
example.com
) مسجل مسبقاً -
يصلح كل من Windows وmacOS (أدرجت أوامر النظامين)
-
الخدمات المطلوبة (النسخ المجانية تكفي)
- حساب GitHub
- حساب Cloudflare (لاستخدام Pages)
- Git على جهازك المحلي
- Hugo (للمعاينة محلياً وللبناء على Cloudflare)
0-2. مثال على بنية المستودع
hugo.toml
content/
blog/
posts/
data/
functions/
api/
auth.js
callback.js
layouts/
_default/
baseof.html
index.html
list.html
single.html
static/
_headers
admin/
config.yml
index.html
css/
main.css
وتؤدي هذه البنية الأدوار التالية:
hugo.toml
… إعدادات الموقع بالكامل (يجب استبدالbaseURL
بعنوان الإنتاج)functions/api/*.js
… دوال Cloudflare Pages (النقاط/api/auth
و/api/callback
لمصادقة GitHub)layouts/_default/*.html
… قوالب Hugostatic/admin/
… واجهة وإعدادات DecapCMSstatic/css/main.css
… المظهر (CSS)static/_headers
… إعدادات رؤوس HTTP في Cloudflare Pages (اختيارية)
ستجد في الدليل علامات “راجع الاستبدال” تشير إلى الأماكن التي يلزم تكييفها مع بيئتك.
1. التحضير
1-1. إنشاء حساب GitHub
- أنشئ حساب GitHub من المتصفح.
1-2. إنشاء حساب Cloudflare
- أنشئ حساب Cloudflare من المتصفح.
1-3. تثبيت Git وHugo
-
Windows (PowerShell)
PS C:\Users\alice> winget install Git.Git PS C:\Users\alice> winget install Hugo.Hugo.Extended
-
macOS (Terminal)
mac:~ dev$ brew install git mac:~ dev$ brew install hugo
2. التنفيذ الفعلي (حتى أول نشر)
2-1. تجهيز المستودع محلياً
-
أنشئ مستودعاً فارغاً في GitHub
- المسار: GitHub > New repository
- مثال الاسم:
my-hugo-blog
-
انسخه إلى جهازك المحلي
-
Windows
PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git PS C:\work> cd .\my-hugo-blog
-
macOS
mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git mac:~/work dev$ cd my-hugo-blog
-
-
ضع مجموعة الملفات والمجلدات المذكورة أعلاه مباشرة تحت المستودع
قائمة “راجع الاستبدال” (تغييرات إلزامية)
-
hugo.toml
baseURL = "https://www.example.com" # ← استبدل بعنوان الإنتاج (مثال: https://www.example.com) languageCode = "ja-jp" title = "Example Blog" publishDir = "public" [permalinks] blog = "/blog/:year/:month/:slug/"
-
static/admin/config.yml
backend: name: github repo: <YOUR_GH_USERNAME>/my-hugo-blog # ← استبدل بمستودعك على GitHub branch: master # ← اسم الفرع الافتراضي (main أو master) base_url: https://www.example.com # ← استبدل بعنوان الإنتاج auth_endpoint: /api/auth # ← نقطة الدالة (ثابتة) media_folder: static/uploads public_folder: /uploads
ملاحظة: ملفا
functions/api/auth.js
وfunctions/api/callback.js
يعملان كما هما لأنهما يستخدمانurl.origin
، فلا حاجة لتعديل خاص بالبيئة.
-
نفّذ أول عملية commit وpush
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + DecapCMS + CF Pages" PS C:\work\my-hugo-blog> git push -u origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + DecapCMS + CF Pages" mac:~/work/my-hugo-blog dev$ git push -u origin master
-
2-2. إنشاء تطبيق OAuth في GitHub (لتسجيل دخول DecapCMS)
-
المسار: GitHub > Settings > Developer settings > OAuth Apps > New OAuth App
-
أدخل البيانات:
- Application name:
DecapCMS for my-hugo-blog
- Homepage URL:
https://www.example.com
- Authorization callback URL:
https://www.example.com/api/callback
← مهم جداً
- Application name:
-
بعد الإنشاء ستحصل على:
- Client ID
- Client Secret (أنشئ واحداً جديداً واحتفظ به)
هذه القيم ستُسجل كمتغيرات بيئة في Cloudflare Pages.
2-3. إنشاء مشروع Cloudflare Pages
-
المسار: Cloudflare Dashboard > Pages > Create a project > Connect to Git
-
اربط GitHub واختر المستودع
my-hugo-blog
-
إعدادات البناء:
-
Framework preset:
None
(أو اترك Cloudflare يتعرف عليه تلقائياً) -
Build command:
hugo
-
Build output directory:
public
-
متغيرات البيئة:
HUGO_VERSION
=0.128.0
(مثال؛ من الأفضل مطابقته مع إصدارك المحلي)GITHUB_CLIENT_ID
= (القيمة من الخطوة السابقة)GITHUB_CLIENT_SECRET
= (القيمة من الخطوة السابقة)
-
-
اضغط Save and Deploy وانتظر عملية النشر الأولى
- عند النجاح ستحصل على رابط معاينة
*.pages.dev
- عند النجاح ستحصل على رابط معاينة
2-4. ربط النطاق المخصص www.example.com
-
المسار: Cloudflare > Pages > (المشروع) > Custom domains > Set up a custom domain
-
أدخل
www.example.com
وأضفه -
إذا لم تكن قد نقلت النطاق إلى Cloudflare:
- سجّل معلومات سجل DNS التي يعرضها Cloudflare (مثل
CNAME www -> <project>.pages.dev
) - توجه إلى لوحة تحكم الجهة المسجلة للنطاق وأضف السجل وفق التعليمات الخاصة بها
- سجّل معلومات سجل DNS التي يعرضها Cloudflare (مثل
-
بعد الانتشار تأكد من فتح
https://www.example.com/
تلميح: إذا كان النطاق مداراً بالكامل في Cloudflare فيمكن إنشاء سجلات DNS المطلوبة بنقرة واحدة.
2-5. تسجيل الدخول إلى لوحة DecapCMS
- افتح
https://www.example.com/admin/
- اضغط زر Login with GitHub ووافق على OAuth
- في المرة الأولى سيطلب GitHub تأكيد التفويض؛ اضغط Authorize
إذا فشل تسجيل الدخول، فتحقق من عنوان callback في OAuth ومن متغيرات البيئة في Cloudflare (
GITHUB_CLIENT_ID
/SECRET
).
3. إجراءات التشغيل (إنشاء المقالات، مزامنة المحلي، تعديل القوالب)
3-1. إنشاء مقال عبر لوحة CMS
- الواجهة:
https://www.example.com/admin/
- من القائمة اليسرى اختر Blog ثم New blog
- أدخل:
Title
وPublish Date
وDescription
وBody
- عند الضغط على Publish سيُنشَر المقال: commit في GitHub → نشر تلقائي في Cloudflare
ملف Markdown الناتج سيوضع في
content/blog/
.
3-2. تحديث المستودع المحلي بعد النشر من CMS
-
Windows
PS C:\work\my-hugo-blog> git pull origin master
-
macOS
mac:~/work/my-hugo-blog dev$ git pull origin master
بهذه الطريقة تحصل محلياً على أحدث المقالات، ما يسمح بتعديل القوالب أو CSS بأمان.
3-3. تعديل التصميم أو القوالب محلياً
-
شغّل خادم التطوير المحلي
-
Windows
PS C:\work\my-hugo-blog> hugo server -D
-
macOS
mac:~/work/my-hugo-blog dev$ hugo server -D
-
افتح
http://localhost:1313/
في المتصفح للتحقق
-
-
أمثلة على نقاط التعديل:
layouts/_default/baseof.html
… عناصر<head>
والرأس والتذييلlayouts/_default/index.html
… قائمة “أحدث المقالات” في الصفحة الرئيسيةlayouts/_default/single.html
… قالب صفحات المقالاتstatic/css/main.css
… الألوان، الخطوط، الهوامش
-
بعد التعديل نفّذ commit ثم push
-
Windows
PS C:\work\my-hugo-blog> git add -A PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts" PS C:\work\my-hugo-blog> git push
-
macOS
mac:~/work/my-hugo-blog dev$ git add -A mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts" mac:~/work/my-hugo-blog dev$ git push
-
بعد ثوانٍ أو دقائق سينفذ Cloudflare نشرًا تلقائياً
-
3-4. لمحة عن إدارة الفروع
- تأكد من تطابق اسم الفرع الافتراضي (
main
أوmaster
) بين إعدادconfig.yml
في DecapCMS وإعدادات Cloudflare - لإنشاء بيئة معاينة، افتح Pull Request في GitHub وسيولّد Cloudflare Pages بيئة Preview تلقائياً
4. إضافات مفيدة (اختيارية)
4-1. مثال لملف static/_headers
(منع التخزين المؤقت للوحة الإدارة)
/admin/*
Cache-Control: no-store
4-2. robots وملف sitemap (حسب الحاجة)
- أنشئ
static/robots.txt
للتحكم بالزواحف - أضف إعداد
outputs
فيhugo.toml
لتوسيع توليد RSS أو sitemap
5. أعطال شائعة وكيفية التعامل معها
- لوحة CMS تدور بلا توقف
→ تحقق من أن عنوان callback في OAuth هوhttps://www.example.com/api/callback
وأن متغيرات البيئةGITHUB_CLIENT_ID/SECRET
في Cloudflare صحيحة - الصفحة الرئيسية تعمل لكن صفحات المقالات تعطي 404
→ تأكد من تطابقbaseURL
فيhugo.toml
مع النطاق الفعلي، وراجع سجل البناء في Cloudflare للتأكد من إنشاءpublic/
- صفحة /admin بيضاء بالكامل
→ تأكد من عدم حظر تحميل DecapCMS (قد تمنعه إضافات المتصفح). عطّل الإضافات وأعد التحميل
6. قائمة المراجعة للأجزاء التي تتطلب استبدالاً (إعادة تأكيد)
الملف/الإعداد | المفتاح | قيمة الاستبدال (مثال) |
---|---|---|
hugo.toml |
baseURL |
https://www.example.com |
static/admin/config.yml |
repo |
<YOUR_GH_USERNAME>/my-hugo-blog |
static/admin/config.yml |
branch |
main أو master وفق الواقع |
static/admin/config.yml |
base_url |
https://www.example.com |
Cloudflare Pages | HUGO_VERSION |
مثال: 0.128.0 |
Cloudflare Pages | GITHUB_CLIENT_ID |
Client ID لتطبيق OAuth في GitHub |
Cloudflare Pages | GITHUB_CLIENT_SECRET |
Client Secret لتطبيق OAuth في GitHub |
تطبيق GitHub OAuth | Callback URL | https://www.example.com/api/callback |
Cloudflare Pages | Custom domain | إضافة www.example.com |
7. ملاحق: أمثلة ملفات يمكنك النسخ منها
7-1. functions/api/auth.js
// Cloudflare Pages Functions (/api/auth)
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
try {
const url = new URL(request.url);
const redirectUrl = new URL('https://github.com/login/oauth/authorize');
redirectUrl.searchParams.set('client_id', client_id);
redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
redirectUrl.searchParams.set('scope', 'repo user');
redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
return Response.redirect(redirectUrl.href, 302);
} catch (err) {
return new Response(String(err?.message || err), { status: 500 });
}
}
7-2. functions/api/callback.js
function renderBody(status, content) {
const html = `
<script>
const receiveMessage = (message) => {
window.opener.postMessage(
'authorization:github:${status}:${JSON.stringify(content)}',
message.origin
);
window.removeEventListener("message", receiveMessage, false);
}
window.addEventListener("message", receiveMessage, false);
window.opener.postMessage("authorizing:github", "*");
</script>`;
return new Blob([html]);
}
export async function onRequest(context) {
const { request, env } = context;
const client_id = env.GITHUB_CLIENT_ID;
const client_secret = env.GITHUB_CLIENT_SECRET;
try {
const url = new URL(request.url);
const code = url.searchParams.get('code');
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
body: JSON.stringify({ client_id, client_secret, code }),
});
const result = await response.json();
if (result.error) {
return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
}
const token = result.access_token;
const provider = 'github';
return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
} catch (error) {
return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
}
}
7-3. static/admin/config.yml
backend:
name: github
repo: <YOUR_GH_USERNAME>/my-hugo-blog
branch: main
base_url: https://www.example.com
auth_endpoint: /api/auth
media_folder: static/uploads
public_folder: /uploads
collections:
- name: 'blog'
label: 'Blog'
folder: 'content/blog'
create: true
slug: '{{slug}}'
editor:
preview: false
fields:
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Publish Date', name: 'date', widget: 'datetime' }
- { label: 'Description', name: 'description', widget: 'string' }
- { label: 'Body', name: 'body', widget: 'markdown' }
9. نصائح لتحسين SEO والأداء
بعد بناء المدونة يمكنك تحسين ترتيب البحث وسرعة العرض عبر الخطوات التالية:
-
إعداد خريطة الموقع وrobots.txt
- سجل
public/sitemap.xml
المولد تلقائياً في Google Search Console - أضف قواعد الزواحف في
static/robots.txt
- سجل
-
ضبط OGP (Open Graph) وبطاقات تويتر
- أضف عناصر
<meta property="og:title">
و<meta property="og:image">
إلىlayouts/_default/baseof.html
- يساعد ذلك على ظهور الروابط بشكل جذاب عند مشاركتها على الشبكات الاجتماعية
- أضف عناصر
-
تحويل الصور إلى WebP
- استعمل
resources/_gen/images/
وخط أنابيب Hugo لتحويل الصور تلقائياً إلى WebP - يسرّع زمن تحميل الصفحات
- استعمل
-
إضافة وظائف التصنيفات والوسوم
- أضف
categories
وtags
إلى Front Matter فيcontent/blog/
واعرض غيمة وسوم في القالب
- أضف
-
إدخال البيانات المنظمة
- استخدم JSON-LD لتمرير معلومات المقالات لمحركات البحث والحصول على نتائج غنية
8. الخلاصة
- أنشئ مستودع GitHub، واربطه بـ Cloudflare Pages، وأعدد OAuth لـ DecapCMS واربط النطاق المخصص ─ وهكذا يكتمل الهيكل
- المقالات تُنشأ من
/admin/
→ تُحفظ في GitHub → تنشر تلقائياً عبر Cloudflare - عدّل القوالب والتصميم محلياً عبر
hugo server
، ثم ادفع التغييرات باستخدام Git
بهذه الخطوات تحصل على بيئة مدونات بلا خوادم باستخدام Hugo + GitHub + Decap CMS + Cloudflare قابلة للتشغيل والإدارة.
بعد اكتمال البنية الأساسية منخفضة التكلفة، ما زلت أشعر أن الموقع ينقصه البحث الداخلي والتصنيفات وغيمة الوسوم مقارنة بالمدونات الناضجة. لكن لا بأس أن نبنيها تدريجياً بالعمل الثنائي مع ChatGPT.