صمم أول مدونة لك باستخدام Astro– خطوة بخطوة”
إذا كنت تريد إنشاء موقع سريع، حديث، خفيف، ويعتمد على أحدث مفاهيم الويب مثل Islands Architecture… فمرحبًا بك في هذا المقال المثالي للبدء!
في هذا المقال العملية، سنبدأ من الصفر تمامًا ونبني معًا مدونة كاملة باستخدام Astro — إطار الويب الذي غيّر قواعد اللعبة بفضل سرعته الخارقة وبساطته المدهشة.
هذا المقال ليس مقال نظري أو سردًا لمفاهيم مجردة؛ بل هي ورشة عمل مفتوحة ستتعلم فيها كيفية:
- إنشاء مشروع Astro من الصفر
- بناء مكونات UI احترافية
- إنشاء صفحات ديناميكية
- تنظيم المحتوى باستخدام Content Collections
- ربط Markdown مع مكوّنات Astro
- استخدام نظام المسارات الديناميكي
[slug].astro - إنشاء صفحة أرشيف /blog
وبالطبع، ستخرج في النهاية بمشروع حقيقي تستطيع رفعه على GitHub، نشره على Netlify أو Vercel، أو اعتماده كأساس لمدونتك الشخصية أو موقع محتوى احترافي.
أولا : تجهيز بيئة العمل وبدء مشروع Astro (عملي 100%)
- إنشاء مشروع Astro جديد
- تشغيله على جهازك
- فهم بنية المشروع
- تعديل أول صفحة
هيا نبدأ
1. تثبيت أدوات التطوير
تحتاج إلى:
Node.js (الإصدار 18 أو أحدث)
تحقق من النسخة:
node -v
2. إنشاء مشروع Astro جديد
في Windows Terminal (كما تفضّل دائمًا)، نفّذ:
npm create astro@latest
سيظهر لك معالج الأسئلة:
اختر الإعدادات التالية:
| السؤال | الإجابة المقترحة |
|---|---|
| Project name | astro-blog |
| Template | Empty project |
| TypeScript | Yes (recommended) |
| Install dependencies | Yes |
بعدها أدخل المشروع:
cd astro-blog
3. تشغيل مشروع Astro لأول مرة
npm run dev
سيفتح لك Astro موقعك على:
http://localhost:4321
سترى صفحة البداية البسيطة:
“Welcome to Astro”
4. فهم بنية مشروع Astro
عند فتح المشروع في VS Code سترى التالي:
astro-blog/
│── src/
│ ├── pages/
│ │ └── index.astro
│── public/
│── package.json
│── astro.config.mjs
شرح سريع:
src/pages
كل ملف داخل هذا المجلّد = صفحة حقيقية في الموقع.
مثل: about.astro → /about
public
صور، ملفات ثابتة، أي شيء يُنسخ كما هو.
astro.config.mjs
إعدادات المشروع.
index.astro
الصفحة الرئيسية.
5. أول تعديل عملي — لنصنع صفحتنا الأولى
افتح:
src/pages/index.astro
ستجد محتوى بسيط.
احذفه واستبدله بهذا:
---
const title = "مرحباً بك في أول مشروع Astro!";
---
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<p>
هذه هي أول صفحة نبنيها في سلسلة “بناء مدونة باستخدام Astro”.
سترى كيف يعمل كل شيء خطوة بخطوة.
</p>
</body>
</html>
احفظ الملف → سترى التغيير مباشرة في المتصفح.
6. تحدّي صغير (اختبار الفهم)
قبل الانتقال للدرس التالي، قم بإضافة صفحة جديدة:
ملف جديد:
src/pages/about.astro
واكتب داخله صفحة بسيطة:
- عنوان
- فقرة تعريف بالمدونة
- رابط يعود للصفحة الرئيسية
مثال:
<a href="/">العودة إلى الصفحة الرئيسية</a>
إذا ظهرت الصفحة على:
http://localhost:4321/about
فأنت جاهز للجزء الثاني.
الجزء الثاني – بناء أول مكوّن في Astro (Navbar) وفهم المكوّنات عمليًا
في هذا الجزء سننتقل من الصفحات إلى المكوّنات Components، وهي قلب فلسفة Astro.
سنقوم ببناء مشروعنا بشكل عملي تمامًا:
- إنشاء أول مكوّن: Navbar
- استدعاؤه في كل الصفحات
- فهم Frontmatter
- فهم كيفية تمرير الخصائص Props
- ماذا يميز مكوّنات Astro عن React/Vue/Svelte
لنبدأ 👇
1. ما هو المكوّن في Astro؟
المكوّن في Astro هو ملف .astro يحتوي على:
- منطقة السكريبت (Frontmatter) بين
--- - منطقة HTML
- قد يتضمن CSS مضمّن
على عكس React، مكوّنات Astro لا تحتاج جافاسكربت افتراضيًا—وهذا ما يجعل المواقع أسرع بكثيـــر.
2. إنشاء مكوّن Navbar
داخل المشروع، أنشئ مجلدًا للمكوّنات:
src/components/
ثم أنشئ ملفًا:
src/components/Navbar.astro
والآن ضع هذا الكود:
---
const links = [
{ name: "الرئيسية", url: "/" },
{ name: "عن المدونة", url: "/about" },
{ name: "المدونة", url: "/blog" },
];
---
<nav style="padding: 12px; background:#f3f3f3; display:flex; gap:16px;">
{links.map(link => (
<a href={link.url} style="text-decoration:none; color:#333;">
{link.name}
</a>
))}
</nav>
ماذا يحدث هنا؟
- استخدمنا Frontmatter لتعريف مصفوفة روابط
- استخدمنا
{}داخل HTML لإدراج بيانات ديناميكية - استخدمنا
.mapمثل React تمامًا - لا يوجد أي JavaScript في المتصفح—كل شيء يولّد عند البناء (Static)
3. استخدام Navbar داخل الصفحات
افتح:
src/pages/index.astro
وفي أعلى الملف، أضف الاستيراد:
---
import Navbar from "../components/Navbar.astro";
const title = "مرحباً بك في أول مشروع Astro!";
---
ثم داخل <body>:
<body>
<Navbar />
<h1>{title}</h1>
<p>
هذه هي أول صفحة نبنيها في سلسلة “بناء مدونة باستخدام Astro”.
سترى كيف يعمل كل شيء خطوة بخطوة.
</p>
<Hero title="مرحبا بكم" subtitle="هذه أول تجربة لنا مع Astro" />
</body>
الآن:
✔ يظهر الـ Navbar
✔ قابل لإعادة الاستخدام
✔ لا يحتوي على أي JavaScript في المتصفح
✔ أسرع من أي إطار JavaScript تقليدي
4. إضافة Navbar إلى صفحة About أيضًا
افتح:
src/pages/about.astro
أضف:
---
import Navbar from "../components/Navbar.astro";
---
ثم داخل <body>:
<body>
<Navbar />
<h1>عن المدونة</h1>
<p>هذه مدونة تجريبية نبنيها خطوة بخطوة باستخدام Astro.</p>
</body>
الآن لديك مكوّن يعمل في عدة صفحات.
5. فهم Frontmatter بوضوح
الكتلة بين --- هي منطقة JavaScript/TypeScript لا تظهر للمستخدم.
تستخدمها في:
- المتغيرات
- استيراد المكوّنات
- جلب البيانات (fetch)
- تمرير قيم للمكوّنات
مثال بسيط:
---
const username = "Ahmed";
---
<p>مرحباً {username}</p>
6. تمرير خصائص (Props) للمكوّن
أنشئ مكوّن جديد:
src/components/Hero.astro
اكتب:
---
const { title, subtitle } = Astro.props;
---
<section style="padding:40px; background:#eee; margin-top:20px;">
<h1>{title}</h1>
<p>{subtitle}</p>
</section>
ثم استخدمه:
<Hero title="مرحبا بكم" subtitle="هذه أول تجربة لنا مع Astro" />
7. مكوّنات Astro vs مكوّنات React – الفهم العملي الواضح
| الجانب | Astro Components | React/Vue/Svelte |
|---|---|---|
| يتم تشغيل الكود في | وقت البناء Build | المتصفح |
| هل يستخدم JS؟ | فقط عند الحاجة | دائمًا |
| السرعة | أسرع بكثير | أبطأ |
| هل يمكن استخدام React داخله؟ | نعم | — |
Astro لا يحارب الأطر… بل يدعمها.
يمكنك كتابة React داخل صفحات Astro عبر Islands Architecture، وهذا ما سنفعله لاحقًا.
8. تحدّي عملي صغير
✦ أضف رابطًا جديدًا داخل Navbar بعنوان:
“اتصل بنا” → /contact
✦ ثم أنشئ صفحة contact.astro فيها:
- Navbar
- نموذج بسيط يحتوي (الاسم – البريد – الرسالة)
الجزء الثالث — بناء نظام مدونة كامل باستخدام Markdown داخل Astro
هذا الجزء هو القلب الحقيقي للمشروع—سنحوّل موقعك إلى مدونة حقيقية تعتمد على ملفات Markdown مع ربط الصفحات تلقائيًا، تمامًا كما يفعل أي نظام تدوين احترافي.
سنبني كل شيء من الصفر ويدًا بيد داخل مشروعك الحالي الذي هيكلناه سابقًا.
ماذا سنبني اليوم؟
- إنشاء مجلد
posts/لملفات Markdown - إنشاء Content Collections (أفضل طريقة لإدارة المقالات في Astro)
- كتابة أول مقالة
- جلب المقالات في صفحة
/blog - إنشاء صفحة لكل مقالة تلقائيًا
- إضافة Layout خاص بالمقالات
- تنسيق صفحة المقال (العنوان – التاريخ – المحتوى)
هذا الجزء عملي 100%.
لنبدأ 👇
1. تفعيل Content Collections في Astro
افتح:
src/content/config.ts
إذا المجلد غير موجود → أنشئ:
src/content/config.ts
والآن ضع الكود التالي:
import { defineCollection, z } from "astro:content";
const blogCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
date: z.string(),
}),
});
export const collections = {
blog: blogCollection,
};
ماذا فعلنا؟
✔ أنشأنا مجموعة مقالات اسمها blog
✔ حدّدنا مخطط المقال (title – description – date)
✔ Astro سيتأكد من صحة البيانات تلقائيًا
2. إنشاء مجلد المقالات
قم بإنشاء المجلد:
src/content/blog/
3. كتابة أول مقالة Markdown
الخطوة 1: سوف نستخدم Content Collections لادارة المقالات :
ننقل جميع ملفات المقالات من
src/pages/blog/ إلى src/content/blog/
قبل :
src/pages/blog/
├── first-post.md ← صفحة مباشرة
├── second-post.md ← صفحة مباشرة
└── [slug].astro
بعد:
src/content/blog/
├── first-post.md ← محتوى فقط
├── second-post.md ← محتوى فقط
└── hello-world.md
الخطوة 2: الاحتفاظ بصفحة العرض الديناميكية
أبقينا فقط ملف [slug].astro في لعرض المقالات src/pages/blog/ :
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
الخطوة 3: التحقق من الصفحات الأخرى
تأكدنا أن جميع الصفحات تستخدم getCollection() بشكل صحيح:
✅ index.astro – الصفحة الرئيسية
✅ blog.astro – قائمة المقالات
✅ blog/[slug].astro – عرض المقال الواحد
النتيجة النهائية
الآن لدينا بنية واضحة ومنظمة:
src/
├── content/
│ ├── config.ts ← تعريف البنية
│ └── blog/ ← المحتوى (البيانات)
│ ├── hello-world.md
│ ├── first-post.md
│ └── second-post.md
│
└── pages/
├── index.astro ← الصفحة الرئيسية
├── blog.astro ← قائمة المقالات
└── blog/
└── [slug].astro ← عرض المقال
- الفوائد التي حصلنا عليها :
- تنظيم أفضل: فصل واضح بين المحتوى والعرض
- أمان أكبر: التحقق التلقائي من البيانات يمنع الأخطاء
- سهولة الصيانة: إضافة مقال جديد = ملف واحد في content/blog/
- مرونة: يمكننا بسهولة إضافة مجموعات محتوى أخرى (مثل content/projects/)
الآن الخطوة الثانية لكتابة المقال ، أنشئ ملفًا:
src/content/blog/hello-world.md
ثم ضع داخله:
---
title: "أول تدوينة في المدونة"
description: "مقال تجريبي باستخدام Astro Content Collections"
date: "2025-01-01"
---
## مرحبًا بك!
هذه هي أول تدوينة ننشرها ضمن مشروع بناء مدونة باستخدام Astro.
من الآن فصاعدًا، سننشئ جميع المقالات داخل هذا المجلد.
ممتاز—أنت الآن تملك أول تدوينة!
4. إنشاء Layout خاص بالمقالات
أنشئ ملفًا جديدًا:
src/layouts/BlogPost.astro
ضع فيه الكود التالي:
---
import Base from "./Base.astro";
const { title, date, description } = Astro.props;
---
<Base>
<article style="max-width: 700px; margin: 40px auto;">
<h1>{title}</h1>
<p style="color: gray; margin-bottom: 20px;"> {date}</p>
<slot />
</article>
</Base>
من الآن:
أي مقالة Markdown تستخدم هذا Layout ستحصل على تصميم جاهز وجميل.
5. إنشاء صفحة قائمة المقالات (blog)
أنشئ صفحة:
src/pages/blog.astro
ضع بداخلها :
---
import Base from "../layouts/Base.astro";
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
---
<Base>
<h1>المدونة</h1>
<p>جميع المقالات المنشورة</p>
<ul style="list-style:none; padding:0;">
{posts.map(post => (
<li style="margin-bottom:20px;">
<a href={`/blog/${post.slug}/`} style="font-size:20px;">
{post.data.title}
</a>
<p style="color:gray; font-size:14px;">{post.data.date}</p>
</li>
))}
</ul>
</Base>
الآن:
✔ صفحة “المدونة” أصبحت جاهزة
✔ تعرض جميع المقالات
✔ الروابط تعمل تلقائيًا عبر slug
6. إنشاء صفحة عرض كل مقالة (Dynamic Route)
أنشئ مجلدًا ديناميكيًا:
src/pages/blog/[slug].astro
وأضف:
---
import { getCollection, getEntryBySlug } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<BlogPost title={post.data.title} date={post.data.date}>
<Content />
</BlogPost>
الآن:
✔ أي ملف Markdown داخل /blog يولّد صفحة تلقائيًا
✔ مع عنوان + تاريخ + محتوى
✔ تصميم ثابت من BlogPost Layout
7. اختبر الآن!
افتح المتصفح:
قائمة المقالات:
http://localhost:4321/blog
صفحة المقالة:
http://localhost:4321/blog/hello-world
يجب أن ترى:
- عنوان المقال
- التاريخ
- المحتوى Markdown
- تنسيق جميل
- Navbar من Layout الرئيسي
الجزء الخامس – بناء صفحة أرشيف + البحث داخل المقالات + الفلاتر (Tags) في Astro
هذا الجزء بنهايته سيكون لديك:
✔ صفحة أرشيف Archive لعرض كل المقالات
✔ محرك بحث لحظي Instant Search يعمل بتقنية الجزر (Islands)
✔ فلاتر Tags لتصفية المقالات
✔ ترتيب المقالات حسب التاريخ
✔ تجربة متقدمة شبيهة بمدونات احترافية
هدف الدرس
- إنشاء صفحة
/blog(أرشيف المقالات) - جلب جميع المقالات من Content Collection
- عرضها بطريقة مرتّبة
- بناء مكوّن React للبحث
- دعم فلترة المقالات حسب Tags
1. إضافة حقل Tags داخل مقالات Markdown
انشئ ملف باسم first-post.md في المجلد التالي :
src/content/blog/
وأضف الحقول:
---
title: "عنوان المقال"
date: "2025-01-10"
tags: ["Astro", "JavaScript", "Frontend"]
---
مثال كامل:
---
title: "أول مقال تجريبي"
date: "2025-01-10"
tags: ["Astro", "Tutorial"]
---
محتوى المقال…
2. تعديل Schema لدعم Tags
افتح:
src/content/config.ts
واجعله:
import { z, defineCollection } from "astro:content";
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
date: z.string(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = {
blog: blogCollection,
};
3. إنشاء صفحة أرشيف Blog
أنشئ الملف:
src/pages/blog/index.astro
وأضف:
---
import Navbar from "../components/Navbar.astro";
import SearchIsland from "../components/react/SearchIsland.jsx";
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
// ترتيب حسب التاريخ
const sorted = posts.sort(
(a, b) => new Date(b.data.date) - new Date(a.data.date)
);
---
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<title>المدونة</title>
</head>
<body>
<Navbar />
<h1>أرشيف المقالات</h1>
<SearchIsland posts={sorted} client:load />
</body>
</html>
لاحظ:
SearchIslandسيكون مكوّن React مسؤول عن البحث والفلاتر- نمرر له جميع المقالات عبر props
4. بناء مكوّن البحث SearchIsland
اولا تحتاج إلى تثبيت حزمة @astrojs/react إذا لم تكن مثبتة. قم بتشغيل هذا الأمر في Terminal:
npm install @astrojs/react react react-dom
أنشئ المجلد:
src/components/react/SearchIsland.jsx
ضع:
import { useState } from "react";
export default function SearchIsland({ posts }) {
const [query, setQuery] = useState("");
const [activeTag, setActiveTag] = useState(null);
const allTags = [
...new Set(posts.flatMap(p => p.data.tags || []))
];
const filtered = posts.filter(post => {
const matchQuery =
post.data.title.toLowerCase().includes(query.toLowerCase());
const matchTag = activeTag ? (post.data.tags || []).includes(activeTag) : true;
return matchQuery && matchTag;
});
return (
<div style={{ marginTop: "20px" }}>
{/* صندوق البحث */}
<input
type="text"
placeholder="ابحث عن مقال..."
value={query}
onChange={e => setQuery(e.target.value)}
style={{
width: "100%",
padding: "10px",
marginBottom: "20px",
fontSize: "16px"
}}
/>
{/* الفلاتر */}
<div style={{ marginBottom: "20px", display: "flex", gap: "10px", flexWrap: "wrap" }}>
{allTags.map(tag => (
<button
key={tag}
onClick={() => setActiveTag(tag === activeTag ? null : tag)}
style={{
padding: "5px 15px",
borderRadius: "20px",
border: "1px solid #555",
cursor: "pointer",
background: tag === activeTag ? "#333" : "transparent",
color: tag === activeTag ? "#fff" : "#333"
}}
>
{tag}
</button>
))}
</div>
{/* عرض النتائج */}
<ul style={{ listStyle: "none", padding: 0 }}>
{filtered.map(post => (
<li key={post.slug}
style={{
marginBottom: "20px",
padding: "20px",
border: "1px solid #ddd",
borderRadius: "8px"
}}
>
<a
href={`/blog/${post.slug}`}
style={{ fontSize: "20px", textDecoration: "none", color: "#000" }}
>
{post.data.title}
</a>
<p style={{ color: "#666" }}>{post.data.date}</p>
<div style={{ marginTop: "8px", display: "flex", gap: "8px" }}>
{(post.data.tags || []).map(tag => (
<span
key={tag}
style={{
background: "#eee",
padding: "4px 10px",
borderRadius: "12px",
fontSize: "14px"
}}
>
#{tag}
</span>
))}
</div>
</li>
))}
</ul>
{filtered.length === 0 && (
<p style={{ color: "#888" }}>لا توجد نتائج مطابقة.</p>
)}
</div>
);
}
ماذا يفعل هذا الكومبوننت؟
✔ بحث لحظي داخل العناوين
✔ فلترة حسب Tags
✔ إعادة بناء قائمة النتائج مباشرة
✔ لا يحمل أي JS في الصفحة إلا داخل المكوّن فقط (جزيرة!)
ماذا أضفنا للمشروع؟
| الميزة | كيف تعمل؟ |
|---|---|
| أرشيف كامل | عرض جميع المقالات من Content Collection |
| ترتيب حسب التاريخ | باستخدام sort داخل Astro |
| بحث تفاعلي | React + client:load |
| فلاتر Tags | استنتاج جميع الوسوم من المقالات |
| Islands Architecture | تحميل JS فقط داخل SearchIsland |
ما الذي أنجزناه في هذا المقال؟
حتى هذه المرحلة من رحلتنا مع Astro، أصبح بين يديك مشروع مدونة حقيقي مكتمل الأساسيات، وليس مجرد أمثلة نظرية. لقد قمنا معًا بـ:
- ⭐ إنشاء بنية مشروع كاملة تعمل باللغة العربية وتدعم اتجاه RTL.
- ⭐ بناء صفحات رئيسية مثل: الصفحة الرئيسية، صفحة “عن المدونة”، وصفحة التواصل.
- ⭐ إنشاء مكونات UI قابلة لإعادة الاستخدام مثل Navbar و Hero.
- ⭐ تأسيس نظام تدوين فعلي باستخدام ملفات Markdown داخل الـ Content Collections.
- ⭐ بناء نظام صفحات ديناميكية عبر
[slug].astroلعرض أي مقال جديد بشكل تلقائي. - ⭐ إنشاء صفحة أرشيف /blog تستعرض جميع المقالات مع العنوان والتاريخ والوصف المختصر.
- ⭐ الانتقال من النظرية إلى مشروع عملي متكامل يمكنك تشغيله وتعديله وتوسيعه.
لقد أصبح لديك الآن نواة مدونة كاملة يمكنك تحويلها إلى مجلة تقنية، مدونة شخصية، منصة تعليمية، أو حتى موقع محتوى احترافي

