كيفية تقديم 137 ألف قائمة على Next.js ISR دون تفجير ميزانية Vercel
ينطلق النشر الخاص بك في الساعة 11 مساءً. تراقب سجل بناء Vercel يمر بـ 10,000 مسار ثابت، ثم 50,000، ثم يتوقف في مكان ما بالقرب من 89,000. بعد ست ساعات، انتهت المهلة الزمنية للبناء. لن يتم نشر مجلتك التي تحتوي على 137,000 قائمة لأنك حاولت عرض كل شيء مسبقًا في وقت البناء — خطأ كلفنا 11 يومًا واتصال عميل محرج جدًا. في النهاية، نشرنا نظامًا إنتاجيًا يخدم ملايين مرات الصفحة، ويرتبط لآلاف الكلمات الرئيسية طويلة الذيل، وينقل الصفحات عند الطلب مقابل 209 دولارات شهريًا. كان للعمارة التي جعلت ذلك ممكنًا مسؤولية قتل غريزتنا لعرض كل شيء مسبقًا، وإعادة التفكير في كيفية توسع استعلامات Supabase تحت ISR، وتغيير واحد في إعدادات Vercel الذي خفض أوقات الاستجابة بمقدار 340 ميلي ثانية. إليك ما عمل فعلاً.
المكدس: Next.js 14 (App Router)، Supabase (PostgreSQL + Edge Functions)، Vercel (الاستضافة + ISR)، وجرعة صحية من البراغماتية. ارتكبنا أخطاء. اصطدمنا بالجدران. أعدنا كتابة الأشياء التي اعتقدنا أنها انتهت. لكن العمارة النهائية تتعامل مع 137,000+ صفحة ديناميكية مع TTFB أقل من 200 ميلي ثانية عالميًا، وفاتورة Supabase الخاصة بنا تبقى أقل من 100 دولار شهريًا.
إذا كنت تبني شيئًا مشابهًا — سوقًا، أو دليلًا، أو منصة قوائم — فهذه هي المقالة التي تمنيت أن توجد عندما بدأنا.
جدول المحتويات
- لماذا هذا المكدس
- طبقة البيانات: Supabase في الحجم
- استراتيجية توليد الصفحة: ISR و SSG ومشكلة 137K
- معمارية URL وتحسين محركات البحث في الحجم
- البحث والتصفية: الجزء الصعب
- ميزانيات الأداء والتخزين المؤقت للحافة
- المراقبة والرؤية في الإنتاج
- توزيع التكاليف: ما تكلفه فعليًا
- ما كنا سنفعله بشكل مختلف
- الأسئلة الشائعة

لماذا هذا المكدس
قيمنا الكثير من الخيارات قبل الاستقرار على Next.js + Supabase + Vercel. كانت المتطلبات الأساسية:
- 137,000+ صفحة فريدة يمكن لمحركات البحث الزحف والفهرسة
- تحميل الصفحات دون ثانية عالميًا (المستخدمون في 40+ دول)
- البيانات الديناميكية — تتحدث القوائم يوميًا، بعضها بالساعة
- البحث الكامل للنص مع تصفية متعددة الأوجه
- صديقة للميزانية — لم تكن هذه مشروعًا تم تمويله بواسطة رأس المال الاستثماري
اعتبرنا Astro (رائع للمواقع الثابتة، لكننا احتجنا إلى المزيد من التفاعل الديناميكي — على الرغم من أن فريق تطوير Astro الخاص بنا قد نشر مشاريع مجلة ممتازة معها). نظرنا إلى WordPress + WPEngine. اعتبرنا بإيجاز SPA نقي مع Algolia.
فاز Next.js بسبب ميزة واحدة قاتلة: Incremental Static Regeneration. كان ISR يعني أننا لم نضطر للاختيار بين أداء ثابتة وحيوية ديناميكية. يمكننا أن نملك كليهما.
كسب Supabase على PlanetScale و Neon لأن الحزمة الكاملة — المصادقة والتخزين والدوال الحافة وتطبيق Postgres جيد حقًا مع Row Level Security. للحصول على دليل، تحتاج إلى كل هذا.
كان Vercel هو هدف النشر لأن ISR يعمل بشكل أفضل على Vercel (بشكل غير مفاجئ). التكامل أصلي. إعادة التحقق من الطلب فقط تعمل.
ماذا عن الاستضافة الذاتية؟
قمنا بنمذجة إعداد Next.js المستضاف ذاتيًا على Railway. عملت، لكن ISR على Next.js المستضاف ذاتيًا لديه مشاكل. قصة إلغاء التخزين المؤقت أسوأ. تحتاج إلى إدارة طبقة CDN الخاصة بك. لفريق مكونة من 3 مهندسين، كان العبء العملي غير جدير بـ 200 دولار شهريًا التي كنا سننقذها.
طبقة البيانات: Supabase في الحجم
قاعدة بيانات Supabase الخاصة بنا تحتفظ بـ 137,000 قائمة، كل واحدة بـ 40-60 حقل. الفئات والمواقع ومعلومات الاتصال والأوصاف الغنية والصور والتقييمات وساعات التشغيل — الكل.
تصميم المخطط
كان أكبر قرار هو ما إذا كان يجب استخدام مخطط علائقي معايري أو نهج موجه للوثائق أكثر مع أعمدة JSONB. ذهبنا الهجين:
CREATE TABLE listings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
description TEXT,
category_id UUID REFERENCES categories(id),
city_id UUID REFERENCES cities(id),
country_code TEXT NOT NULL,
coordinates GEOGRAPHY(POINT, 4326),
contact JSONB DEFAULT '{}',
attributes JSONB DEFAULT '{}',
media JSONB DEFAULT '[]',
rating_avg NUMERIC(3,2) DEFAULT 0,
rating_count INTEGER DEFAULT 0,
status TEXT DEFAULT 'active',
published_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT NOW(),
search_vector TSVECTOR
);
CREATE INDEX idx_listings_category ON listings(category_id) WHERE status = 'active';
CREATE INDEX idx_listings_city ON listings(city_id) WHERE status = 'active';
CREATE INDEX idx_listings_country ON listings(country_code) WHERE status = 'active';
CREATE INDEX idx_listings_coordinates ON listings USING GIST(coordinates);
CREATE INDEX idx_listings_search ON listings USING GIN(search_vector);
CREATE INDEX idx_listings_slug ON listings(slug);
بيانات علائقية منظمة للأشياء التي نصفيها (الفئات والمدن والدول). JSONB للأشياء شبه المنظمة التي تختلف لكل قائمة (طرق الاتصال والسمات المخصصة وصفائف الوسائط). أعطانا هذا أفضل ما في كلا العالمين — استعلامات مفهرسة سريعة على الأعمدة العلائقية ومرونة على الباقي.
متجه البحث
هذا العمود search_vector حرج. نحن نملأها بزناد:
CREATE OR REPLACE FUNCTION update_search_vector()
RETURNS TRIGGER AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(NEW.description, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(NEW.attributes->>'keywords', '')), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
هذا يعني أن كل قائمة قابلة للبحث في نص كامل من خلال Postgres نفسه. لا توجد حاجة لخدمة بحث خارجية لأول 100K قائمة. سنتحدث عن متى ينهار هذا لاحقًا.
تجميع الاتصالات
يستخدم Supabase PgBouncer لتجميع الاتصالات. مع ISR، تحصل على انفجارات من استدعاءات الوظائف بدون خادم — كل واحد يحتاج اتصال قاعدة بيانات. بدون تجميع، ستستنزف الاتصالات في دقائق.
نحن نستخدم سلسلة الاتصال المجمعة (port 6543) لجميع السياقات بدون خادم وسلسلة الاتصال المباشرة (port 5432) فقط للترحيل والمهام الإدارية. هذا هو أحد تلك الأشياء التي تبدو واضحة ولكنها تمسك الناس.
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // Server-side only
{
db: { schema: 'public' },
auth: { persistSession: false }
}
)
استراتيجية توليد الصفحة: ISR و SSG ومشكلة 137K
هذا هو المكان الذي تصبح الأمور مثيرة للاهتمام. وحيث ارتكبنا أكبر خطأ مبكر.
النهج السذاجة (لا تفعل هذا)
محاولتنا الأولى: توليد جميع 137,000 صفحة في وقت البناء باستخدام generateStaticParams. استغرق البناء 4 ساعات و 22 دقيقة. طبقة Vercel المجانية لها حد 45 دقيقة. حتى طبقة Pro مقسمة في 6 ساعات. لكن المشكلة الحقيقية لم تكن المهلة الزمنية — كانت حلقة التعليقات. استغرق كل نشر نصف يوم. هذا غير قابل للعمل.
نهج ISR (ما يعمل فعليًا)
هنا الاستراتيجية التي شحنت:
- في وقت البناء: توليد أعلى 5,000 صفحة (حسب حركة المرور) بشكل ثابت
- عند طلب أول: توليد الصفحات المتبقية عند الطلب وتخزينها مؤقتًا
- إعادة التحقق: استنادًا إلى الوقت (كل 3600 ثانية) + عند الطلب عبر webhook
// app/listing/[slug]/page.tsx
import { supabase } from '@/lib/supabase'
import { notFound } from 'next/navigation'
export async function generateStaticParams() {
// Only pre-generate top listings by traffic
const { data } = await supabase
.from('listings')
.select('slug')
.eq('status', 'active')
.order('rating_count', { ascending: false })
.limit(5000)
return (data || []).map((listing) => ({
slug: listing.slug,
}))
}
export const revalidate = 3600 // Revalidate every hour
export default async function ListingPage({ params }: { params: { slug: string } }) {
const { data: listing, error } = await supabase
.from('listings')
.select(`
*,
category:categories(*),
city:cities(*, country:countries(*))
`)
.eq('slug', params.slug)
.eq('status', 'active')
.single()
if (!listing || error) notFound()
return <ListingDetail listing={listing} />
}
إعادة التحقق من الطلب
عندما يحدث صاحب القائمة بياناته، لا نريد الانتظار لمدة ساعة حتى تنعش الصفحة. يشغل Supabase webhooks مسار Next.js API:
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-revalidation-secret')
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { slug, type } = await request.json()
if (type === 'listing') {
revalidatePath(`/listing/${slug}`)
revalidatePath(`/`) // Revalidate homepage too
}
return NextResponse.json({ revalidated: true })
}
هذا يعطينا أفضل ما في كلا العالمين: أداء الموقع الثابت مع حداثة الموقع الديناميكي. تكتمل الإنشاءات في أقل من 8 دقائق. يتم إنشاء الصفحات التي لم يتم إنشاؤها مسبقًا في الزيارة الأولى وتخزينها مؤقتًا في الحافة.
الأرقام
| متري | SSG كامل (السذاجة) | ISR (الإنتاج) |
|---|---|---|
| وقت البناء | 4س 22د | 7د 40ث |
| الصفحات عند النشر | 137,000 | 5,000 |
| الزيارة الأولى (غير مخزن مؤقتًا) | N/A | ~800ms |
| الزيارات اللاحقة | ~120ms | ~120ms |
| زمن تأخر إعادة التحقق | إعادة نشر كاملة | < 2 ثانية |
| دقائق البناء الشهرية | بعيد عن الحد | ~230 دقيقة |

معمارية URL وتحسين محركات البحث في الحجم
مع 137,000 صفحة، معمارية URL ليست بعد الفكرة — إنها عمارة. كل عنوان URL هو فرصة ترتيب.
تسلسل الهرمية URL
/ → الصفحة الرئيسية
/categories/[category-slug] → صفحات الفئة (48 فئة)
/locations/[country]/[city] → صفحات الموقع
/listing/[listing-slug] → القائمة الفردية
/search?q=...&category=...&city=... → نتائج البحث (noindex)
صفحات تقاطع الفئة + الموقع هي كنز SEO الحقيقي:
/categories/restaurants/us/new-york → "المطاعم في نيويورك"
/categories/hotels/uk/london → "الفنادق في لندن"
يتم توليد صفحات التقاطع هذه بشكل ديناميكي مع ISR. هناك تقريبًا 12,000 مزيج صحيح. يستهدف كل منها كلمة رئيسية محددة طويلة الذيل.
توليد خريطة الموقع
مع 137K عناوين URL، تحتاج إلى ملفات فهرس خريطة الموقع. حد Google هو 50,000 عنوان URL لكل خريطة موقع.
// app/sitemap/[id]/route.ts
export async function GET(request: Request, { params }: { params: { id: string } }) {
const page = parseInt(params.id)
const perPage = 45000 // Stay under the 50K limit
const offset = page * perPage
const { data: listings } = await supabase
.from('listings')
.select('slug, updated_at')
.eq('status', 'active')
.order('id')
.range(offset, offset + perPage - 1)
const xml = generateSitemapXml(listings)
return new Response(xml, {
headers: { 'Content-Type': 'application/xml' },
})
}
انقسمنا إلى 4 خرائط موقع: sitemap-0.xml من خلال sitemap-3.xml، المشار إليها بواسطة فهرس خريطة الموقع. فهرست Google Search Console 98% من عناوين URL المقدمة خلال 6 أسابيع.
البيانات المنظمة
تتضمن كل صفحة قائمة بيانات JSON-LD المنظمة. بالنسبة للدليل، فإن مخطط LocalBusiness حرج:
const structuredData = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: listing.title,
description: listing.description,
address: {
'@type': 'PostalAddress',
addressLocality: listing.city.name,
addressCountry: listing.city.country.code,
},
geo: {
'@type': 'GeoCoordinates',
latitude: listing.coordinates?.lat,
longitude: listing.coordinates?.lng,
},
aggregateRating: listing.rating_count > 0 ? {
'@type': 'AggregateRating',
ratingValue: listing.rating_avg,
reviewCount: listing.rating_count,
} : undefined,
}
البحث والتصفية: الجزء الصعب
البحث هو دائما الجزء الصعب. دائمة.
المرحلة 1: بحث Postgres Full-Text
للإطلاق الأولي لنا، بحث Postgres tsvector تعامل مع كل شيء. إنه سريع بما يكفي لـ 137K صفوف مع فهرس GIN. يبلغ متوسط أوقات الاستعلام 40-80ms.
const { data } = await supabase
.from('listings')
.select('id, slug, title, description, category:categories(name)')
.textSearch('search_vector', query, { type: 'websearch' })
.eq('status', 'active')
.eq('country_code', countryFilter)
.order('rating_avg', { ascending: false })
.range(0, 19)
المرحلة 2: عندما لم يكن Postgres كافيًا
في حوالي 80,000 قائمة، بدأت عمليات البحث المعقدة متعددة الأوجه (الفئة + الموقع + النص + الترتيب) بضرب 300-500ms. مقبول لمعظم التطبيقات، لكن المستخدمين يتوقعون نتائج فورية.
أضفنا Typesense كطبقة بحث. ليس Algolia (مكلف جدًا في حجمنا — كنا سندفع 500+ دولار شهريًا). ليس Meilisearch (رائع، لكن البحث الجغرافي لـ Typesense كان أفضل لحالة استخدامنا).
يعمل Typesense على مثيل Hetzner واحد بـ 48 دولار شهريًا. يتم مزامنة من Supabase عبر إعادة فهرسة كاملة ليلية + تحديثات webhook في الوقت الفعلي. يبلغ متوسط استعلامات البحث الآن 8-15ms.
| حل البحث | وقت الاستعلام (p50) | وقت الاستعلام (p99) | التكلفة الشهرية | البحث متعدد الأوجه |
|---|---|---|---|---|
| Postgres FTS | 45ms | 320ms | $0 (مضمن) | محدود |
| Typesense | 9ms | 28ms | $48 | ممتاز |
| Algolia | ~5ms | ~15ms | $500+ | ممتاز |
| Meilisearch | ~8ms | ~22ms | $48 (مستضاف ذاتيًا) | جيد |
ميزانيات الأداء والتخزين المؤقت للحافة
وضعنا أهدافًا أداءً قوية من اليوم الأول:
- TTFB: < 200ms (p75 عام)
- LCP: < 1.5s
- CLS: < 0.05
- إجمالي وزن الصفحة: < 300KB (الحمل الأول)
شبكة Vercel Edge
يتم تخزين صفحات ISR مؤقتًا في شبكة Vercel Edge — 100+ PoPs عالميًا. بمجرد إنشاء صفحة وتخزينها مؤقتًا، فإنها تعمل من موقع الحافة الأقرب. هذا هو السبب في بقاء TTFB أقل من 200ms حتى بالنسبة للمستخدمين في جنوب شرق آسيا أو أمريكا الجنوبية.
تحسين الصورة
كل قائمة بها 1-8 صور. وهذا يعني احتمالا أكثر من مليون صورة. نحن نستخدم تحسين الصورة المدمج في Vercel مع next/image:
<Image
src={listing.media[0]?.url}
alt={listing.title}
width={800}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
loading={index === 0 ? 'eager' : 'lazy'}
quality={75}
/>
يتم تخزين الصور في Supabase Storage وخدمتها من خلال CDN صور Vercel. عادة ما تكون الصور الأصلية 2-5MB؛ بعد التحسين، تكون 40-120KB. وحده هذا وفر لنا تقريبًا 80% على النطاق الترددي.
المراقبة والرؤية في الإنتاج
تشغيل 137K صفحة في الإنتاج بدون مراقبة مثل القيادة عمياء. إليك المكدس الخاص بنا:
- Vercel Analytics: Core Web Vitals والمراقبة الفعلية للمستخدمين
- Sentry: تتبع الأخطاء (نمسك حوالي 50 خطأ يوميًا، غالبيتها من الروبوتات ترسل القمامة)
- لوحة معلومات Supabase: أداء قاعدة البيانات وتحليل الاستعلام
- Checkly: المراقبة الاصطناعية وفترات زمنية مدتها 5 دقائق على المسارات الحرجة
- Google Search Console: تغطية الفهرس وإحصائيات الزحف
كانت أكثر عملية مراقبة قيمة أعددناها استعلام Supabase يومي يحسب الصفحات المفهرسة مقابل إجمالي القوائم النشطة. إذا انخفضت النسبة أقل من 95%، نحصل على تنبيه. اكتشف هذا انحدار خريطة موقع في غضون 24 ساعة من نشر تغيير سيء.
توزيع التكاليف: ما تكلفه فعليًا
يسأل الناس دائمًا عن التكلفة. إليك الإنفاق الشهري الحقيقي اعتبارًا من Q1 2026:
| الخدمة | الخطة | التكلفة الشهرية |
|---|---|---|
| Vercel | Pro | $20 |
| Vercel Bandwidth (الإفراط) | الدفع حسب الاستخدام | ~$35 |
| Supabase | Pro | $25 |
| Supabase Database (الحوسبة) | مثيل صغير | $48 |
| Typesense (Hetzner) | CX31 | $48 |
| Checkly | Starter | $7 |
| Sentry | Team | $26 |
| Domain + DNS (Cloudflare) | طبقة مجانية | $0 |
| الإجمالي | ~$209/شهر |
خدمة 137,000 صفحة مع ملايين مرات الصفحة الشهرية لحوالي 200 دولار شهريًا. حاول القيام بذلك مع إعداد الخادم التقليدي الذي يشغل WordPress.
إذا كنت تفكر في مشروع مشابه وتريد فهم كيفية تخطيط عمارة كهذه لميزانيتك، فإن صفحة التسعير الخاصة بنا توضح كيف نقدم عادة مشاريع الدليل والسوق.
ما كنا سنفعله بشكل مختلف
ابدأ مع ISR من اليوم الأول. أضعنا أسبوعين محاولة جعل SSG الكامل يعمل قبل قبول الرياضيات لم تكن تضيف.
استخدم Typesense من البداية. كان Postgres FTS جيدًا مبكرًا، لكن نقل البحث في منتصف المشروع كان مزعجًا. كانت 48 دولار شهريًا تستحق من الإطلاق.
استثمر في التحقق من البيانات في وقت مبكر. مع 137K قائمة مستوردة من مصادر مختلفة، كانت جودة البيانات كابوسًا. كان يجب أن نبني مخططات Zod أكثر صرامة وأنابيب التحقق قبل الاستيراد الأول، وليس بعد أن وجدنا آلاف السجلات المكسورة في الإنتاج.
اختبر مع أحجام بيانات واقعية في التدريج. بيئة التدريج الخاصة بنا كانت بها 500 قائمة. كانت الاستعلامات التي عملت بشكل رائع على 500 صف تنهار عند 137K. نحن الآن نزرع التدريج مع عينة عشوائية 20% من بيانات الإنتاج.
إذا كنت تخطط لبناء دليل أو سوق وتريد تجنب هذه الأخطاء نفسها، تواصل مع فريقنا. مررنا بهذا عدة مرات كافية لنعرف أين الألغام الأرضية.
الأسئلة الشائعة
كم من الوقت يستغرق بناء دليل 100K+ قائمة مع Next.js؟ بالنسبة لفريقنا، استغرق الهندسة المعمارية الأولية والميزات الأساسية حوالي 10 أسابيع. أضاف الاستيراد والتنظيف والتحقق من البيانات 3-4 أسابيع إضافية. إجمالي من البداية إلى الإطلاق الإنتاجي كان تقريبًا 14 أسبوعًا. إذا كنت تعمل مع فريق تطوير Next.js الذي فعل هذا من قبل، يمكنك حلق 2-3 أسابيع من هذا.
هل يمكن لـ Supabase التعامل مع 100,000+ صفوف لدليل؟ بالتأكيد. يعمل Supabase على Postgres، والذي يتعامل مع ملايين الصفوف دون كسر التعرق. المفتاح هو الفهرسة الصحيحة — بدون فهارس في أعمدتك الأكثر استعلامًا، يتدهور الأداء بسرعة. مع الفهارس التي وصفناها أعلاه، تعود الاستعلامات على 137K صفوف باستمرار في أقل من 50ms لعمليات البحث عن السجل الواحد.
ما الفرق بين ISR و SSG بالنسبة للمواقع الكبيرة؟ SSG (Static Site Generation) ينشئ كل صفحة في وقت النشر. ISR (Incremental Static Regeneration) ينشئ مجموعة فرعية في وقت النشر وينشئ الباقي عند الطلب. بالنسبة للمواقع التي بها أكثر من ~10,000 صفحة، ISR مطلوب فعليًا — أصبح بناء SSG الكامل بطيئًا جدًا بحيث لا يمكن لدورات النشر المعقولة.
كيف تتعامل مع تحسين محركات البحث لـ 137,000 صفحة تم توليدها بشكل ديناميكي؟ ثلاثة أشياء مهمة جدًا: توليد خريطة الموقع الصحيحة المقسمة عبر ملفات متعددة، بيانات منظمة فريدة (JSON-LD) على كل صفحة قائمة، والتأكد من أن صفحات ISR التي تم توليدها تعود بأكواد حالة HTTP 200 الصحيحة (ليس 404 ناعمة). نحن أيضًا ننشئ عناوين meta فريدة ووصفية لكل صفحة باستخدام بيانات القائمة — لا محتوى meta مكرر.
هل ISR Vercel موثوق للإنتاج في الحجم؟ في تجربتنا، نعم. كنا نشغل هذا الإعداد لأكثر من 8 أشهر مع وقت تشغيل 99.98%. الحوادث الوحيدة كانت من صنعنا — نشر سيء أفسد webhook إعادة التحقق لدينا، وكوى صيانة Supabase واحدة تسببت في 15 دقيقة من البحث المتدهور. ذاكرة Vercel Edge محكمة.
هل يجب أن أستخدم Algolia أو Typesense لدليل كبير؟ هذا يعتمد على ميزانيتك. Algolia هي معيار الصناعة مع أفضل تجربة مطور، لكنها تصبح باهظة الثمن بعد 100K سجل — توقع 500-1000+ دولار شهريًا. يوفر Typesense 90% من الوظائف بجزء بسيط من التكلفة عند استضافته ذاتيًا. اخترنا Typesense ولم نندم عليها.
كيف تحتفظ بـ 137,000 قائمة محدثة؟ نحن نستخدم مزيجًا من الأساليب: إعادة التحقق عند الطلب التي تشغلها Supabase webhooks عندما تتغير القوائم الفردية، وإعادة التحقق ISR المستندة إلى الوقت (بالساعة) كشبكة أمان، ووظيفة دفعية ليلية تتحقق من البيانات القديمة وتشغل إعادة التحقق بالدفعات. يمكن لمالكي القوائم أيضًا طلب تحديث الصفحة يدويًا من خلال لوحة معلومات الخاصة بهم.
هل يمكن لهذه العمارة أن تعمل مع CMS بدون رأس بدلاً من Supabase؟ نعم، لكن مع المقايضات. إعداد headless CMS مثل Sanity أو Contentful يعمل بشكل جيد من جانب إدارة المحتوى، لكنك على الأرجح ستحتاج لا تزال إلى قاعدة بيانات للبحث والاستعلامات المعقدة. لقد بنينا مشاريع دليل حيث يقيم محتوى التحرير في CMS بدون رأس وبيانات القائمة تعيش في Postgres — إنها نهج هجينة صحيحة.