Next.js

مشروع Next.js متكامل — مدونة احترافية 2026

📅 2026-11-20⏱ 13 دقائق قراءة
🎉 **مبروك! وصلت إلى المقال الأخير في مسار Next.js!** لقد تعلمت كل الأساسيات: App Router، Server Components، Data Fetching، API Routes، Styling، Authentication، والنشر. الآن حان الوقت لتطبيق كل هذه المفاهيم في **مشروع حقيقي متكامل**. في هذا الدليل العملي، سنبني **مدونة احترافية كاملة** — وهي المشروع الذي يبنيه المطورون المحترفون فعلاً. ## ما سنبنيه **مدونة احترافية** تحتوي على: - ✅ **صفحة رئيسية** مع أحدث المقالات. - ✅ **صفحة المقالات** مع قائمة كاملة. - ✅ **صفحة المقال الفردي** مع محتوى Markdown. - ✅ **تصنيفات (Categories)**. - ✅ **بحث (Search)**. - ✅ **جدول محتويات (TOC)**. - ✅ **مشاركة على وسائل التواصل**. - ✅ **SEO محسّن** (Metadata, Open Graph, Sitemap). - ✅ **تصميم متجاوب**. - ✅ **وضع داكن**. - ✅ **نشرة بريدية**. - ✅ **مقالات ذات صلة**. ## هيكل المشروع ``` next-blog/ ├── app/ │ ├── layout.tsx # التخطيط العام │ ├── page.tsx # الصفحة الرئيسية │ ├── globals.css │ ├── blog/ │ │ ├── page.tsx # قائمة المقالات │ │ └── [slug]/ │ │ └── page.tsx # مقال فردي │ ├── categories/ │ │ └── [category]/ │ │ └── page.tsx # مقالات التصنيف │ ├── about/ │ │ └── page.tsx │ └── api/ │ └── subscribe/ │ └── route.ts # API النشرة ├── components/ │ ├── Header.tsx │ ├── Footer.tsx │ ├── PostCard.tsx │ ├── SearchBar.tsx │ ├── TableOfContents.tsx │ ├── ShareButtons.tsx │ └── NewsletterForm.tsx ├── lib/ │ ├── posts.ts # دوال قراءة المقالات │ └── utils.ts ├── content/ │ └── posts/ # ملفات Markdown │ ├── first-post.md │ └── second-post.md ├── public/ │ └── images/ ├── next.config.ts ├── tailwind.config.ts ├── package.json └── tsconfig.json ``` ## الخطوة 1: إنشاء المشروع ```bash npx create-next-app@latest next-blog --typescript --tailwind --eslint --app --no-src-dir --import-alias "@/*" cd next-blog ``` ## الخطوة 2: تثبيت المكتبات ```bash npm install gray-matter remark remark-html npm install -D @tailwindcss/typography ``` **المكتبات:** <table> <thead> <tr> <th>المكتبة</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>gray-matter</code></td> <td>قراءة Frontmatter من Markdown</td> </tr> <tr> <td><code>remark</code></td> <td>تحويل Markdown إلى HTML</td> </tr> <tr> <td><code>remark-html</code></td> <td>Plugin لـ remark</td> </tr> <tr> <td><code>@tailwindcss/typography</code></td> <td>أنماط للنصوص</td> </tr> </tbody> </table> ## الخطوة 3: `tailwind.config.ts` ```typescript import type { Config } from "tailwindcss"; const config: Config = { content: [ "./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}", ], darkMode: "class", theme: { extend: { fontFamily: { cairo: ["var(--font-cairo)", "sans-serif"], }, }, }, plugins: [require("@tailwindcss/typography")], }; export default config; ``` ## الخطوة 4: `app/globals.css` ```css @tailwind base; @tailwind components; @tailwind utilities; @layer base { body { @apply bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100; } } @layer components { .container-custom { @apply max-w-4xl mx-auto px-4; } } /* صناديق الكود */ .prose pre { @apply bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto; } .prose code { @apply bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-sm; } .prose pre code { @apply bg-transparent p-0; } ``` ## الخطوة 5: `lib/posts.ts` ```typescript import fs from "fs"; import path from "path"; import matter from "gray-matter"; import { remark } from "remark"; import html from "remark-html"; const postsDirectory = path.join(process.cwd(), "content/posts"); export interface Post { slug: string; title: string; date: string; excerpt: string; category: string; tags: string[]; coverImage?: string; content: string; } // جلب كل المقالات export function getAllPosts(): Post[] { const fileNames = fs.readdirSync(postsDirectory); const posts = fileNames .filter((name) => name.endsWith(".md")) .map((name) => { const slug = name.replace(/\.md$/, ""); const fullPath = path.join(postsDirectory, name); const fileContents = fs.readFileSync(fullPath, "utf8"); const { data, content } = matter(fileContents); return { slug, title: data.title, date: data.date, excerpt: data.excerpt || content.slice(0, 150) + "...", category: data.category || "غير مصنف", tags: data.tags || [], coverImage: data.coverImage, content, }; }) .sort((a, b) => (a.date < b.date ? 1 : -1)); return posts; } // جلب مقال واحد export function getPostBySlug(slug: string): Post | null { try { const fullPath = path.join(postsDirectory, `${slug}.md`); const fileContents = fs.readFileSync(fullPath, "utf8"); const { data, content } = matter(fileContents); return { slug, title: data.title, date: data.date, excerpt: data.excerpt || content.slice(0, 150) + "...", category: data.category || "غير مصنف", tags: data.tags || [], coverImage: data.coverImage, content, }; } catch { return null; } } // تحويل Markdown إلى HTML export async function markdownToHtml(markdown: string): Promise<string> { const result = await remark().use(html).process(markdown); return result.toString(); } // جلب مقالات التصنيف export function getPostsByCategory(category: string): Post[] { return getAllPosts().filter((post) => post.category === category); } // جلب كل التصنيفات export function getAllCategories(): string[] { const posts = getAllPosts(); return [...new Set(posts.map((post) => post.category))]; } // جلب المقالات ذات الصلة export function getRelatedPosts(slug: string, limit = 3): Post[] { const post = getPostBySlug(slug); if (!post) return []; return getAllPosts() .filter((p) => p.slug !== slug && p.category === post.category) .slice(0, limit); } // وقت القراءة export function getReadingTime(content: string): number { const words = content.trim().split(/\s+/).length; return Math.max(1, Math.ceil(words / 200)); } // تنسيق التاريخ export function formatDate(date: string): string { return new Date(date).toLocaleDateString("ar-EG", { year: "numeric", month: "long", day: "numeric", }); } ``` ## الخطوة 6: مقال تجريبي **`content/posts/first-post.md`:** ```markdown --- title: "مرحباً بك في مدونتي" date: "2026-11-20" excerpt: "أول مقال في مدونتي الجديدة بـ Next.js" category: "تقنية" tags: ["Next.js", "React", "برمجة"] coverImage: "https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=1200" --- ## مقدمة مرحباً بك في مدونتي الجديدة! هذه مدونة مبنية بـ **Next.js** و **Tailwind CSS**. ## لماذا Next.js؟ Next.js هو إطار عمل رائع لبناء تطبيقات React. يتميز بـ: - **App Router** — نظام توجيه حديث - **Server Components** — أداء عالٍ - **SEO ممتاز** — جاهز لمحركات البحث - **سهل النشر** — على Vercel ## مثال على كود ```javascript function greet(name) { return `مرحباً ${name}!`; } ``` ## الخلاصة هذا مشروع رائع لتعلّم Next.js. استمتع! ``` **`content/posts/second-post.md`:** ```markdown --- title: "كيف تتعلم البرمجة في 2026" date: "2026-11-19" excerpt: "دليل شامل لتعلم البرمجة من الصفر" category: "تعلم" tags: ["برمجة", "تعلم"] coverImage: "https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=1200" --- ## ابدأ من HTML HTML هو أساس كل موقع. ## ثم CSS CSS هو ما يجعل الموقع جميلاً. ## بعدها JavaScript JavaScript هو ما يجعل الموقع تفاعلياً. ``` ## الخطوة 7: `app/layout.tsx` ```tsx import type { Metadata } from "next"; import { Cairo } from "next/font/google"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; import "./globals.css"; const cairo = Cairo({ subsets: ["arabic", "latin"], weight: ["400", "600", "700", "900"], variable: "--font-cairo", }); export const metadata: Metadata = { title: { default: "مدونتي | NahdaAI", template: "%s | مدونتي", }, description: "مدونتي الشخصية — مقالات عن البرمجة والتقنية", metadataBase: new URL("https://myblog.com"), openGraph: { type: "website", locale: "ar_AR", siteName: "مدونتي", }, }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ar" dir="rtl" className={cairo.variable}> <body className="font-cairo"> <Header /> <main className="min-h-screen">{children}</main> <Footer /> </body> </html> ); } ``` ## الخطوة 8: `components/Header.tsx` ```tsx import Link from "next/link"; import { getAllCategories } from "@/lib/posts"; export default function Header() { const categories = getAllCategories(); return ( <header className="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50"> <div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between"> <Link href="/" className="text-2xl font-bold text-blue-600"> 📝 مدونتي </Link> <nav className="hidden md:flex gap-6"> <Link href="/" className="text-gray-700 dark:text-gray-300 hover:text-blue-600"> الرئيسية </Link> <Link href="/blog" className="text-gray-700 dark:text-gray-300 hover:text-blue-600"> المقالات </Link> {categories.slice(0, 3).map((cat) => ( <Link key={cat} href={`/categories/${encodeURIComponent(cat)}`} className="text-gray-700 dark:text-gray-300 hover:text-blue-600" > {cat} </Link> ))} <Link href="/about" className="text-gray-700 dark:text-gray-300 hover:text-blue-600"> من نحن </Link> </nav> </div> </header> ); } ``` ## الخطوة 9: `components/Footer.tsx` ```tsx export default function Footer() { return ( <footer className="bg-gray-900 text-white py-8 mt-16"> <div className="max-w-6xl mx-auto px-4 text-center"> <p className="mb-4">© 2026 مدونتي — جميع الحقوق محفوظة</p> <div className="flex justify-center gap-4"> <a href="https://twitter.com" className="hover:text-blue-400 transition"> 🐦 تويتر </a> <a href="https://github.com" className="hover:text-blue-400 transition"> 💻 GitHub </a> </div> </div> </footer> ); } ``` ## الخطوة 10: `components/PostCard.tsx` ```tsx import Link from "next/link"; import Image from "next/image"; import { Post, formatDate, getReadingTime } from "@/lib/posts"; export default function PostCard({ post }: { post: Post }) { return ( <article className="bg-white dark:bg-gray-800 rounded-2xl overflow-hidden shadow-sm hover:shadow-lg transition group"> {post.coverImage && ( <Link href={`/blog/${post.slug}`}> <div className="relative h-48 overflow-hidden"> <Image src={post.coverImage} alt={post.title} fill className="object-cover group-hover:scale-105 transition duration-300" /> </div> </Link> )} <div className="p-6"> <div className="flex items-center gap-3 mb-3"> <Link href={`/categories/${encodeURIComponent(post.category)}`} className="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 text-xs px-3 py-1 rounded-full" > {post.category} </Link> <span className="text-gray-500 text-xs"> ⏱ {getReadingTime(post.content)} دقائق </span> </div> <Link href={`/blog/${post.slug}`}> <h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2 hover:text-blue-600 transition"> {post.title} </h3> </Link> <p className="text-gray-600 dark:text-gray-400 text-sm mb-4 line-clamp-2"> {post.excerpt} </p> <div className="flex items-center justify-between text-xs text-gray-500"> <span>{formatDate(post.date)}</span> <Link href={`/blog/${post.slug}`} className="text-blue-600 hover:underline font-medium" > اقرأ المزيد ← </Link> </div> </div> </article> ); } ``` ## الخطوة 11: `app/page.tsx` ```tsx import Link from "next/link"; import { getAllPosts } from "@/lib/posts"; import PostCard from "@/components/PostCard"; export default function HomePage() { const posts = getAllPosts(); const featuredPosts = posts.slice(0, 3); return ( <> {/* Hero */} <section className="bg-gradient-to-br from-blue-600 to-purple-700 text-white py-20"> <div className="max-w-4xl mx-auto px-4 text-center"> <h1 className="text-5xl font-bold mb-4">مدونتي</h1> <p className="text-xl opacity-90 mb-8"> مقالات عن البرمجة والتقنية — بالعربية </p> <Link href="/blog" className="inline-block bg-white text-blue-600 px-8 py-3 rounded-lg font-bold hover:bg-gray-100 transition" > تصفح المقالات → </Link> </div> </section> {/* المقالات المميزة */} <section className="max-w-6xl mx-auto px-4 py-16"> <h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-8"> 📚 أحدث المقالات </h2> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> {featuredPosts.map((post) => ( <PostCard key={post.slug} post={post} /> ))} </div> <div className="text-center mt-12"> <Link href="/blog" className="inline-block bg-blue-600 text-white px-8 py-3 rounded-lg font-bold hover:bg-blue-700 transition" > عرض جميع المقالات ← </Link> </div> </section> </> ); } ``` ## الخطوة 12: `app/blog/page.tsx` ```tsx import { getAllPosts } from "@/lib/posts"; import PostCard from "@/components/PostCard"; export const metadata = { title: "المقالات", description: "جميع مقالات المدونة", }; export default function BlogPage() { const posts = getAllPosts(); return ( <div className="max-w-6xl mx-auto px-4 py-12"> <h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-8"> 📚 جميع المقالات </h1> <p className="text-gray-600 dark:text-gray-400 mb-8"> {posts.length} مقال </p> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> {posts.map((post) => ( <PostCard key={post.slug} post={post} /> ))} </div> </div> ); } ``` ## الخطوة 13: `app/blog/[slug]/page.tsx` ```tsx import { notFound } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; import { getPostBySlug, getAllPosts, markdownToHtml, formatDate, getReadingTime, getRelatedPosts, } from "@/lib/posts"; import PostCard from "@/components/PostCard"; import ShareButtons from "@/components/ShareButtons"; export async function generateStaticParams() { const posts = getAllPosts(); return posts.map((post) => ({ slug: post.slug })); } export async function generateMetadata({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; const post = getPostBySlug(slug); if (!post) return {}; return { title: post.title, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, type: "article", images: post.coverImage ? [post.coverImage] : [], }, }; } export default async function PostPage({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; const post = getPostBySlug(slug); if (!post) notFound(); const htmlContent = await markdownToHtml(post.content); const relatedPosts = getRelatedPosts(slug); return ( <article className="max-w-4xl mx-auto px-4 py-12"> <Link href="/blog" className="text-blue-600 hover:underline mb-6 inline-block" > ← العودة للمقالات </Link> {post.coverImage && ( <div className="relative h-96 rounded-2xl overflow-hidden mb-8"> <Image src={post.coverImage} alt={post.title} fill className="object-cover" priority /> </div> )} <div className="flex items-center gap-3 mb-4"> <Link href={`/categories/${encodeURIComponent(post.category)}`} className="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 text-sm px-3 py-1 rounded-full" > {post.category} </Link> <span className="text-gray-500 text-sm"> 📅 {formatDate(post.date)} </span> <span className="text-gray-500 text-sm"> ⏱ {getReadingTime(post.content)} دقائق </span> </div> <h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4"> {post.title} </h1> <p className="text-xl text-gray-600 dark:text-gray-400 mb-8"> {post.excerpt} </p> <ShareButtons title={post.title} url={`https://myblog.com/blog/${post.slug}`} /> <div className="prose prose-lg dark:prose-invert max-w-none mt-8" dangerouslySetInnerHTML={{ __html: htmlContent }} /> {post.tags.length > 0 && ( <div className="flex gap-2 mt-8"> {post.tags.map((tag) => ( <span key={tag} className="bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm px-3 py-1 rounded-full" > #{tag} </span> ))} </div> )} {relatedPosts.length > 0 && ( <section className="mt-16 pt-8 border-t"> <h2 className="text-2xl font-bold mb-6">📚 مقالات ذات صلة</h2> <div className="grid md:grid-cols-3 gap-6"> {relatedPosts.map((p) => ( <PostCard key={p.slug} post={p} /> ))} </div> </section> )} </article> ); } ``` ## الخطوة 14: `components/ShareButtons.tsx` ```tsx "use client"; interface ShareButtonsProps { title: string; url: string; } export default function ShareButtons({ title, url }: ShareButtonsProps) { const encodedTitle = encodeURIComponent(title); const encodedUrl = encodeURIComponent(url); return ( <div className="flex gap-2"> <a href={`https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`} target="_blank" rel="noopener noreferrer" className="bg-black text-white px-4 py-2 rounded-lg text-sm hover:bg-gray-800 transition" > 🐦 تويتر </a> <a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`} target="_blank" rel="noopener noreferrer" className="bg-blue-700 text-white px-4 py-2 rounded-lg text-sm hover:bg-blue-800 transition" > 💼 LinkedIn </a> <a href={`https://wa.me/?text=${encodedTitle} - ${encodedUrl}`} target="_blank" rel="noopener noreferrer" className="bg-green-600 text-white px-4 py-2 rounded-lg text-sm hover:bg-green-700 transition" > 📱 WhatsApp </a> </div> ); } ``` ## الخطوة 15: `app/categories/[category]/page.tsx` ```tsx import { notFound } from "next/navigation"; import { getAllCategories, getPostsByCategory } from "@/lib/posts"; import PostCard from "@/components/PostCard"; export function generateStaticParams() { const categories = getAllCategories(); return categories.map((category) => ({ category: encodeURIComponent(category), })); } export default async function CategoryPage({ params, }: { params: Promise<{ category: string }>; }) { const { category } = await params; const decoded = decodeURIComponent(category); const posts = getPostsByCategory(decoded); if (posts.length === 0) notFound(); return ( <div className="max-w-6xl mx-auto px-4 py-12"> <h1 className="text-4xl font-bold mb-4">📁 تصنيف: {decoded}</h1> <p className="text-gray-600 dark:text-gray-400 mb-8"> {posts.length} مقال </p> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> {posts.map((post) => ( <PostCard key={post.slug} post={post} /> ))} </div> </div> ); } ``` ## الخطوة 16: `app/about/page.tsx` ```tsx export const metadata = { title: "من نحن", }; export default function AboutPage() { return ( <div className="max-w-2xl mx-auto px-4 py-16"> <h1 className="text-4xl font-bold mb-6">من نحن</h1> <p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4"> مرحباً بك في مدونتي! أنا مطور ويب، أشارك هنا مقالاتي عن البرمجة والتقنية. </p> <p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed"> تهدف هذه المدونة إلى نشر المعرفة التقنية بالعربية. </p> </div> ); } ``` ## الخطوة 17: `app/sitemap.ts` ```typescript import { getAllPosts } from "@/lib/posts"; import { MetadataRoute } from "next"; export default function sitemap(): MetadataRoute.Sitemap { const posts = getAllPosts(); const baseUrl = "https://myblog.com"; const postUrls = posts.map((post) => ({ url: `${baseUrl}/blog/${post.slug}`, lastModified: new Date(post.date), changeFrequency: "monthly" as const, priority: 0.8, })); return [ { url: baseUrl, priority: 1 }, { url: `${baseUrl}/blog`, priority: 0.9 }, { url: `${baseUrl}/about`, priority: 0.5 }, ...postUrls, ]; } ``` ## الخطوة 18: تشغيل المشروع ```bash npm run dev ``` افتح: `http://localhost:3000` **🎉 مبروك! لديك مدونة احترافية كاملة!** ## تمارين إضافية ### تمرين 1: بحث أضف ميزة البحث في المقالات. **الحل:** ```tsx "use client"; import { useState } from "react"; export default function SearchBar({ posts }: { posts: any[] }) { const [query, setQuery] = useState(""); const filtered = posts.filter((p) => p.title.toLowerCase().includes(query.toLowerCase()) ); return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="ابحث..." className="w-full px-4 py-3 border rounded-lg" /> <ul> {filtered.map((p) => ( <li key={p.slug}>{p.title}</li> ))} </ul> </div> ); } ``` ### تمرين 2: جدول المحتويات أضف TOC (Table of Contents). ### تمرين 3: الوضع الداكن أضف زر تبديل الوضع الداكن. ### تمرين 4: نشرة بريدية أضف نموذج الاشتراك. ### تمرين 5: تعليقات أضف نظام تعليقات (Disqus أو Giscus). ### تمرين 6: RSS Feed أضف `/feed.xml` للمشتركين. ### تمرين 7: البحث المتقدم أضف بحثاً في المحتوى أيضاً. ### تمرين 8: النشر انشر المدونة على Vercel. ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>إنشاء المشروع</td> <td>⬜</td> </tr> <tr> <td>إعداد Markdown</td> <td>⬜</td> </tr> <tr> <td>قراءة المقالات</td> <td>⬜</td> </tr> <tr> <td>الصفحة الرئيسية</td> <td>⬜</td> </tr> <tr> <td>قائمة المقالات</td> <td>⬜</td> </tr> <tr> <td>صفحة المقال الفردي</td> <td>⬜</td> </tr> <tr> <td>SEO و Sitemap</td> <td>⬜</td> </tr> <tr> <td>النشر</td> <td>⬜</td> </tr> </tbody> </table> ## 🎉 مبروك! أكملت مسار Next.js! لقد وصلت إلى نهاية المسار. أنت الآن تعرف: - ✅ **أساسيات Next.js:** App Router، Pages، Layouts. - ✅ **Server Components:** Server vs Client. - ✅ **Data Fetching:** fetch, cache, Server Actions. - ✅ **API Routes.** - ✅ **Styling:** Tailwind CSS. - ✅ **Authentication:** NextAuth. - ✅ **Deployment:** Vercel, Firebase. - ✅ **مشروع متكامل.** ## ماذا بعد Next.js؟ الآن أنت جاهز لـ: 1. **بناء مشاريع حقيقية** — ابدأ ببناء مشاريعك. 2. **GraphQL** — بديل REST. 3. **tRPC** — Type-safe APIs. 4. **Prisma** — ORM حديث. 5. **Stripe** — الدفع الإلكتروني. 6. **بناء معرض أعمالك.** ## الخلاصة في هذا المشروع، بنيت: - ✅ **مدونة احترافية كاملة.** - ✅ **Markdown للمحتوى.** - ✅ **SEO محسّن.** - ✅ **تصنيفات وبحث.** - ✅ **مقالات ذات صلة.** - ✅ **مشاركة اجتماعية.** - ✅ **نشر سهل.** **هذا المشروع هو أساس كل تطبيقات Next.js الاحترافية.** احتفظ بالكود، وطور فيه بنفسك!