Loading blog posts...
Loading blog posts...
جاري التحميل...
لقد أحدث Next.js ثورة في طريقة بناء تطبيقات React، ومع إصدار Next.js 15 (وأحدث إصدار 15.5)، يصل الإطار إلى مستويات جديدة من الأداء وتجربة المطور والوظائف. سواء كنت تبني مدونة بسيطة أو تطبيق مؤسسي معقد، يوفر Next.js 15 كل ما تحتاجه جاهزاً.
في هذا الدليل الشامل، سنستكشف كل شيء من التثبيت إلى الميزات المتقدمة، بما في ذلك كيفية الاستفادة من عرض Markdown لمنشورات المدونة مثل هذا المنشور!
تم إصدار Next.js 15 في أكتوبر 2024 وتم تحديثه باستمرار خلال عام 2025، ويجلب تحسينات رائدة تجعله الإصدار الأقوى حتى الآن.
قدم Next.js 15.1 دعماً مستقراً لـ React 19، مما يجلب أحدث ميزات React إلى تطبيقاتك:
jsx// ميزات React 19 تعمل الآن بسلاسة في Next.js 15 import { use } from 'react'; export default function UserProfile({ userPromise }) { // خطاف 'use' الجديد للتعامل مع الوعود const user = use(userPromise); return ( <div className="profile"> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); }
يقدم Turbopack، الخلف المبني على Rust لـ Webpack، تحسينات أداء مذهلة:
bash# تفعيل Turbopack للتطوير next dev --turbo # مع Turbopack، ستشاهد سرعات مثل: # ✓ جاهز خلال 1.2 ثانية (كان 5 ثواني سابقاً) # ✓ Fast Refresh خلال 50 ميلي ثانية (كان ثانية واحدة سابقاً)
نتائج القياس (على تطبيق Vercel):
Webpack التقليدي:
├── الترجمة الأولية: 5.2 ثانية
├── Fast Refresh: 980 ميلي ثانية
└── وقت الحزم: 12.4 ثانية
Turbopack (أسرع بنسبة 75-95%):
├── الترجمة الأولية: 1.3 ثانية
├── Fast Refresh: 49 ميلي ثانية
└── وقت الحزم: 3.1 ثانية
يوسع مكون <Form> الجديد نماذج HTML بقدرات Next.js الفائقة:
tsximport Form from 'next/form'; export default function SearchPage() { return ( <Form action="/search"> {/* الجلب المسبق للتنقل الفوري */} <input name="query" placeholder="ابحث..." /> <button type="submit">بحث</button> </Form> ); }
الميزات:
يقدم Next.js 15 إصدارات غير متزامنة من واجهات برمجة التطبيقات الديناميكية لجلب بيانات أفضل:
typescript// قبل (Next.js 14) import { cookies } from 'next/headers'; export default function Page() { const cookieStore = cookies(); const theme = cookieStore.get('theme'); return <div>المظهر: {theme}</div>; } // بعد (Next.js 15) import { cookies } from 'next/headers'; export default async function Page() { const cookieStore = await cookies(); const theme = cookieStore.get('theme'); return <div>المظهر: {theme}</div>; }
واجهات البرمجة غير المتزامنة تشمل الآن:
cookies() - قراءة ملفات تعريف الارتباط الخاصة بالطلبheaders() - الوصول إلى رؤوس الطلبparams - معاملات المسار الديناميكيsearchParams - معاملات البحث في URLيلتقط خطاف onRequestError الجديد الأخطاء ويرسلها إلى مزود المراقبة الخاص بك:
typescript// instrumentation.ts export async function onRequestError( err: Error, request: Request, context: { routerKind: 'Pages Router' | 'App Router'; routePath: string; routeType: 'render' | 'route' | 'action' | 'middleware'; } ) { // إرسال إلى خدمة تتبع الأخطاء await fetch('https://your-error-tracker.com/api/errors', { method: 'POST', body: JSON.stringify({ message: err.message, stack: err.stack, url: request.url, route: context.routePath, type: context.routeType, timestamp: new Date().toISOString(), }), }); }
يقدم Next.js 15.5 المسارات المكتوبة المستقرة لسلامة النوع في وقت الترجمة:
typescript// next.config.ts const config: NextConfig = { experimental: { typedRoutes: true, // أصبح مستقراً الآن! }, }; export default config;
tsximport Link from 'next/link'; export default function Navigation() { return ( <nav> {/* ✅ TypeScript يعرف أن هذا المسار موجود */} <Link href="/blog/nextjs-15">دليل Next.js</Link> {/* ❌ خطأ TypeScript: المسار غير موجود */} <Link href="/non-existent-route">رابط معطوب</Link> {/* ✅ مسارات ديناميكية آمنة من حيث النوع */} <Link href={{ pathname: '/blog/[slug]', params: { slug: 'nextjs-15' } }} > مسار ديناميكي </Link> </nav> ); }
إنشاء مشروع Next.js 15 جديد أمر بسيط:
bash# إنشاء تطبيق Next.js جديد npx create-next-app@latest my-nextjs-app # ستُسأل عن: # ✔ هل تريد استخدام TypeScript؟ نعم # ✔ هل تريد استخدام ESLint؟ نعم # ✔ هل تريد استخدام Tailwind CSS؟ نعم # ✔ هل تريد استخدام مجلد `src/`؟ نعم # ✔ هل تريد استخدام App Router؟ نعم # ✔ هل تريد استخدام Turbopack لـ `next dev`؟ نعم cd my-nextjs-app npm run dev
إذا كان لديك مشروع موجود:
bash# تحديث Next.js npm install next@latest react@latest react-dom@latest # التحقق من التغييرات الكبيرة npm run build # تفعيل Turbopack # تحديث package.json: { "scripts": { "dev": "next dev --turbo" } }
يبدو مشروع Next.js 15 النموذجي كالتالي:
my-nextjs-app/
├── src/
│ ├── app/ # App Router (موصى به)
│ │ ├── layout.tsx # التخطيط الجذري
│ │ ├── page.tsx # الصفحة الرئيسية
│ │ ├── globals.css # الأنماط العامة
│ │ ├── blog/
│ │ │ ├── page.tsx # قائمة المدونة
│ │ │ └── [slug]/
│ │ │ └── page.tsx # منشور المدونة
│ │ └── api/
│ │ └── hello/
│ │ └── route.ts # نقطة نهاية API
│ ├── components/ # مكونات قابلة لإعادة الاستخدام
│ └── lib/ # دوال مساعدة
├── public/ # الملفات الثابتة
├── content/ # ملفات Markdown
│ └── blog/
│ └── my-post.md
├── next.config.ts # إعدادات Next.js
├── tsconfig.json # إعدادات TypeScript
└── package.json
إحدى ميزات Next.js 15 القوية هي عرض Markdown السلس. إليك كيفية بناء مدونة مثل التي تقرأها!
bashnpm install gray-matter remark remark-html npm install react-syntax-highlighter @types/react-syntax-highlighter npm install reading-time
أنشئ ملفات markdown في content/blog/:
markdown--- title: "منشور مدونتي الأول" excerpt: "تعلم كيفية بناء مدونة مع Next.js 15" category: "tutorial" date: "2025-11-22" readTime: "قراءة 5 دقائق" tags: ["Next.js", "React", "Markdown"] author: "اسمك" --- # منشور مدونتي الأول هذا محتوى منشور مدونتي المكتوب بـ **Markdown**! ## مثال على الكود ```javascript console.log('مرحباً، Next.js 15!');
### الخطوة 3: إنشاء محلل Markdown
```typescript
// src/lib/markdown.ts
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/blog');
export interface BlogPost {
slug: string;
title: string;
excerpt: string;
category: string;
date: string;
readTime: string;
tags: string[];
author: string;
content: string;
}
export async function getPost(slug: string): Promise<BlogPost> {
const fullPath = path.join(postsDirectory, `${slug}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// تحليل البيانات الوصفية في Markdown
const { data, content } = matter(fileContents);
// تحويل markdown إلى HTML
const processedContent = await remark()
.use(html)
.process(content);
return {
slug,
content: processedContent.toString(),
...data,
} as BlogPost;
}
export function getAllPosts(): Array<Omit<BlogPost, 'content'>> {
const fileNames = fs.readdirSync(postsDirectory);
const posts = fileNames
.filter(fileName => fileName.endsWith('.md'))
.map(fileName => {
const slug = fileName.replace(/\.md$/, '');
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
return {
slug,
...data,
} as Omit<BlogPost, 'content'>;
})
.sort((a, b) => (a.date > b.date ? -1 : 1));
return posts;
}
tsx// src/app/blog/page.tsx import Link from 'next/link'; import { getAllPosts } from '@/lib/markdown'; export default async function BlogPage() { const posts = getAllPosts(); return ( <div className="container mx-auto px-4 py-16"> <h1 className="text-4xl font-bold mb-8">المدونة</h1> <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3"> {posts.map((post) => ( <Link key={post.slug} href={`/blog/${post.slug}`} className="group block p-6 bg-white rounded-lg shadow-lg hover:shadow-xl transition-shadow" > <div className="flex gap-2 mb-3"> {post.tags.map((tag) => ( <span key={tag} className="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded" > {tag} </span> ))} </div> <h2 className="text-2xl font-bold mb-2 group-hover:text-blue-600 transition-colors"> {post.title} </h2> <p className="text-gray-600 mb-4">{post.excerpt}</p> <div className="flex justify-between text-sm text-gray-500"> <span>{post.date}</span> <span>{post.readTime}</span> </div> </Link> ))} </div> </div> ); }
tsx// src/app/blog/[slug]/page.tsx import { notFound } from 'next/navigation'; import { getPost, getAllPosts } from '@/lib/markdown'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; export async function generateStaticParams() { const posts = getAllPosts(); return posts.map((post) => ({ slug: post.slug, })); } export default async function BlogPostPage({ params, }: { params: { slug: string }; }) { const post = await getPost(params.slug); if (!post) { notFound(); } return ( <article className="container mx-auto px-4 py-16 max-w-4xl"> {/* الرأس */} <header className="mb-12"> <div className="flex gap-2 mb-4"> {post.tags.map((tag) => ( <span key={tag} className="text-xs px-3 py-1 bg-blue-100 text-blue-800 rounded-full" > {tag} </span> ))} </div> <h1 className="text-5xl font-bold mb-4">{post.title}</h1> <div className="flex items-center gap-4 text-gray-600"> <span>{post.author}</span> <span>•</span> <span>{post.date}</span> <span>•</span> <span>{post.readTime}</span> </div> </header> {/* المحتوى مع تمييز بناء الجملة */} <div className="prose prose-lg max-w-none"> <ReactMarkdown components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={vscDarkPlus} language={match[1]} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); }, }} > {post.content} </ReactMarkdown> </div> </article> ); }
جميع المكونات في App Router هي Server Components افتراضياً:
tsx// هذا يعمل على الخادم - لا يتم إرسال JavaScript للعميل! export default async function UserDashboard() { const data = await fetch('https://api.example.com/user', { cache: 'no-store', // بيانات ديناميكية }); const user = await data.json(); return ( <div> <h1>مرحباً، {user.name}</h1> <UserStats stats={user.stats} /> </div> ); }
استخدم 'use client' للمكونات التفاعلية:
tsx'use client'; import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>العدد: {count}</p> <button onClick={() => setCount(count + 1)}> زيادة </button> </div> ); }
typescript// بيانات ثابتة (يتم إنشاؤها وقت البناء) async function getStaticData() { const res = await fetch('https://api.example.com/posts', { cache: 'force-cache', // السلوك الافتراضي }); return res.json(); } // بيانات ديناميكية (يتم جلبها مع كل طلب) async function getDynamicData() { const res = await fetch('https://api.example.com/user', { cache: 'no-store', }); return res.json(); } // بيانات معاد التحقق منها (مخزنة مؤقتاً مع إعادة التحقق الزمني) async function getRevalidatedData() { const res = await fetch('https://api.example.com/products', { next: { revalidate: 3600 }, // إعادة التحقق كل ساعة }); return res.json(); }
typescriptimport type { Metadata } from 'next'; export const metadata: Metadata = { title: 'دليل Next.js 15', description: 'تعلم كل شيء عن Next.js 15', openGraph: { title: 'دليل Next.js 15', description: 'تعلم كل شيء عن Next.js 15', images: ['/og-image.jpg'], }, twitter: { card: 'summary_large_image', title: 'دليل Next.js 15', description: 'تعلم كل شيء عن Next.js 15', images: ['/og-image.jpg'], }, };
tsx// app/blog/loading.tsx export default function Loading() { return ( <div className="container mx-auto px-4 py-16"> <div className="animate-pulse"> <div className="h-8 bg-gray-200 rounded w-1/4 mb-8"></div> <div className="grid gap-8 md:grid-cols-3"> {[...Array(6)].map((_, i) => ( <div key={i} className="bg-gray-100 p-6 rounded-lg"> <div className="h-4 bg-gray-200 rounded mb-4"></div> <div className="h-4 bg-gray-200 rounded mb-4"></div> <div className="h-4 bg-gray-200 rounded w-2/3"></div> </div> ))} </div> </div> </div> ); }
tsximport Image from 'next/image'; export default function Hero() { return ( <Image src="/hero.jpg" alt="صورة البطل" width={1200} height={600} priority // تحميل فوري للصور أعلى الصفحة placeholder="blur" // عرض ضبابي أثناء التحميل blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." /> ); }
tsximport { Inter, Roboto_Mono } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', }); const robotoMono = Roboto_Mono({ subsets: ['latin'], display: 'swap', }); export default function RootLayout({ children }) { return ( <html lang="ar" className={inter.className}> <body>{children}</body> </html> ); }
typescript// next.config.ts const config: NextConfig = { // تفعيل تحليل الحزم bundlePagesRouterDependencies: true, // تحسين الحزم transpilePackages: ['@my-org/ui'], // تفعيل تحسينات الإنتاج compiler: { removeConsole: process.env.NODE_ENV === 'production', }, };
لنختبر لغات برمجة مختلفة للتأكد من أن تمييز بناء الجملة يعمل:
javascriptconst greet = (name) => { console.log(`مرحباً، ${name}!`); return `مرحباً بك في Next.js 15`; }; // دوال المصفوفات const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2);
pythondef fibonacci(n): """توليد متسلسلة فيبوناتشي""" if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2) # List comprehension squares = [x**2 for x in range(10)]
css.container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; padding: 2rem; } .card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 0.5rem; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); }
json{ "name": "my-nextjs-app", "version": "1.0.0", "scripts": { "dev": "next dev --turbo", "build": "next build", "start": "next start" }, "dependencies": { "next": "^15.5.0", "react": "^19.0.0" } }
bash#!/bin/bash # سكريبت النشر echo "نشر تطبيق Next.js..." npm run build npm run test if [ $? -eq 0 ]; then echo "✓ البناء ناجح" npm run deploy else echo "✗ فشل البناء" exit 1 fi
bash# تثبيت Vercel CLI npm i -g vercel # النشر vercel # نشر الإنتاج vercel --prod
dockerfile# Dockerfile FROM node:18-alpine AS base # التبعيات FROM base AS deps WORKDIR /app COPY package*.json ./ RUN npm ci # المُنشئ FROM base AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build # المُنفذ FROM base AS runner WORKDIR /app ENV NODE_ENV production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 ENV PORT 3000 CMD ["node", "server.js"]
bash# البناء للإنتاج npm run build # تشغيل خادم الإنتاج npm start # أو استخدام PM2 pm2 start npm --name "nextjs-app" -- start
bashnpm install next@latest react@latest react-dom@latest
typescript// تحديث استدعاءات API الديناميكية لتكون غير متزامنة const cookieStore = await cookies(); const headersList = await headers();
json{ "scripts": { "dev": "next dev --turbo" } }
bashnpm run build npm run start
يمثل Next.js 15 قفزة هائلة إلى الأمام في تطوير الويب. مع دعم React 19، وأداء Turbopack الفائق السرعة، ودعم TypeScript المحسّن، والميزات الجديدة القوية مثل مكون Form وواجهات برمجة الطلبات غير المتزامنة، إنه الإطار المثالي لبناء تطبيقات ويب حديثة.
المزيج من:
...يجعل Next.js 15 أفضل إصدار حتى الآن.
سواء كنت تبني مدونة شخصية مع عرض Markdown (مثل هذا المنشور!)، أو منصة تجارة إلكترونية، أو لوحة تحكم معقدة، يوفر Next.js 15 جميع الأدوات التي تحتاجها لبناء تطبيقات سريعة وقابلة للتوسع وجاهزة للإنتاج.
في Joulyan IT، نتخصص في بناء تطبيقات ويب عالية الأداء باستخدام أحدث التقنيات مثل Next.js 15. من التخطيط إلى النشر، نساعد الشركات على الاستفادة من أحدث تقنيات الويب لإنشاء تجارب مستخدم استثنائية.
هل تريد مناقشة مشروعك القادم؟ اتصل بنا لتتعرف على كيف يمكننا مساعدتك في تحقيق رؤيتك باستخدام Next.js 15.
آخر تحديث: 22 نوفمبر 2025