كنا نشغل كتالوج تجارة إلكترونية كبير على Next.js 14's App Router لمدة حوالي ثمانية عشر شهراً عندما تم إطلاق Next.js 16. 91,247 صفحة. قوائم المنتجات، أشجار الفئات، المحتوى التحريري، متغيرات محلية عبر 14 سوق. نموذج التخزين المؤقت القديم -- حيث تم تخزين مكونات الخادم مؤقتاً بشكل افتراضي -- أصبح حقل ألغام من أخطاء البيانات القديمة وrevalidateTag الفوضوي. عندما أعلنت فريق Next.js عن cacheComponents والتحول إلى عدم التخزين المؤقت بشكل افتراضي في Next.js 15 (تم نقله وتحسينه في v16)، علمنا أنه حان الوقت. هذه هي قصة هذا الترحيل: ما نجح، وما لم ينجح، وأرقام الأداء على الجانب الآخر.

جدول المحتويات

Next.js 16 cacheComponents: ترحيل 91,000 صفحة من App Router Caching

مشكلة التخزين المؤقت التي واجهناها فعلاً

دعني أرسم الصورة. في Next.js 14's App Router، كانت طلبات الجلب في Server Components مخزنة مؤقتاً بشكل افتراضي. استمر Data Cache عبر النشرات. قام Full Route Cache بتخزين HTML المعروض وحمولات RSC وقت البناء. واحتفظ Router Cache على جانب العميل بالأجزاء المحددة مقدماً حول... حسناً، أطول مما تتوقع.

بالنسبة لموقع يحتوي على 91,000 صفحة، أنشأ نهج التخزين المؤقت الافتراضي لكل شيء فئتين من المشاكل:

البيانات القديمة في كل مكان. تم تحديث أسعار المنتجات في CMS بدون رأس (Sanity، في حالتنا) لكن نتائج الجلب المخزنة مؤقتاً بقيت. كان لدينا استدعاءات revalidateTag موزعة عبر 47 إجراء خادم مختلف. هل تفتقد علامة واحدة؟ يرى العميل سعر أمس. كان لدينا حرفياً قناة Slack تسمى #cache-crimes حيث أبلغت فريق المحتوى عن الصفحات القديمة.

أوقات البناء من الجحيم. أخذ الإنشاء الثابت الكامل لـ 91,000 صفحة أكثر من 3 ساعات. انتقلنا إلى ISR مع revalidate: 3600 لمعظم الصفحات، لكن التفاعل بين ISR و Data Cache و on-demand revalidation كان صعب الفهم حقاً. كان المطورون الجدد في الفريق يقضون أسبوعيهما الأولين فقط في فهم طبقات التخزين المؤقت.

ضريبة النموذج العقلي

إليك ما أعتقد أن الناس يقللون من شأنه: التكلفة الإدراكية للتخزين المؤقت الضمني. عندما يكون التخزين المؤقت هو الافتراضي وتختار الخروج، يتطلب كل مكون جديد منك أن تسأل "هل يجب تخزين هذا مؤقتاً؟" ثم تتذكر إضافة التوجيه الصحيح إذا كانت الإجابة بلا. عندما يكون عدم التخزين المؤقت هو الافتراضي وتختار الانضمام، فأنت تفكر فقط في التخزين المؤقت عندما تريده بنشاط. هذا نموذج أساسي مختلف -- وأفضل --.

ما تغير في Next.js 15 و 16

Next.js 15 كان التحول الفلسفي الكبير. قلب الفريق الافتراضيات:

السلوك Next.js 14 Next.js 15 Next.js 16
fetch() في Server Components مخزن مؤقتاً بشكل افتراضي غير مخزن مؤقتاً بشكل افتراضي غير مخزن مؤقتاً بشكل افتراضي
Route Handlers (GET) مخزن مؤقتاً بشكل افتراضي غير مخزن مؤقتاً بشكل افتراضي غير مخزن مؤقتاً بشكل افتراضي
Client Router Cache 30s (dynamic) / 5min (static) 0s لأجزاء الصفحة 0s افتراضي، قابل للتكوين
Full Route Cache مفعّل للمسارات الثابتة نفس الشيء نفس الشيء، مع تحسينات cacheLife
التخزين المؤقت على مستوى المكون unstable_cache توجيه use cache (تجريبي) API cacheComponents (مستقر)

قدم Next.js 15 توجيه use cache كميزة تجريبية خلف علم. Next.js 16، الذي تم إطلاقه في أوائل 2025، استقر هذا باعتباره خيار تكوين cacheComponents وتوجيه "use cache" المرتبط به، إلى جانب cacheLife لتحديد ملفات تعريف التخزين المؤقت المخصصة وcacheTag للإلغاء الموجه.

الرؤية الرئيسية: انتقل التخزين المؤقت من كونه سلوكاً ضمنياً للإطار عمل إلى اختيار صريح للمطور على مستوى المكون. هذا يعني الكثير للمواقع الكبيرة.

فهم cacheComponents

ميزة cacheComponents في next.config.js تفعّل التخزين المؤقت على مستوى المكون من خلال توجيه "use cache". إليك الإعداد الأساسي:

// next.config.js (Next.js 16)
const nextConfig = {
  experimental: {
    cacheComponents: true,
  },
};

module.exports = nextConfig;

بمجرد التفعيل، يمكنك إضافة "use cache" في أعلى أي Server Component غير متزامن أو إجراء خادم أو حتى ملف تخطيط:

// app/products/[slug]/page.tsx
"use cache";

import { cacheLife, cacheTag } from 'next/cache';

export default async function ProductPage({ params }: { params: { slug: string } }) {
  cacheLife('products'); // ملف تعريف التخزين المؤقت المخصص
  cacheTag(`product-${params.slug}`);

  const product = await fetchProduct(params.slug);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <ProductDetails product={product} />
      <DynamicPricing productId={product.id} /> {/* هذا المكون غير مخزن مؤقتاً */}
    </div>
  );
}

ملفات تعريف cacheLife

هنا يصبح الأمر مثيراً للاهتمام بالنسبة للمواقع الكبيرة. تحدد ملفات تعريف التخزين المؤقت المسماة في next.config.js:

const nextConfig = {
  experimental: {
    cacheComponents: true,
    cacheLife: {
      products: {
        stale: 300,      // قدم محتوى قديم لمدة 5 دقائق
        revalidate: 3600, // أعد التحقق بعد ساعة
        expire: 86400,    // انتهاء الصلاحية الصعب بعد 24 ساعة
      },
      editorial: {
        stale: 3600,
        revalidate: 86400,
        expire: 604800,   // 7 أيام
      },
      navigation: {
        stale: 86400,
        revalidate: 604800,
        expire: 2592000,  // 30 يوم
      },
    },
  },
};

يرسم نموذج الثلاث طبقات (stale، revalidate، expire) خريطة بشكل جميل لدلالات stale-while-revalidate. خلال نافذة stale، يتم خدمة المحتوى المخزن مؤقتاً فوراً. بعد stale لكن قبل expire، يتم بدء إعادة التحقق في الخلفية. بعد expire، إدخال التخزين المؤقت ذهب.

cacheTag للإلغاء

تستبدل دالة cacheTag النمط revalidateTag القديم بشيء أكثر قابلية للتركيب:

import { revalidateTag } from 'next/cache';

// في معالج webhook أو إجراء خادم:
export async function handleProductUpdate(productSlug: string) {
  revalidateTag(`product-${productSlug}`);
  revalidateTag('product-listing'); // ألغِ صفحات القائمة أيضاً
}

لم يتغير هذا الجزء كثيراً من Next.js 15، لكنه يعمل بشكل أفضل بكثير مع cacheComponents لأنك تقوم بوضع علامات على مكونات مخزنة مؤقتاً محددة بدلاً من محاولة إلغاء ذاكرة تخزين مؤقت غير شفافة على مستوى الإطار عمل.

Next.js 16 cacheComponents: ترحيل 91,000 صفحة من App Router Caching - الهندسة المعمارية

استراتيجية الترحيل لدينا لـ 91,000 صفحة

لم نفعل هذا في لقطة واحدة. مع 91,000 صفحة عبر 14 لغة محلية، كان الترحيل الضخم الكبير سيكون متهوراً. إليك كيف قسمناه:

المرحلة 1: الترقية إلى Next.js 16، لا تغييرات في التخزين المؤقت (الأسبوع 1-2)

قمنا بالترقية من Next.js 14.2 إلى 16.0 دون تفعيل cacheComponents. وحده هذا غيّر السلوك لأن طلبات الجلب لم تعد مخزنة مؤقتاً بشكل افتراضي. توقعنا انحدارات TTFB وحصلنا عليها:

  • متوسط TTFB ارتفع من 180ms إلى 340ms على صفحات المنتجات
  • حمل خادم الأصل زاد بحوالي 60% (احتفظت Sanity CDN بها بشكل جيد، لكن نقاط نهاية API المخصصة لدينا لم تفعل)
  • في الواقع أصبح إعادة تحقق ISR أسرع لأن هناك حالة تخزين مؤقت أقل للإدارة

هذا أكد ما اشتبهنا فيه: لقد كنا نعتمد بكثافة على التخزين المؤقت الضمني، وكثير من صفحاتنا احتاجت فعلاً إلى التخزين المؤقت -- فقط التخزين المؤقت الصريح والمقصود.

المرحلة 2: الأمان والتصنيف (الأسبوع 3)

صنفنا كل مسار في تطبيقنا:

نوع الصفحة العدد استراتيجية التخزين المؤقت ملف تعريف cacheLife
صفحات تفاصيل المنتج 42,000 التخزين المؤقت مع علامة المنتج products (5min stale / 1hr revalidate)
صفحات قوائم الفئات 3,200 التخزين المؤقت مع علامة الفئة products (5min stale / 1hr revalidate)
صفحات المقالات/المدونة 8,400 التخزين المؤقت بقوة editorial (1hr stale / 24hr revalidate)
المتغيرات المحلية 31,647 نفس الصفحة الأساسية موروث من القاعدة
صفحات الحساب/الديناميكية 6,000 بلا تخزين مؤقت N/A

المرحلة 3: تفعيل cacheComponents، إضافة التوجيهات (الأسبوع 4-6)

قمنا بتفعيل العلم وبدأنا إضافة توجيهات "use cache". القرار الرئيسي: قمنا بالتخزين المؤقت على مستوى الصفحة لمعظم المسارات، لكن على مستوى المكون للصفحات التي تحتوي على محتوى ثابت/ديناميكي مختلط.

بالنسبة لصفحات المنتجات، تم تخزين معلومات المنتج والصور مؤقتاً، لكن تم ترك مكون التسعير وحالة المخزون بدون تخزين مؤقت:

// components/ProductInfo.tsx
"use cache";

import { cacheLife, cacheTag } from 'next/cache';

export async function ProductInfo({ slug }: { slug: string }) {
  cacheLife('products');
  cacheTag(`product-${slug}`, 'product-info');
  
  const product = await getProduct(slug);
  
  return (
    <section>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ProductImages images={product.images} />
    </section>
  );
}
// components/DynamicPricing.tsx
// لا توجد توجيهات "use cache" -- دائماً طازج

export async function DynamicPricing({ productId }: { productId: string }) {
  const pricing = await getPricing(productId); // يصل إلى API التسعير في كل طلب
  
  return (
    <div className="pricing">
      <span className="price">${pricing.current}</span>
      {pricing.onSale && <span className="was-price">${pricing.original}</span>}
    </div>
  );
}

المرحلة 4: تكامل Webhook (الأسبوع 7)

أعدنا تسليك webhooks Sanity لدينا لاستدعاء revalidateTag بالعلامات الصحيحة. كان هذا في الواقع أبسط من إعدادنا القديم لأن العلامات كانت الآن صريحة في الكود، وليس موزعة عبر خيارات الجلب.

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const body = await request.json();
  const secret = request.headers.get('x-webhook-secret');
  
  if (secret !== process.env.REVALIDATION_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }

  switch (body._type) {
    case 'product':
      revalidateTag(`product-${body.slug.current}`);
      revalidateTag('product-listing');
      break;
    case 'category':
      revalidateTag(`category-${body.slug.current}`);
      revalidateTag('navigation');
      break;
    case 'article':
      revalidateTag(`article-${body.slug.current}`);
      break;
  }

  return new Response('OK', { status: 200 });
}

التنفيذ: خطوة بخطوة

إذا كنت تقوم بترحيل مشابه، فإليك كتيب الممارسات العملية الذي نوصي به (وما نستخدمه الآن لـ مشاريع تطوير Next.js في Social Animal):

الخطوة 1: تفعيل العلم

// next.config.js
module.exports = {
  experimental: {
    cacheComponents: true,
    cacheLife: {
      // ابدأ بافتراضيات معقولة
      default: {
        stale: 60,
        revalidate: 900,
        expire: 86400,
      },
    },
  },
};

الخطوة 2: ابحث عن مساراتك الساخنة

استخدم تحليلاتك لتحديد الصفحات التي تحصل على أكثر حركة مرور وحيث يكون TTFB مهماً أكثر. بالنسبة لنا، كانت صفحات الفئات (حركة مرور عالية، محتوى مستقر نسبياً) وصفحات المنتجات (حركة مرور عالية، محتوى ديناميكي معتدل).

الخطوة 3: أضف `"use cache"` من الأعلى للأسفل

ابدأ بالتخطيطات. إذا كان تخطيط الجذر الخاص بك يجلب بيانات التنقل، قم بتخزينها مؤقتاً أولاً -- إنها أعلى تأثير وأقل مخاطرة:

// app/layout.tsx
// ملاحظة: "use cache" على التخطيطات يخزن قذيفة التخطيط مؤقتاً
// صفحات الأطفال تعرض نفسها بشكل مستقل

import { Navigation } from '@/components/Navigation';

export default async function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Navigation /> {/* هذا المكون له "use cache" خاص به */}
        {children}
      </body>
    </html>
  );
}

الخطوة 4: إعداد المراقبة

استخدمنا تحليلات Vercel المدمجة بالإضافة إلى تسجيل مخصص لتتبع معدلات الاصابات في الذاكرة المؤقتة. في الأسبوع الأول بعد تفعيل cacheComponents، كان معدل اصابات الذاكرة المؤقتة لدينا 34٪ فقط. بعد ضبط مدد stale، ارتفع إلى 78٪.

نتائج الأداء والمعايير

إليك الأرقام الحقيقية بعد الترحيل الكامل، مقاسة على مدة 30 يوماً على خطة Vercel Pro:

المقياس قبل (Next.js 14) بعد المرحلة 1 (v16، بدون ذاكرة) بعد الترحيل الكامل
متوسط TTFB (صفحات المنتجات) 180ms 340ms 95ms
متوسط TTFB (صفحات الفئات) 220ms 410ms 72ms
متوسط TTFB (صفحات المقالات) 150ms 280ms 45ms
P99 TTFB (جميع الصفحات) 1,200ms 2,100ms 380ms
وقت البناء (كامل) 3h 12min 2h 48min 48min
استدعاءات دوال Vercel/اليوم 2.4M 3.8M 1.1M
فاتورة Vercel الشهرية ~$840 ~$1,200 ~$520
معدل اصابات الذاكرة المؤقتة غير معروف (ضمني) N/A 78%
حوادث المحتوى القديم (#cache-crimes) 8-12/week 0 1-2/month

تستحق تحسينات وقت البناء التوضيح. مع cacheComponents، انتقلنا بعيداً عن توليد جميع الصفحات البالغ عددها 91,000 وقت البناء. بدلاً من ذلك، قمنا بإنشاء ثابت لأفضل 5,000 صفحة فقط (حسب حركة المرور) وسمحنا للبقية بالإنشاء عند الطلب مع التخزين المؤقت. كانت توجيهات cacheComponents تعني أن هذه الصفحات المُنشأة عند الطلب تم تخزينها مؤقتاً بعد الزيارة الأولى، مع التحكم في السيولة cacheLife.

كان انخفاض فاتورة Vercel كبيراً. عدد أقل من استدعاءات الدوال (بسبب التخزين المؤقت للمكونات الصريح) بالإضافة إلى أوقات بناء أقصر يعني توفيرات حقيقية. يدفع هذا الانخفاض ~$320/شهر مقابل نفسه.

المزالق والمشاكل

حدود التسلسل

توجيه "use cache" ينشئ حد تسلسل. يجب أن يكون كل شيء يتم تمريره إلى مكون مخزن مؤقتاً كخاصية قابلة للتسلسل. كان لدينا عدة مكونات تحصل على وظائف رد نداء أو عناصر React كخاصيات -- تلك انكسرت على الفور. كان الإصلاح هو إعادة الهيكلة لاستخدام أنماط التركيب بدلاً من ذلك:

// ❌ هذا ينكسر مع "use cache"
"use cache";
export async function ProductCard({ product, onAddToCart }) {
  // onAddToCart دالة -- غير قابلة للتسلسل!
}

// ✅ هذا يعمل
"use cache";
export async function ProductCard({ product }) {
  return (
    <div>
      <h2>{product.name}</h2>
      {/* AddToCart مكون عميل، غير مخزن مؤقتاً */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}

المعاملات الديناميكية وانفجار مفتاح التخزين المؤقت

مع 91,000 صفحة، كل واحدة بمعاملات فريدة، مساحة مفتاح التخزين المؤقت ضخمة. اصطدمنا بحدود التخزين المؤقت للحافة في Vercel في الأسبوع الأول واضطررنا إلى أن نكون أكثر استراتيجية بشأن الصفحات التي حصلت على قيم expire طويلة. حصلت صفحات حركة المرور المنخفضة ذات الذيل الطويل على مدد تخزين مؤقت أقصر.

فخ `Date.now()`

أي مكون يستخدم "use cache" يستدعي Date.now() أو new Date() داخل الدالة المخزنة مؤقتاً سيخزن هذا الطابع الزمني مؤقتاً. وجدنا هذا في عرض "آخر تحديث" أظهر نفس الوقت لساعات. الإصلاح: انقل المنطق الحساس للوقت إلى مكون عميل أو مكون خادم غير مخزن مؤقتاً.

حدود الذاكرة المؤقتة المتداخلة

عندما تتداخل مكونات مخزنة مؤقتاً داخل مكونات مخزنة مؤقتاً أخرى، للذاكرة المؤقتة الداخلية دورة حياتها الخاصة. هذا قوي لكنه محير. وضعنا اتفاقية فريق: التخزين المؤقت على مستوى الصفحة أو مستوى المكون، وليس كليهما، ما لم يكن هناك سبب واضح.

متى يجب استخدام cacheComponents ومتى لا

استخدمه عند:

  • لديك أكثر من بضع مئات من الصفحات وأوقات بناء ISR مؤلمة
  • المحتوى الخاص بك له متطلبات طازجة واضحة تختلف حسب القسم
  • تحتاج إلى تحكم دقيق فيما يتم تخزينه مؤقتاً مقابل دائماً طازج
  • أنت تعمل على Vercel أو منصة تدعم طبقات ذاكرة التخزين المؤقت Next.js
  • تريد تقليل تكاليف البنية التحتية على المواقع عالية حركة المرور

لا تستخدمه عند:

  • الموقع صغير بما يكفي لكي يعمل SSG الكامل بشكل جيد
  • كل صفحة ديناميكية بالكامل (محتوى خاص بالمستخدم في كل مكان)
  • أنت لست على منصة استضافة تدعم البنية التحتية لتخزين Next.js المؤقت
  • فريقك جديد على Next.js -- تعرف على الأساسيات أولاً

إذا كنت تقيّم ما إذا كان مشروعك يحتاج إلى هذا المستوى من التحكم في التخزين المؤقت، أو ما إذا كان إطار عمل مختلف مثل Astro قد يكون أفضل لموقع المحتوى الكثيف، فقد يكون من المناسب التفكير في هذا قبل الالتزام برحلة.

بالنسبة للمشاريع حيث يأتي المحتوى من مصادر CMS بدون رأس متعددة، نظام cacheTag في Next.js 16 يعمل بشكل جميل مع بنى CMS بدون رأس -- كل نوع محتوى يحصل على قناة إلغاء خاصة به.

الأسئلة الشائعة

ما هي cacheComponents في Next.js 16؟ cacheComponents خيار تكوين تجريبي في Next.js 16 يفعّل توجيه "use cache" لـ Server Components. يتيح لك وضع علامات صريحة على المكونات التي يجب تخزينها مؤقتاً وتحديد ملفات تعريف التخزين المؤقت المخصصة باستخدام cacheLife. إنها التطور المستقر لتوجيه use cache الذي كان تجريبياً في Next.js 15.

كيف تختلف cacheComponents عن ISR (Incremental Static Regeneration)؟ ISR يخزن صفحات كاملة ويعيد التحقق منها وفقاً لجدول زمني. cacheComponents يتيح لك تخزين مكونات فردية داخل صفحة مؤقتاً، لكل منها أعمار ذاكرة مختلفة. صفحة واحدة يمكن أن يكون لديها رأس مخزن مؤقتاً لمدة 24 ساعة، معلومات المنتج مخزنة مؤقتاً لمدة ساعة واحدة، والتسعير الذي لم يتم تخزينه مؤقتاً بأبداً. ISR لا يمكنه فعل ذلك -- إنها كل شيء أو لا شيء على مستوى الصفحة.

هل يجب أن أكون على Vercel لاستخدام cacheComponents؟ لا، لكن التجربة أفضل على Vercel لأن البنية التحتية للتخزين المؤقت متكاملة بإحكام. يمكن نشرات Next.js ذاتية الاستضافة استخدام cacheComponents مع محول الذاكرة المؤقتة للملف، لكن لن تحصل على فوائد التوزيع على الحافة. تضيف منصات مثل Netlify و Cloudflare الدعم، لكن اعتباراً من منتصف 2025، يبقى Vercel التنفيذ الأكمل.

كيف ألغي المكونات المخزنة مؤقتاً في Next.js 16؟ تستخدم cacheTag() داخل مكونك المخزن مؤقتاً لتعيين علامات، ثم تستدعي revalidateTag('tag-name') من إجراء خادم أو معالج مسار أو نقطة webhook. هذا يلغي جميع المكونات المخزنة مؤقتاً بهذه العلامة. إنها نفس API من Next.js 15، لكنها أكثر فائدة الآن لأنك تضع علامات على مكونات مخزنة مؤقتاً صريحة بدلاً من محاولة إلغاء ذاكرة تخزين مؤقت غير شفافة على مستوى الإطار عمل.

هل ستقلل cacheComponents من فاتورة Vercel الخاصة بي؟ يمكن أن تقلل التكاليف بشكل كبير. في حالتنا، انخفضت استدعاءات الدوال بنسبة 54٪ لأن استجابات مكونات التخزين المؤقت تم تقديمها من طبقة التخزين المؤقت بدلاً من استدعاء وظائف بدون خادم. يوفر تحسين وقت البناء أيضاً في دقائق البناء. ستختلف الأميال الخاصة بك بناءً على أنماط حركة المرور ومعدلات الاصابات في الذاكرة المؤقتة -- تحقق من حاسب أسعار Vercel مع الاستخدام الحالي.

ماذا يحدث إذا أضفت "use cache" إلى مكون يتلقى خاصيات غير قابلة للتسلسل؟ ستحصل على خطأ البناء. توجيه "use cache" ينشئ حد تسلسل، لذا يجب أن تكون جميع الخصائص قابلة للتسلسل (سلاسل نصية وأرقام وأجسام سادة ومصفوفات). الوظائف وعناصر React وحالات الفئات والقيم غير القابلة للتسلسل الأخرى ستسبب فشل البناء. أعد هيكلة المكون لقبول خاصيات البيانات فقط ومعالجة التفاعلية في مكونات Child Client.

هل يمكنني استخدام cacheComponents مع React Server Components من أطر عمل أخرى؟ لا. cacheComponents ميزة حصرية لـ Next.js تبني على React's Server Components. بينما قد يصبح بناء جملة "use cache" في النهاية معيار React، فإن أنظمة cacheLife و cacheTag هي APIs Next.js. إذا كنت تستخدم إطار عمل مثل Remix أو إعداد RSC مخصص، ستحتاج إلى استراتيجيات تخزين مؤقت مختلفة.

كم من الوقت يستغرق ترحيل موقع Next.js كبير إلى cacheComponents؟ لموقعنا البالغ 91,000 صفحة مع فريق مكون من 4 مطورين، استغرق الترحيل الكامل 7 أسابيع بما في ذلك الاختبار والمراقبة. يمكن لموقع أصغر (أقل من 10,000 صفحة) بنموذج بيانات أبسط ربما أن يفعل ذلك في 1-2 أسبوع. التغييرات الفعلية للكود مباشرة -- يذهب الوقت إلى دراسة احتياجات التخزين المؤقت الخاصة بك، واختبار تدفقات الإلغاء، والمراقبة بعد النشر. إذا كنت تفضل عدم السير وحدك، تواصل معنا -- لقد فعلنا هذا عدة مرات.