Upstash Redis و Next.js: تحديد معدل الطلبات والتخزين المؤقت وقوائم الرسائل

تواجه تطبيقات الويب الحديثة ثلاثة تحديات متكررة: حماية واجهات API من الإساءة، وتسريع استجابات الخادم، ومعالجة العمليات في الخلفية بشكل موثوق. يُعد Redis الحل الكلاسيكي لهذه الحالات الثلاث — لكن استضافة وصيانة خادم Redis في بيئة الإنتاج يتطلب وقتًا وتعقيدًا تشغيليًا.
يُزيل Upstash Redis هذا الاحتكاك من خلال تقديم Redis بدون خادم (serverless)، يُحاسب لكل طلب، مع SDK أصلي لـ TypeScript. لا خادم لإدارته، ولا اتصال دائم للمحافظة عليه — كل استدعاء يمر عبر HTTP، مما يجعله مثاليًا لبيئات serverless مثل Vercel و Cloudflare Workers و AWS Lambda.
في هذا الدليل، ستبني واجهة API لإدارة جهات الاتصال مع Next.js تدمج ثلاثة أنماط Redis أساسية:
- تحديد معدل الطلبات (Rate Limiting) — تقييد الطلبات لكل عنوان IP لحماية نقاط النهاية
- التخزين المؤقت (Caching) — تخزين استجابات API مؤقتًا لتقليل زمن الاستجابة وحمل قاعدة البيانات
- قوائم الرسائل (Message Queues) — معالجة المهام غير المتزامنة مع QStash (خدمة الرسائل بدون خادم من Upstash)
المتطلبات الأساسية
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبت
- حساب Upstash مجاني — سجل في console.upstash.com
- معرفة أساسية بـ React و TypeScript
- إلمام بـ Next.js App Router (مسارات API، Server Actions)
- محرر أكواد (VS Code موصى به)
ما ستبنيه
نظام إدارة جهات اتصال يتضمن:
- تحديد معدل الطلبات لكل IP — يحظر العملاء الذين يتجاوزون 10 طلبات في الدقيقة
- تخزين مؤقت ذكي — يخزن نتائج الاستعلامات المتكررة مع إبطال تلقائي
- قائمة معالجة — يرسل رسائل ترحيب عبر QStash عند إنشاء جهة اتصال
- لوحة مراقبة — تعرض مقاييس Redis في الوقت الفعلي
الخطوة 1: إنشاء مشروع Next.js
ابدأ مشروع Next.js جديد مع TypeScript و App Router:
npx create-next-app@latest upstash-contacts --typescript --tailwind --app --src-dir
cd upstash-contactsثبّت تبعيات Upstash:
npm install @upstash/redis @upstash/ratelimit @upstash/qstashحزمة @upstash/redis توفر عميل Redis عبر HTTP. @upstash/ratelimit تقدم خوارزميات تحديد المعدل الجاهزة للاستخدام. @upstash/qstash تدير قوائم الرسائل بدون خادم.
الخطوة 2: إعداد Upstash Redis
سجّل الدخول إلى وحدة تحكم Upstash وأنشئ قاعدة بيانات Redis جديدة. اختر المنطقة الأقرب لخادم النشر (مثلاً eu-west-1 للنشر الأوروبي).
انسخ متغيرات الاتصال وأضفها إلى ملف .env.local:
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
QSTASH_TOKEN=eyJxxxxxxxxxxxxxxxxxxxxxxxxx
QSTASH_CURRENT_SIGNING_KEY=sig_xxxxxxxxxxxxxxxx
QSTASH_NEXT_SIGNING_KEY=sig_xxxxxxxxxxxxxxxxالآن أنشئ ملف إعداد Redis المشترك:
// src/lib/redis.ts
import { Redis } from "@upstash/redis";
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});هذا العميل يستخدم بروتوكول HTTP — بدون اتصال TCP دائم. كل استدعاء هو طلب REST مستقل، وهو مثالي لدوال serverless التي يمكن إنشاؤها وتدميرها في أي وقت.
الخطوة 3: تطبيق تحديد معدل الطلبات
تحديد معدل الطلبات يحمي واجهات API من الإساءة وهجمات رفض الخدمة. توفر Upstash وحدة مخصصة مع خوارزميات متعددة.
إنشاء محدد المعدل
// src/lib/rate-limit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "./redis";
// خوارزمية النافذة المنزلقة: 10 طلبات في الدقيقة لكل معرّف
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, "1 m"),
analytics: true, // تفعيل التتبع على لوحة Upstash
prefix: "ratelimit:contacts-api",
});النافذة المنزلقة (sliding window) هي الخيار الأفضل لمعظم الحالات. على عكس النافذة الثابتة التي تعيد تعيين العداد بشكل مفاجئ، توزع النافذة المنزلقة الطلبات بالتساوي عبر الوقت.
تقدم Upstash ثلاث خوارزميات:
- Fixed Window — عداد بسيط يُعاد تعيينه على فترات ثابتة
- Sliding Window — تنعيم بين نافذتين لتجنب الذروات
- Token Bucket — يسمح بدفعات محكومة، مثالي لواجهات API ذات أنماط حركة مرور غير منتظمة
إنشاء وسيط تحديد المعدل
// src/middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
}),
limiter: Ratelimit.slidingWindow(10, "1 m"),
analytics: true,
prefix: "ratelimit:api",
});
export async function middleware(request: NextRequest) {
// تطبيق فقط على مسارات API
if (!request.nextUrl.pathname.startsWith("/api")) {
return NextResponse.next();
}
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: "طلبات كثيرة جدًا. يرجى المحاولة لاحقًا." },
{
status: 429,
headers: {
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
"Retry-After": Math.ceil((reset - Date.now()) / 1000).toString(),
},
}
);
}
const response = NextResponse.next();
response.headers.set("X-RateLimit-Limit", limit.toString());
response.headers.set("X-RateLimit-Remaining", remaining.toString());
response.headers.set("X-RateLimit-Reset", reset.toString());
return response;
}
export const config = {
matcher: "/api/:path*",
};هذا الوسيط يعترض جميع الطلبات إلى /api/*، ويتحقق من حد المعدل لكل IP، ويُرجع رمز 429 Too Many Requests إذا تجاوز العميل الحد. رؤوس X-RateLimit-* تُعلم العميل بعدد الطلبات المتبقية.
الخطوة 4: تطبيق التخزين المؤقت
التخزين المؤقت يقلل زمن الاستجابة وحمل قاعدة البيانات. Upstash Redis مثالي لهذا بفضل TTL (مدة الحياة) المدمج وتوافقه مع هياكل بيانات Redis.
نمط Cache-Aside
النمط الأكثر شيوعًا هو cache-aside: التحقق من ذاكرة التخزين المؤقت قبل الاستعلام من المصدر، وتخزين النتيجة في ذاكرة التخزين المؤقت للطلبات اللاحقة.
// src/lib/cache.ts
import { redis } from "./redis";
interface CacheOptions {
ttl?: number; // مدة الحياة بالثواني (الافتراضي: 60)
prefix?: string;
}
export async function cached<T>(
key: string,
fetcher: () => Promise<T>,
options: CacheOptions = {}
): Promise<T> {
const { ttl = 60, prefix = "cache" } = options;
const cacheKey = `${prefix}:${key}`;
// 1. التحقق من ذاكرة التخزين المؤقت
const cached = await redis.get<T>(cacheKey);
if (cached !== null) {
return cached;
}
// 2. تنفيذ الجلب إذا لم يكن مخزنًا مؤقتًا
const data = await fetcher();
// 3. التخزين في ذاكرة التخزين المؤقت مع TTL
await redis.set(cacheKey, JSON.stringify(data), { ex: ttl });
return data;
}
export async function invalidateCache(pattern: string): Promise<void> {
// الحصول على جميع المفاتيح المطابقة للنمط
const keys = await redis.keys(`cache:${pattern}*`);
if (keys.length > 0) {
await redis.del(...keys);
}
}استخدام التخزين المؤقت في مسار API
// src/app/api/contacts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { cached, invalidateCache } from "@/lib/cache";
// محاكاة قاعدة بيانات
const db = {
contacts: [
{ id: "1", name: "أمين بن علي", email: "amine@example.com" },
{ id: "2", name: "سارة الطرابلسي", email: "sara@example.com" },
{ id: "3", name: "يوسف الحمدي", email: "youssef@example.com" },
],
};
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const contacts = await cached(
`contacts:list:${query}`,
async () => {
// محاكاة استعلام بطيء لقاعدة البيانات
await new Promise((resolve) => setTimeout(resolve, 500));
if (query) {
return db.contacts.filter(
(c) =>
c.name.toLowerCase().includes(query.toLowerCase()) ||
c.email.toLowerCase().includes(query.toLowerCase())
);
}
return db.contacts;
},
{ ttl: 30 } // تخزين مؤقت لمدة 30 ثانية
);
return NextResponse.json({ contacts });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const { name, email } = body;
if (!name || !email) {
return NextResponse.json(
{ error: "الاسم والبريد الإلكتروني مطلوبان" },
{ status: 400 }
);
}
const newContact = {
id: Date.now().toString(),
name,
email,
};
db.contacts.push(newContact);
// إبطال ذاكرة التخزين المؤقت بعد عملية الكتابة
await invalidateCache("contacts:");
return NextResponse.json({ contact: newContact }, { status: 201 });
}النقطة الأساسية هي إبطال ذاكرة التخزين المؤقت أثناء عمليات الكتابة. بدون ذلك، سيرى المستخدمون بيانات قديمة. النمط invalidateCache("contacts:") يحذف جميع إدخالات التخزين المؤقت التي يبدأ مفتاحها بـ cache:contacts:.
تخزين مؤقت متقدم: Stale-While-Revalidate
للبيانات التي تتغير بشكل متكرر لكن تتحمل تأخرًا طفيفًا، يقدم نمط stale-while-revalidate أفضل توازن بين الأداء والحداثة:
// src/lib/swr-cache.ts
import { redis } from "./redis";
interface SWRCacheOptions {
ttl: number; // مدة الحياة "الطازجة" بالثواني
staleFor: number; // المدة الإضافية التي تُقدم فيها البيانات مع إعادة التحقق
prefix?: string;
}
interface CachedEntry<T> {
data: T;
cachedAt: number;
}
export async function swrCached<T>(
key: string,
fetcher: () => Promise<T>,
options: SWRCacheOptions
): Promise<T> {
const { ttl, staleFor, prefix = "swr" } = options;
const cacheKey = `${prefix}:${key}`;
const entry = await redis.get<CachedEntry<T>>(cacheKey);
if (entry) {
const age = (Date.now() - entry.cachedAt) / 1000;
if (age <= ttl) {
// بيانات طازجة — تقديم مباشرة
return entry.data;
}
if (age <= ttl + staleFor) {
// بيانات قديمة لكن مقبولة — تقديم وإعادة التحقق في الخلفية
revalidate(cacheKey, fetcher, ttl + staleFor);
return entry.data;
}
}
// لا توجد ذاكرة تخزين مؤقت أو قديمة جدًا — جلب وتخزين
const data = await fetcher();
await redis.set(
cacheKey,
JSON.stringify({ data, cachedAt: Date.now() }),
{ ex: ttl + staleFor }
);
return data;
}
async function revalidate<T>(
key: string,
fetcher: () => Promise<T>,
totalTtl: number
): Promise<void> {
try {
const data = await fetcher();
await redis.set(
key,
JSON.stringify({ data, cachedAt: Date.now() }),
{ ex: totalTtl }
);
} catch {
// صامت — البيانات القديمة تم تقديمها بالفعل
}
}الخطوة 5: قوائم الرسائل مع QStash
QStash هي خدمة الرسائل بدون خادم من Upstash. تسمح لك بتشغيل مهام في الخلفية بدون خادم مخصص — يرسل QStash طلب HTTP إلى نقطة النهاية عندما يحين وقت تنفيذ المهمة.
إعداد QStash
// src/lib/qstash.ts
import { Client } from "@upstash/qstash";
export const qstash = new Client({
token: process.env.QSTASH_TOKEN!,
});نشر رسالة في القائمة
عند إنشاء جهة اتصال جديدة، نرسل رسالة إلى QStash لتشغيل بريد ترحيبي:
// src/app/api/contacts/route.ts (تحديث POST)
import { qstash } from "@/lib/qstash";
export async function POST(request: NextRequest) {
const body = await request.json();
const { name, email } = body;
if (!name || !email) {
return NextResponse.json(
{ error: "الاسم والبريد الإلكتروني مطلوبان" },
{ status: 400 }
);
}
const newContact = {
id: Date.now().toString(),
name,
email,
};
db.contacts.push(newContact);
await invalidateCache("contacts:");
// نشر رسالة لإرسال بريد ترحيبي
await qstash.publishJSON({
url: `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/welcome-email`,
body: {
contactId: newContact.id,
name: newContact.name,
email: newContact.email,
},
retries: 3,
delay: 5, // الانتظار 5 ثوانٍ قبل المحاولة الأولى
});
return NextResponse.json({ contact: newContact }, { status: 201 });
}إنشاء webhook المستهلك
// src/app/api/webhooks/welcome-email/route.ts
import { NextRequest, NextResponse } from "next/server";
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";
async function handler(request: NextRequest) {
const body = await request.json();
const { contactId, name, email } = body;
console.log(`إرسال بريد ترحيبي إلى ${name} (${email})`);
// هنا، ادمج خدمة البريد الإلكتروني (Resend، SendGrid، إلخ)
await simulateSendEmail({
to: email,
subject: `مرحبًا ${name}!`,
body: `شكرًا لانضمامك إلينا. حسابك جاهز.`,
});
return NextResponse.json({ success: true });
}
async function simulateSendEmail(params: {
to: string;
subject: string;
body: string;
}) {
// محاكاة تأخير الإرسال
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(`تم إرسال البريد إلى ${params.to}: ${params.subject}`);
}
// التحقق من توقيع QStash لتأمين الـ webhook
export const POST = verifySignatureAppRouter(handler);دالة verifySignatureAppRouter ضرورية — فهي تتحقق من أن الطلب يأتي فعلاً من QStash وليس من مهاجم. يوقّع QStash كل رسالة باستخدام المفاتيح المحددة في متغيرات البيئة.
جدولة مهام متكررة
يدعم QStash أيضًا المهام المجدولة (cron). على سبيل المثال، إرسال تقرير يومي:
// src/lib/scheduled-tasks.ts
import { qstash } from "./qstash";
export async function setupDailyReport() {
await qstash.schedules.create({
destination: `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/daily-report`,
cron: "0 9 * * *", // كل يوم في الساعة 9 صباحًا
retries: 3,
});
}// src/app/api/webhooks/daily-report/route.ts
import { NextRequest, NextResponse } from "next/server";
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";
import { redis } from "@/lib/redis";
async function handler(request: NextRequest) {
// جمع مقاييس اليوم
const totalRequests = await redis.get<number>("metrics:total-requests") || 0;
const newContacts = await redis.get<number>("metrics:new-contacts") || 0;
const rateLimitHits = await redis.get<number>("metrics:rate-limit-hits") || 0;
const report = {
date: new Date().toISOString().split("T")[0],
totalRequests,
newContacts,
rateLimitHits,
};
console.log("التقرير اليومي:", report);
// إعادة تعيين العدادات
await redis.set("metrics:total-requests", 0);
await redis.set("metrics:new-contacts", 0);
await redis.set("metrics:rate-limit-hits", 0);
return NextResponse.json({ report });
}
export const POST = verifySignatureAppRouter(handler);الخطوة 6: بناء لوحة المراقبة
أنشئ مكون React لعرض مقاييس Redis في الوقت الفعلي.
مسار API للمقاييس
// src/app/api/metrics/route.ts
import { NextResponse } from "next/server";
import { redis } from "@/lib/redis";
export async function GET() {
const [totalRequests, newContacts, rateLimitHits, cacheKeys] =
await Promise.all([
redis.get<number>("metrics:total-requests"),
redis.get<number>("metrics:new-contacts"),
redis.get<number>("metrics:rate-limit-hits"),
redis.keys("cache:*"),
]);
return NextResponse.json({
totalRequests: totalRequests || 0,
newContacts: newContacts || 0,
rateLimitHits: rateLimitHits || 0,
cachedEntries: cacheKeys.length,
uptime: process.uptime(),
});
}مكون لوحة المراقبة
// src/app/dashboard/page.tsx
"use client";
import { useEffect, useState } from "react";
interface Metrics {
totalRequests: number;
newContacts: number;
rateLimitHits: number;
cachedEntries: number;
uptime: number;
}
export default function DashboardPage() {
const [metrics, setMetrics] = useState<Metrics | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchMetrics = async () => {
try {
const res = await fetch("/api/metrics");
const data = await res.json();
setMetrics(data);
} catch (err) {
console.error("خطأ في تحميل المقاييس:", err);
} finally {
setLoading(false);
}
};
fetchMetrics();
const interval = setInterval(fetchMetrics, 5000); // تحديث كل 5 ثوانٍ
return () => clearInterval(interval);
}, []);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-gray-500">جاري تحميل المقاييس...</p>
</div>
);
}
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">لوحة مراقبة Redis</h1>
<div className="grid grid-cols-2 gap-6 md:grid-cols-4">
<MetricCard
label="إجمالي الطلبات"
value={metrics?.totalRequests ?? 0}
color="blue"
/>
<MetricCard
label="جهات اتصال جديدة"
value={metrics?.newContacts ?? 0}
color="green"
/>
<MetricCard
label="حد المعدل"
value={metrics?.rateLimitHits ?? 0}
color="red"
/>
<MetricCard
label="إدخالات مخزنة"
value={metrics?.cachedEntries ?? 0}
color="purple"
/>
</div>
</div>
);
}
function MetricCard({
label,
value,
color,
}: {
label: string;
value: number;
color: string;
}) {
const colorClasses: Record<string, string> = {
blue: "bg-blue-50 border-blue-200 text-blue-700",
green: "bg-green-50 border-green-200 text-green-700",
red: "bg-red-50 border-red-200 text-red-700",
purple: "bg-purple-50 border-purple-200 text-purple-700",
};
return (
<div className={`rounded-xl border-2 p-6 ${colorClasses[color]}`}>
<p className="text-sm font-medium opacity-75">{label}</p>
<p className="text-3xl font-bold mt-2">{value.toLocaleString()}</p>
</div>
);
}الخطوة 7: اختبار النظام الكامل
اختبار تحديد معدل الطلبات
استخدم curl لإرسال طلبات سريعة والتحقق من عمل محدد المعدل:
# إرسال 12 طلبًا بسرعة
for i in $(seq 1 12); do
echo "طلب $i:"
curl -s -o /dev/null -w "HTTP %{http_code} | المتبقي: " \
http://localhost:3000/api/contacts
echo ""
doneيجب أن ترى أول 10 طلبات تُرجع 200 وآخر اثنين تُرجع 429.
اختبار التخزين المؤقت
# الطلب الأول — بدون تخزين مؤقت (بطيء)
time curl http://localhost:3000/api/contacts
# الطلب الثاني — من التخزين المؤقت (سريع)
time curl http://localhost:3000/api/contactsالطلب الأول يجب أن يستغرق حوالي 500 مللي ثانية (محاكاة قاعدة البيانات)، والثاني يجب أن يكون شبه فوري.
اختبار قوائم الرسائل
# إنشاء جهة اتصال — يُشغل بريدًا عبر QStash
curl -X POST http://localhost:3000/api/contacts \
-H "Content-Type: application/json" \
-d '{"name": "فاطمة الشريف", "email": "fatma@example.com"}'تحقق من سجلات تطبيقك لرؤية رسالة "تم إرسال البريد" تظهر بعد بضع ثوانٍ من الإنشاء.
استكشاف الأخطاء وإصلاحها
خطأ "UPSTASH_REDIS_REST_URL is not defined"
تحقق من وجود ملف .env.local في جذر المشروع واحتوائه على المتغيرات الصحيحة. أعد تشغيل خادم التطوير بعد أي تعديل على .env.local.
محدد المعدل لا يحظر الطلبات في بيئة التطوير
في وضع التطوير، قد يكون x-forwarded-for فارغًا. يستخدم الوسيط 127.0.0.1 كقيمة احتياطية، مما يعني أن جميع الطلبات المحلية تشترك في نفس العداد. هذا السلوك صحيح.
webhooks QStash تفشل محليًا
يحتاج QStash للوصول إلى خادمك عبر الإنترنت. للتطوير المحلي، استخدم نفقًا مثل ngrok:
npx ngrok http 3000ثم حدّث NEXT_PUBLIC_APP_URL في .env.local بعنوان ngrok.
التخزين المؤقت لا يُمسح بعد الكتابة
تحقق من أن دالة invalidateCache تستخدم البادئة الصحيحة. مفاتيح Redis حساسة لحالة الأحرف.
أفضل ممارسات الإنتاج
1. استخدام بادئات مفاتيح متسقة
نظّم مفاتيح Redis ببادئات وصفية لتسهيل المراقبة والتصحيح:
ratelimit:api:{ip}
cache:contacts:list:{query}
swr:dashboard:metrics
metrics:total-requests
2. تعيين TTL مناسبة
- تحديد المعدل: TTL قصير (1 إلى 5 دقائق)
- تخزين البيانات المؤقت: TTL متوسط (30 ثانية إلى 5 دقائق)
- الجلسات: TTL طويل (24 ساعة إلى 7 أيام)
- البيانات المؤقتة: TTL قصير جدًا (5 إلى 30 ثانية)
3. التعامل مع أخطاء Redis بأمان
لا يجب أن يكون Redis نقطة فشل حرجة. إذا كان Redis غير متاح، يجب أن يستمر تطبيقك في العمل:
export async function cachedWithFallback<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number = 60
): Promise<T> {
try {
const cached = await redis.get<T>(`cache:${key}`);
if (cached) return cached;
} catch {
// Redis غير متاح — الاستمرار بدون تخزين مؤقت
console.warn("Redis غير متاح، التنفيذ بدون تخزين مؤقت");
}
const data = await fetcher();
try {
await redis.set(`cache:${key}`, JSON.stringify(data), { ex: ttl });
} catch {
// صامت — البيانات تُرجع على أي حال
}
return data;
}4. مراقبة الاستهلاك
خطة Upstash المجانية تقدم 10,000 طلب يوميًا. لتطبيقات الإنتاج:
- استخدم لوحة Upstash لمراقبة الاستهلاك
- اضبط تنبيهات التجاوز
- حسّن عدد الأوامر لكل عملية (استخدم خطوط أنابيب Redis)
النشر على Vercel
النشر بسيط بفضل طبيعة Upstash بدون خادم:
- ادفع الكود إلى GitHub
- اربط المستودع بـ Vercel
- أضف متغيرات البيئة في إعدادات Vercel
- انشر
تقدم Upstash أيضًا تكاملاً أصليًا مع Vercel يُعدّ متغيرات البيئة تلقائيًا:
# ثبّت تكامل Upstash من سوق Vercel
# سيتم إضافة UPSTASH_REDIS_REST_URL و UPSTASH_REDIS_REST_TOKEN
# تلقائيًا إلى مشروعكالخطوات التالية
الآن بعد أن أتقنت أساسيات Upstash Redis مع Next.js، إليك بعض المسارات لاستكشاف المزيد:
- إدارة الجلسات — استبدل الجلسات المبنية على ملفات تعريف الارتباط بجلسات Redis لقابلية توسع أفضل
- لوحات المتصدرين والتصنيفات — استخدم Sorted Sets في Redis للتصنيفات في الوقت الفعلي
- Pub/Sub في الوقت الفعلي — ادمج Upstash Redis مع Server-Sent Events للدفع في الوقت الفعلي
- تخزين مؤقت للصفحات الكاملة — خزّن استجابات HTML للصفحات الثابتة الديناميكية
- التكامل مع Drizzle ORM — ادمج التخزين المؤقت Redis مع طبقة الوصول إلى البيانات
الخلاصة
تعلمت كيفية دمج Upstash Redis في تطبيق Next.js لحل ثلاث مشاكل شائعة: تحديد معدل الطلبات والتخزين المؤقت وقوائم الرسائل. نهج Upstash بدون خادم يُزيل التعقيد التشغيلي — لا خادم Redis لإدارته، ولا اتصال دائم للمحافظة عليه، وفوترة لكل طلب تجعل الخدمة متاحة حتى للمشاريع الصغيرة.
الأنماط المقدمة في هذا الدليل — cache-aside و stale-while-revalidate و webhooks الموثقة — هي أسس متينة لأي تطبيق في بيئة الإنتاج. ادمجها حسب احتياجاتك: تحديد المعدل للحماية، والتخزين المؤقت للتسريع، وقوائم الرسائل لتوزيع العمل.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء مهام خلفية للإنتاج باستخدام Trigger.dev v3 و Next.js
تعلم كيفية بناء مهام خلفية موثوقة ومهام مجدولة وسير عمل متعدد الخطوات باستخدام Trigger.dev v3 مع Next.js. يغطي هذا الدرس إنشاء المهام ومعالجة الأخطاء وإعادة المحاولة والمهام المجدولة والنشر في بيئة الإنتاج.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

Better Auth مع Next.js 15: الدليل الشامل للمصادقة في 2026
تعلم كيفية تنفيذ نظام مصادقة متكامل في Next.js 15 باستخدام Better Auth. يغطي هذا الدليل تسجيل الدخول بالبريد الإلكتروني وOAuth والجلسات وحماية المسارات والتحكم بالأدوار.