Next.js 16، مكونات ذاكرة التخزين المؤقت ومترجم React: ما الذي يتغير بالفعل
· 6 min read · Filippo Spinella · Next.js, React, Frontend, Performance
لسنوات عديدة، كان أحد الأسئلة الأكثر إزعاجًا في Next.js هو: "هل هذه الصفحة ثابتة أم ديناميكية؟". يبدو الأمر وكأنه سؤال بسيط، حتى تقوم بإضافة مكالمة إلى cookies()، أو fetch بخيارات مختلفة، أو عميل قاعدة بيانات، أو نظام إدارة المحتوى (CMS)، أو عربة تسوق، أو جزء من المحتوى المخصص.
يعد Next.js 16 مثيرًا للاهتمام لأنه يحاول جعل هذه المحادثة أقل غموضًا. إنه لا يزيل التعقيد، ولكنه يغير النموذج العقلي: تكون المسارات ديناميكية بشكل افتراضي، وتعلن ذاكرة التخزين المؤقت عن نفسها عند الحاجة، ويصبح Suspense الطريقة الطبيعية لتكوين أغلفة سريعة بأجزاء تظل جديدة.
الميزة التي يجب فهمها هي مكونات ذاكرة التخزين المؤقت. تعد Stable Turbopack وReact Compiler وproxy.ts وواجهات برمجة التطبيقات الجديدة للإبطال مهمة، ولكنها تدور حول نفس المشكلة: إنشاء تطبيقات سريعة دون الحاجة إلى تخمين ما قرره إطار العمل خلف الكواليس.
لأن هذا الشيء مهم
في التطبيق الحقيقي، لا يكون لديك فقط "صفحات ثابتة" و"صفحات ديناميكية". لديك قطع مختلفة باحتياجات مختلفة.
قد تتغير ورقة المنتج عدة مرات في اليوم. قد يتغير السعر في كثير من الأحيان. يجب أن يكون التوفر مباشرًا تقريبًا. اسم المستخدم شخصي. يمكن دفق التعليقات. يمكن أن يكون الشريط الجانبي مستقرًا. العربة لا.
إذا تعاملت مع كل شيء كوحدة واحدة، فسوف ينتهي بك الأمر دائمًا إلى أحد النقيضين:
- التخزين المؤقت العدواني وخطر رؤية البيانات القديمة؛
- العرض الديناميكي في كل مكان والأداء أسوأ من اللازم.
تعمل مكونات ذاكرة التخزين المؤقت على وجه التحديد على تجنب هذا الاختيار الخاطئ.
النموذج على أرض الواقع
باستخدام cacheComponents: true، يمكنك الإعلان عن ما هو قابل للتخزين المؤقت باستخدام "use cache". وبعد ذلك يمكنك ربط المدة والعلامات بـ cacheLife() وcacheTag(). تظل الأجزاء الديناميكية ديناميكية ويمكن عزلها باستخدام Suspense.
الإعداد صغير:
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { cacheComponents: true, }; export default nextConfig;
التغيير الكبير ليس في التكوين. إنها تتعلق بكيفية البدء في كتابة المكونات.
// app/products/[slug]/page.tsx import { Suspense } from 'react'; import { cacheLife, cacheTag } from 'next/cache'; async function getProduct(slug: string) { 'use cache'; cacheLife('hours'); cacheTag(`product:${slug}`); return db.product.findUnique({ where: { slug } }); } async function ProductDetails({ slug }: { slug: string }) { const product = await getProduct(slug); return ( <section> <h1>{product.name}</h1> <p>{product.description}</p> </section> ); } async function LiveInventory({ slug }: { slug: string }) { const inventory = await db.inventory.findFirst({ where: { slug } }); return <p>{inventory.quantity} pezzi disponibili</p>; } export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return ( <> <ProductDetails slug={slug} /> <Suspense fallback={<p>Controllo disponibilità...</p>}> <LiveInventory slug={slug} /> </Suspense> </> ); }
ليس من الضروري أن تكون الصفحة مخزنة مؤقتًا بالكامل أو ديناميكية بالكامل. يمكن أن تكون ورقة المنتج سريعة وقابلة لإعادة الاستخدام. يمكن أن يبقى المخزون جديدًا. يرى المستخدم شيئًا على الفور، دون انتظار الجزء الأبطأ.
use cache هو مستند قابل للتنفيذ
الشيء الذي يعجبني في "use cache" هو أنه يجبرك على توضيح نيتك. عندما تقرأ وظيفة ما، ستدرك على الفور أن شخصًا ما قد قرر: "يمكن إعادة استخدام هذه البيانات".
إنه مفيد بشكل خاص عندما لا تستخدم fetch. تقرأ العديد من التطبيقات البيانات من Prisma أو Drizzle أو مجموعات SDK الداخلية أو عملاء CMS أو وظائف الخدمة. في تلك الحالات، لم يكن المنطق القديم المبني على خيارات fetch كافيًا.
قاعدة أساسية:
- محتوى ذاكرة التخزين المؤقت مستقر نسبيًا؛
- استخدام العلامات الحبيبية؛
- يترك الأذونات الديناميكية والجلسات وعربات التسوق والإشعارات وحالات المعاملات؛
- ضع الأجزاء البطيئة بالداخل
Suspense; - قم بالقياس قبل أن تقول "لقد قمنا بتحسين الأداء".
إبطال دون رمي كل شيء بعيدا
تكون ذاكرة التخزين المؤقت مفيدة فقط إذا كان بإمكانك تحديثها بدقة. هنا تصبح cacheTag وrevalidateTag وupdateTag مهمة.
مثال:
'use server'; import { updateTag } from 'next/cache'; export async function updateProductName(productId: string, name: string) { await db.product.update({ where: { id: productId }, data: { name }, }); updateTag(`product:${productId}`); }
التفاصيل المهمة هي العلامة. product:${productId} يحدّد حدودًا دقيقة. products يحكي دلوًا ضخمًا. في البداية يكون الدلو الضخم مريحًا؛ بعد بضعة أشهر يصبح السبب وراء إبطال نصف التطبيق لتغيير العنوان.
Turbopack المستقر: الجزء الذي تسمعه كل يوم
يقوم Next.js 16 بإحضار Turbopack إلى مركز التطوير والبناء. إنها ليست الميزة الأكثر شاعرية، ولكنها الميزة التي تشعر بها أثناء العمل: الخادم الذي يبدأ مبكرًا، والتحديث الأسرع، والبنيات التي لا تشعر وكأنها استراحة قسرية لتناول القهوة.
ومع ذلك، لن أقوم بترحيل قاعدة تعليمات برمجية مليئة بالمكونات الإضافية المخصصة وعيناي مغمضتان. أود أن أتحقق:
- البناء المحلي
- استيراد غير قياسي
- MDX، SVG وCSS؛
- ملحقات Webpack المتبقية؛
- صفحات حرجة؛
- الاختلافات في أوقات البناء.
بالنسبة للمشاريع الجديدة، سأبدأ من الافتراضي. بالنسبة للأشخاص الناضجين، سأقوم بهجرة محسوبة.
رد الفعل المترجم: إزالة الضوضاء، وليس الفكر
React Compiler 1.0 مستقر ويدعمه Next.js 16 باستخدام reactCompiler. الوعد هو تقليل الكثير من الحفظ اليدوي: تقليل memo، وتقليل useMemo، وتقليل useCallback المستخدمة "للسلامة".
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { reactCompiler: true, }; export default nextConfig;
لن أعامله مثل الغبار السحري. يساعد المترجم عندما يتبع الكود قواعد React بشكل جيد. إذا كانت المكونات لها آثار جانبية غريبة، أو طفرات مخفية، أو خطافات سيئة الاستخدام، فيجب إصلاح ذلك أولاً.
الطريقة الصحية لتجربتها:
- تحديث
eslint-plugin-react-hooks; - إصلاح الانتهاكات الفعلية؛
- وتمكينه في منطقة خاضعة للرقابة؛
- قياس بناء الوقت والسلوك.
- قم بإزالة الحفظ اليدوي فقط عندما لا تكون هناك حاجة إليه.
الهدف ليس محو كل useMemo. الهدف هو التوقف عن كتابة الحفظ الوقائي لأننا نخاف من التقديم.
proxy.ts وحدود الشبكة
يصبح middleware.ts القديم proxy.ts. إنه تغيير في الاسم، ولكنه منطقي: يقع هذا الملف عند حدود الطلب، وهو ليس برنامجًا وسيطًا عامًا تقليديًا على نمط الواجهة الخلفية.
// proxy.ts import { NextRequest, NextResponse } from 'next/server'; export default function proxy(request: NextRequest) { const isLoggedIn = Boolean(request.cookies.get('session')); if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }
القاعدة هنا بسيطة: أبقِها صغيرة. إعادة التوجيه، توجيه المصادقة، الرؤوس، عمليات إعادة الكتابة الأساسية. إذا بدأت تشعر وكأنها واجهة خلفية ثانية، فمن المحتمل أنها تفعل الكثير.
كيف سأهاجر حقا
لن أقوم بتشغيل جميع الميزات مرة واحدة. سأفعل هذا:
- أقوم بتحديث Next وReact وReact DOM؛
- أقوم بتشغيل الكود الرسمي؛
- أقوم بإصلاح التغييرات المعطلة على
params،searchParams،cookies()،headers()وdraftMode()؛ - أقوم بترحيل
middleware.tsإلىproxy.ts؛ - أتحقق من الإصدارات والصفحات المهمة؛
- أقوم بتمكين مكونات ذاكرة التخزين المؤقت في قسم حيث تؤدي ذاكرة التخزين المؤقت حاليًا إلى حدوث احتكاك؛
- أقوم بتحديد اصطلاحات العلامات والإبطال؛
- أحاول استخدام React Compiler بشكل منفصل؛
- مقارنة المقاييس قبل وبعد.
الترحيل الجيد ليس هو الذي يستخدم كافة الميزات الجديدة. وهذا ما يجعل سلوك التطبيق أكثر قابلية للقراءة.
ما يتغير في طريقة التفكير
الشيء الأكثر فائدة في Next.js 16 هو أنه يجبرك على تسمية النوايا بشكل أفضل. الوظيفة ليست مجرد "الحصول على المنتج من قاعدة البيانات". إنها "احصل على المنتج، يمكنني تخزينه مؤقتًا لساعات، وأقوم بإبطاله باستخدام هذه العلامة". المكون ليس مجرد "عرض الصفحة". إنها "هذه هي القشرة السريعة، وهذه القطعة شخصية، وهذا يأتي عبر البث المباشر."
في البداية يبدو الأمر وكأنه المزيد من العمل. ثم يصبح شكلاً من أشكال الهدوء. لم تعد قرارات الأداء مخفية في مزيج من الافتراضات والاستدلال والذاكرة القبلية. إنهم في الكود.