التصدير الثابت في Next.js — الدليل الشامل للإنتاج
كل ما تحتاج معرفته لنشر موقع Next.js ثابت بالكامل: أوضاع التصدير، القيود، المسارات الديناميكية، تحسين الصور، والنشر.

لماذا التصدير الثابت؟
الاستضافة الحديثة تتغير. منصات مثل GitHub Pages وCloudflare Pages والاستضافة المشتركة التقليدية توفر خدمة ملفات ثابتة سريعة وبأسعار معقولة — لكنها لا تشغّل Node.js. إذا كان موقع Next.js الخاص بك لا يحتاج إلى خادم حي لجلب البيانات، فإن التحول الكامل للتصدير الثابت باستخدام output: "export" يمنحك:
- بدون Cold Starts — HTML مُعالج مسبقاً، يُقدَّم فورياً.
- قدرة تخزين CDN قصوى — كل مسار هو ملف ثابت.
- نشر مُبسَّط — ارفع مجلد
out/في أي مكان. - كفاءة التكلفة — لا خادم للدفع عليه أو صيانته.
هذه هي المعمارية التي اخترتها لموقع معرضي الشخصي y0ussef.com، وهذا الدليل يلتقط كل ما تعلمته.
إعداد التصدير الثابت
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export", // ← هذا هو الإعداد الرئيسي
trailingSlash: true, // يُنشئ /about/index.html بدلاً من /about.html
images: {
unoptimized: true, // مطلوب — تحسين الصور يحتاج لخادم
},
};
export default nextConfig;
⚠️
unoptimized: trueيُعطّل تغيير حجم الصور وتحويلها إلى WebP المدمج في Next.js. ستحتاج للتعامل مع هذا بنفسك — أنا أستخدم سكريبت ما قبل البناء المبني علىsharp.
القيود الأساسية
فهم ما لا يمكنك فعله في التصدير الثابت أمر بالغ الأهمية:
| الميزة | التصدير الثابت | السبب |
|---|---|---|
getStaticProps | ✅ مدعوم بالكامل | يعمل وقت البناء |
getStaticPaths | ✅ مدعوم بالكامل | يُنشئ المسارات الديناميكية مسبقاً |
getServerSideProps | ❌ غير مدعوم | يحتاج خادماً حياً |
| مسارات API | ❌ غير مدعوم | يحتاج خادماً حياً |
| Middleware | ❌ غير مدعوم | يحتاج Edge runtime |
| Streaming / RSC | ❌ غير مدعوم | للخادم فقط |
| توليد صور OG | ❌ غير مدعوم | يحتاج مسار API |
توجيه اللغة بدون i18n Plugin
توجيه i18n المدمج في Next.js لا يعمل مع التصدير الثابت. الحل الذي أستخدمه هو التوجيه القائم على المسار:
pages/
en/
index.tsx → /en/
about.tsx → /en/about/
blog/
index.tsx → /en/blog/
[slug].tsx → /en/blog/[slug]/
ar/
index.tsx → /ar/
about.tsx → /ar/about/
كل مجلد لغة هو فضاء مسار منفصل. الاتجاه واللغة يُعيَّنان عبر useEffect في _app.tsx:
// src/pages/_app.tsx
useEffect(() => {
const isArabic = router.pathname.startsWith("/ar");
document.documentElement.dir = isArabic ? "rtl" : "ltr";
document.documentElement.lang = isArabic ? "ar" : "en";
}, [router.pathname]);
بسيط، بدون اعتماديات، ويعمل بشكل مثالي مع SSG.
المسارات الديناميكية مع getStaticPaths
لمقالات المدونة أو المشاريع أو أي محتوى ديناميكي:
// src/pages/en/blog/[slug].tsx
import { GetStaticProps, GetStaticPaths } from "next";
export const getStaticPaths: GetStaticPaths = () => {
const slugs = getAllBlogSlugs(); // يقرأ من src/content/blog/
return {
paths: slugs.map((slug) => ({ params: { slug } })),
fallback: false, // 404 للـ slugs غير المعروفة — مطلوب للتصدير الثابت
};
};
export const getStaticProps: GetStaticProps = ({ params }) => {
const post = getBlogPostBySlug(params!.slug as string, "en");
if (!post) return { notFound: true };
return { props: { post } };
};
fallback: falseإلزامي للتصدير الثابت. كلاهماfallback: 'blocking'وfallback: trueيحتاجان خادماً.
تحسين الصور بدون الخادم
بما أن images.unoptimized: true يُعطّل تحسين Next.js، كتبت سكريبت ما قبل البناء باستخدام sharp:
// scripts/convert-images.js
const sharp = require("sharp");
const fs = require("fs");
const path = require("path");
const IMAGES_DIR = path.join(__dirname, "..", "public", "images");
async function convertImages() {
const files = fs.readdirSync(IMAGES_DIR);
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (![".png", ".jpg", ".jpeg"].includes(ext)) continue;
const source = path.join(IMAGES_DIR, file);
const target = path.join(IMAGES_DIR, `${path.basename(file, ext)}.webp`);
if (fs.existsSync(target)) continue;
await sharp(source).webp({ quality: 85 }).toFile(target);
console.log(`تم التحويل: ${file} → ${path.basename(target)}`);
}
}
convertImages();
ربطه بعملية البناء:
{
"scripts": {
"convert-images": "node scripts/convert-images.js",
"build": "npm run convert-images && next build"
}
}
توليد sitemap.xml وقت البناء
بدون مسارات API، يجب أن يكون الـ sitemap ملفاً ثابتاً يُولَّد أثناء ما قبل البناء:
// scripts/generate-sitemap.js
const SITE_URL = "https://y0ussef.com";
function generateSitemap() {
const blogSlugs = getBlogSlugs();
const allRoutes = [...staticRoutes, ...blogSlugs.flatMap(slug => [
`/en/blog/${slug}/`,
`/ar/blog/${slug}/`,
])];
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allRoutes.map(route => ` <url><loc>${SITE_URL}${route}</loc></url>`).join("\n")}
</urlset>`;
fs.writeFileSync("public/sitemap.xml", xml);
console.log(`تم توليد الـ Sitemap: ${allRoutes.length} رابط`);
}
الملخص
التصدير الثابت مع Next.js Pages Router نهج قوي ومُقلَّل التقدير لمواقع المعارض الشخصية والصفحات التسويقية والمواقع الغنية بالمحتوى التي لا تحتاج ميزات الخادم الحي.
المبادئ الرئيسية التي أتبعها:
- كل البيانات وقت البناء —
getStaticPropsلكل شيء. fallback: falseعلى جميع المسارات الديناميكية.- تعامل مع تحسين الصور بنفسك عبر سكريبت sharp قبل البناء.
- توليد Sitemaps وقت البناء عبر سكريبت Node.js.
- توجيه اللغة القائم على المسار بدلاً من إعداد i18n.
نشر سعيد. 🚀
شارك هذا المقال
يوسف محمود
Full-Stack Engineer & Project Engineer
