بناء منصة إيجار عطلات باستخدام Next.js و Supabase
الزائر الخاص بك يتمرر عبر العقار #47 عندما يتوقف تقويم التوفر لمدة ثلاث ثوانٍ. يغلقون التبويب. هذا 180 دولاراً من العمولة التي لن تراها مرة أخرى — وحدث ذلك لأن جدول الحجز الخاص بك تم قفله أثناء تحويل المنطقة الزمنية. لقد شهدت هذا يحدث عبر منصتين إيجار عطل مباشرة، كلاهما بني باستخدام Next.js و Supabase، كلاهما يعالج الدفعات الحقيقية من خلال Stripe Connect. واحد ضرب 3000 قائمة في ثمانية أشهر. الآخر كاد ينهار تحت 400 لأن المؤسس تخطى الأمان على مستوى الصف وسمح للضيوف برؤية تاريخ الحجز لبعضهم البعض. الفرق لم يكن الموهبة أو الميزانية — كانت قرارات مخطط قاعدة البيانات التي تم اتخاذها في الأسبوع الثاني. إليك البنية التي نجت، و Supabase triggers التي منعت الحجوزات المزدوجة في تمام الساعة 11 مساءً يوم السبت، وسبب تعامل هذا المكدس مع تعقيد الإيجار قصير الأجل بشكل أفضل من أحادي Rails الذي يوصي به الجميع.
من المتوقع أن يصل سوق الإيجار قصير الأجل إلى 113.9 مليار دولار بحلول عام 2027 (Statista). تملك Airbnb جزءاً ضخماً، لكن المنصات المتخصصة — الإقامات الصديقة للحيوانات الأليفة والفلل الفاخرة والانسحابات الريفية ومنازل الموجات — تزدهر لأنها تخدم جماهير محددة بشكل أفضل من أي متعددة الأغراض. لا تحتاج إلى هزيمة Airbnb. تحتاج إلى التفوق عليهم في مجال تخصصك.
جدول المحتويات
- لماذا Next.js + Supabase لمنصة الإيجار
- نظرة عامة على البنية المعمارية
- تصميم مخطط قاعدة البيانات
- بناء محرك الحجز
- البحث والتصفية باستخدام PostGIS
- المصادقة والوصول المتعدد الأدوار
- معالجة الدفع والتحويلات
- المراسلة الفورية والإخطارات
- التعامل مع الصور والأداء
- الاستقرار والاعتبارات الموسعة
- تفصيل التكلفة: ما ستنفقه بالفعل
- الأسئلة الشائعة

لماذا Next.js + Supabase لمنصة الإيجار
دعني أكون واضحاً: كان يمكنك بناء هذا باستخدام عشرات المجموعات المختلفة. Laravel و Rails و Django — جميع الخيارات الجيدة. لكن مزيج Next.js + Supabase يصل إلى مكان محبب لمنصات الإيجار على وجه التحديد.
يعطيك Next.js:
- عرض من جانب الخادم لـ SEO (يجب أن تصنف صفحات القوائم)
- App Router مع React Server Components للتحميلات الأولية السريعة
- مسارات API للمنطق الخلفي بدون خادم منفصل
- تحسين الصور مدمج (حرج لصور الممتلكات)
- إعادة الإنشاء الثابتة الإضافية لصفحات القوائم التي نادراً ما تتغير
يعطيك Supabase:
- PostgreSQL مع PostGIS للاستعلامات الجغرافية ("أظهر لي الإيجارات في غضون 20 كم")
- Row Level Security (RLS) التي تعمل فعلاً للتطبيقات متعددة المستأجرين
- الاشتراكات في الوقت الفعلي للمراسلة وتحديثات الحجز
- المصادقة المدمجة مع موفري OAuth
- التخزين لصور العقار مع تسليم CDN
- وظائف الحافة للمنطق التجاري بدون خادم
الميزة القاتلة الحقيقية هي أن Supabase مجرد Postgres تحت الغطاء. عندما تتجاوز عرض Supabase المدار (أو تحتاج إلى ذلك)، يمكنك الترحيل إلى أي مضيف Postgres. لا توجد حبس البائع على أغلى أصولك — بيانات الخاص بك.
إذا كنت تقيم أطر العمل، فقد شحن فريق تطوير Next.js عدة منصات على نفس المكدس بالضبط.
نظرة عامة على البنية المعمارية
إليك البنية عالية المستوى التي عملت بشكل جيد عبر عدة مشاريع:
┌─────────────────────────────────────────────┐
│ تطبيق Next.js │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ الصفحات│ │ مسارات │ │ مكون │ │
│ │ (SSR/ISR)│ │ API │ │ الخادم │ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼──────────────▼──────┐ │
│ │ Supabase Client SDK │ │
│ └────────────────┬────────────────────────┘ │
└───────────────────┼───────────────────────────┘
│
┌──────────▼──────────┐
│ Supabase │
│ ┌──────────────┐ │
│ │ PostgreSQL │ │
│ │ + PostGIS │ │
│ ├──────────────┤ │
│ │ المصادقة │ │
│ ├──────────────┤ │
│ │ التخزين │ │
│ ├──────────────┤ │
│ │ الوقت الفعلي │ │
│ ├──────────────┤ │
│ │ وظائف الحافة │ │
│ └──────────────┘ │
└─────────────────────┘
│
┌──────────▼──────────┐
│ الخدمات الخارجية │
│ • Stripe Connect │
│ • Mapbox/Google │
│ • SendGrid/Resend │
│ • Cloudflare CDN │
└─────────────────────┘
قرار البنية الرئيسي هو استخدام Supabase Edge Functions للعمليات الحاسمة للأعمال مثل إنشاء الحجز والويب كروك للدفع، مع الحفاظ على مسارات API Next.js للمهام الخفيفة مثل استعلامات البحث والتحقق من النماذج. يهم هذا الفصل عندما يتم إطلاق webhook Stripe وتحتاج إلى ضمان التحديثات الذرية لحالة الحجز.
تصميم مخطط قاعدة البيانات
هذا هو المكان الذي تخطئ فيه معظم منصات الإيجار مبكراً وتدفع ثمنه لاحقاً. إليك مخطط نجا من حركة الإنتاج:
-- تفعيل PostGIS
create extension if not exists postgis;
-- توسيع ملفات التعريف لـ Supabase auth.users
create table public.profiles (
id uuid references auth.users on delete cascade primary key,
full_name text not null,
avatar_url text,
phone text,
role text check (role in ('guest', 'host', 'admin')) default 'guest',
stripe_account_id text, -- لصرف المضيف
identity_verified boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- الخصائص / القوائم
create table public.properties (
id uuid default gen_random_uuid() primary key,
host_id uuid references public.profiles(id) not null,
title text not null,
slug text unique not null,
description text,
property_type text check (property_type in (
'apartment', 'house', 'villa', 'cabin', 'unique'
)),
max_guests int not null default 1,
bedrooms int not null default 0,
beds int not null default 1,
bathrooms numeric(3,1) not null default 1,
amenities text[] default '{}',
-- التسعير
base_price_cents int not null,
cleaning_fee_cents int default 0,
currency text default 'USD',
-- الموقع
address_line1 text,
city text not null,
state text,
country text not null,
postal_code text,
location geography(Point, 4326), -- PostGIS
-- الحالة
status text check (status in ('draft', 'listed', 'unlisted', 'suspended'))
default 'draft',
-- السياسات
cancellation_policy text check (cancellation_policy in (
'flexible', 'moderate', 'strict'
)) default 'moderate',
check_in_time time default '15:00',
check_out_time time default '11:00',
min_nights int default 1,
max_nights int default 365,
-- البيانات الوصفية
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- فهرس مكاني للاستعلامات الجغرافية
create index properties_location_idx
on public.properties using gist (location);
-- التوفر والتسعير يتجاوز
create table public.availability (
id uuid default gen_random_uuid() primary key,
property_id uuid references public.properties(id) on delete cascade,
date date not null,
available boolean default true,
price_override_cents int, -- null = استخدم السعر الأساسي
min_nights_override int,
unique(property_id, date)
);
-- الحجوزات
create table public.bookings (
id uuid default gen_random_uuid() primary key,
property_id uuid references public.properties(id) not null,
guest_id uuid references public.profiles(id) not null,
check_in date not null,
check_out date not null,
guests_count int not null default 1,
-- لقطة التسعير (ثابتة بعد الإنشاء)
nightly_rate_cents int not null,
nights int not null,
subtotal_cents int not null,
cleaning_fee_cents int not null,
service_fee_cents int not null,
total_cents int not null,
currency text default 'USD',
-- الحالة
status text check (status in (
'pending', 'confirmed', 'cancelled', 'completed', 'disputed'
)) default 'pending',
-- الدفع
stripe_payment_intent_id text,
stripe_transfer_id text,
paid_at timestamptz,
-- الطوابع الزمنية
created_at timestamptz default now(),
updated_at timestamptz default now(),
-- منع الحجوزات المزدوجة على مستوى قاعدة البيانات
exclude using gist (
property_id with =,
daterange(check_in, check_out) with &&
) where (status in ('pending', 'confirmed'))
);
هذا القيد exclude على جدول الحجوزات؟ هذا هو الخط الأكثر أهمية في المخطط بأكمله. يمنع الحجوزات المزدوجة على مستوى قاعدة البيانات باستخدام قيد استبعاد GiST. لا توجد حالات سباق. لا توجد رسائل بريد إلكترونية "آسف، شخص ما احتجزها قبل ثانيتين". قاعدة البيانات ببساطة لن تسمح بنطاقات تاريخ متداخلة لنفس الخاصية.
ستحتاج إلى امتداد btree_gist:
create extension if not exists btree_gist;

بناء محرك الحجز
تدفق الحجز هو قلب أي منصة إيجار. إليك كيفية هيكلتها:
الخطوة 1: فحص التوفر
// lib/bookings/check-availability.ts
import { createClient } from '@/lib/supabase/server';
export async function checkAvailability(
propertyId: string,
checkIn: string,
checkOut: string
) {
const supabase = await createClient();
// التحقق من التواريخ المحجوزة
const { data: blockedDates } = await supabase
.from('availability')
.select('date')
.eq('property_id', propertyId)
.eq('available', false)
.gte('date', checkIn)
.lt('date', checkOut);
if (blockedDates && blockedDates.length > 0) {
return { available: false, reason: 'بعض التواريخ غير متاحة' };
}
// التحقق من الحجوزات الموجودة (الحزام + الدعامات مع قيد قاعدة البيانات)
const { data: conflicts } = await supabase
.from('bookings')
.select('id')
.eq('property_id', propertyId)
.in('status', ['pending', 'confirmed'])
.or(`check_in.lt.${checkOut},check_out.gt.${checkIn}`);
if (conflicts && conflicts.length > 0) {
return { available: false, reason: 'التواريخ مُحجوزة بالفعل' };
}
return { available: true };
}
الخطوة 2: حساب السعر
لا تثق أبداً بحسابات الأسعار من جانب العميل. أعد دائماً الحساب على الخادم:
// lib/bookings/calculate-price.ts
export async function calculateBookingPrice(
propertyId: string,
checkIn: string,
checkOut: string
) {
const supabase = await createClient();
const { data: property } = await supabase
.from('properties')
.select('base_price_cents, cleaning_fee_cents')
.eq('id', propertyId)
.single();
if (!property) throw new Error('الخاصية غير موجودة');
// احصل على أي تجاوزات سعر لهذه التواريخ
const { data: overrides } = await supabase
.from('availability')
.select('date, price_override_cents')
.eq('property_id', propertyId)
.gte('date', checkIn)
.lt('date', checkOut)
.not('price_override_cents', 'is', null);
const overrideMap = new Map(
overrides?.map(o => [o.date, o.price_override_cents]) ?? []
);
// حساب التسعير ليلة بليلة
let subtotal = 0;
const start = new Date(checkIn);
const end = new Date(checkOut);
const nights = Math.round(
(end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)
);
for (let i = 0; i < nights; i++) {
const current = new Date(start);
current.setDate(current.getDate() + i);
const dateStr = current.toISOString().split('T')[0];
const rate = overrideMap.get(dateStr) ?? property.base_price_cents;
subtotal += rate;
}
const serviceFee = Math.round(subtotal * 0.12); // 12% رسم الخدمة
const total = subtotal + property.cleaning_fee_cents + serviceFee;
return {
nights,
nightlyRate: property.base_price_cents,
subtotal,
cleaningFee: property.cleaning_fee_cents,
serviceFee,
total,
};
}
الخطوة 3: إنشاء حجز مع نية الدفع
هذا هو المكان الذي يدخل فيه Stripe. أستخدم Server Action في Next.js 14+:
// app/actions/create-booking.ts
'use server';
import Stripe from 'stripe';
import { createClient } from '@/lib/supabase/server';
import { calculateBookingPrice } from '@/lib/bookings/calculate-price';
import { checkAvailability } from '@/lib/bookings/check-availability';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createBooking(formData: FormData) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('غير مصرح');
const propertyId = formData.get('propertyId') as string;
const checkIn = formData.get('checkIn') as string;
const checkOut = formData.get('checkOut') as string;
const guestsCount = parseInt(formData.get('guests') as string);
// أعد التحقق من التوفر
const availability = await checkAvailability(propertyId, checkIn, checkOut);
if (!availability.available) {
return { error: availability.reason };
}
// أعد حساب السعر على جانب الخادم
const pricing = await calculateBookingPrice(propertyId, checkIn, checkOut);
// إنشاء نية الدفع Stripe
const paymentIntent = await stripe.paymentIntents.create({
amount: pricing.total,
currency: 'usd',
metadata: {
propertyId,
checkIn,
checkOut,
guestId: user.id,
},
});
// إدراج الحجز
const { data: booking, error } = await supabase
.from('bookings')
.insert({
property_id: propertyId,
guest_id: user.id,
check_in: checkIn,
check_out: checkOut,
guests_count: guestsCount,
nightly_rate_cents: pricing.nightlyRate,
nights: pricing.nights,
subtotal_cents: pricing.subtotal,
cleaning_fee_cents: pricing.cleaningFee,
service_fee_cents: pricing.serviceFee,
total_cents: pricing.total,
stripe_payment_intent_id: paymentIntent.id,
status: 'pending',
})
.select()
.single();
if (error) {
// سيلتقط قيد الاستبعاد الحجوزات المزدوجة
if (error.code === '23P01') {
return { error: 'تم احتجاز هذه التواريخ للتو من قبل شخص آخر.' };
}
throw error;
}
return {
bookingId: booking.id,
clientSecret: paymentIntent.client_secret,
};
}
لاحظ كيف نتعامل مع رمز الخطأ 23P01 — هذا انتهاك الاستبعاد في PostgreSQL. حتى إذا نقر مستخدمان على "الحجز" في نفس جزء الألف من الثانية، يتم تمرير حجز واحد فقط.
البحث والتصفية باستخدام PostGIS
البحث الجغرافي غير قابل للتفاوض لمنصات الإيجار. إليك دالة Postgres التي تتعامل مع البحث المستند إلى نصف القطر مع المرشحات:
create or replace function search_properties(
lat double precision,
lng double precision,
radius_km int default 50,
min_price int default 0,
max_price int default 100000,
min_bedrooms int default 0,
guest_count int default 1,
p_check_in date default null,
p_check_out date default null
)
returns setof public.properties
language sql stable
as $$
select p.*
from public.properties p
where p.status = 'listed'
and ST_DWithin(
p.location,
ST_MakePoint(lng, lat)::geography,
radius_km * 1000
)
and p.base_price_cents between min_price and max_price
and p.bedrooms >= min_bedrooms
and p.max_guests >= guest_count
and (
p_check_in is null
or not exists (
select 1 from public.bookings b
where b.property_id = p.id
and b.status in ('pending', 'confirmed')
and b.check_in < p_check_out
and b.check_out > p_check_in
)
)
order by p.location <-> ST_MakePoint(lng, lat)::geography
limit 50;
$$;
استدعها من Next.js:
const { data } = await supabase.rpc('search_properties', {
lat: 34.0522,
lng: -118.2437,
radius_km: 30,
guest_count: 4,
p_check_in: '2026-08-01',
p_check_out: '2026-08-07',
});
يعمل هذا في أقل من 50 ملي ثانية لـ 100 ألف+ قوائم مع الفهرسة المناسبة. لا توجد حاجة إلى Elasticsearch حتى تصل إلى نطاق أكبر بكثير.
المصادقة والوصول المتعدد الأدوار
تتعامل Supabase Auth برفع الأثقال. الجزء الصعب هو الطبيعة ثنائية الدور لمنصات الإيجار — يمكن لشخص ما أن يكون ضيفاً ومضيفاً.
أتعامل مع هذا بحقل دور على الملف الشخصي الذي يتم ترقيته من guest إلى host عندما يقومون بإنشاء قوائمهم الأولى، بالإضافة إلى سياسات Row Level Security:
-- يمكن للمضيفين فقط تحرير خصائصهم الخاصة
create policy "hosts_manage_own_properties" on public.properties
for all using (host_id = auth.uid());
-- يمكن للضيوف عرض الخصائص المدرجة
create policy "anyone_view_listed" on public.properties
for select using (status = 'listed');
-- يمكن للضيوف فقط رؤية حجوزاتهم الخاصة
create policy "guests_own_bookings" on public.bookings
for select using (guest_id = auth.uid());
-- يمكن للمضيفين رؤية الحجوزات لخصائصهم
create policy "hosts_property_bookings" on public.bookings
for select using (
property_id in (
select id from public.properties where host_id = auth.uid()
)
);
RLS بصراحة أحد أقوى ميزات Supabase للتطبيقات متعددة المستأجرين. قواعد الأمان تعيش بجانب البيانات، وليس مشتتة عبر برنامج middleware API.
معالجة الدفع والتحويلات
استخدم Stripe Connect. نقطة كاملة. يتعامل مع الدفعات في السوق والانقسامات و 1099s والتحقق من الهوية والتحويلات الدولية. البديل هو بناء نظام نقل أموال خاص بك، وهو... لا تفعل ذلك.
إليق التدفق:
- يتم إدارة المضيف عبر Stripe Connect Express (Stripe يتعامل مع واجهة مستخدم التحقق من الهوية)
- يدفع الضيف عبر نيات الدفع Stripe
- يتم الاحتفاظ بالدفع حتى الوصول + 24 ساعة
- تحويل المكافآت إلى المضيف ناقصاً رسم النظام الأساسي
تسعير Stripe Connect في 2026: 0.25% + 0.25 دولار لكل تحويل بالإضافة إلى رسوم المعالجة القياسية (2.9% + 0.30 دولار لكل رسم). لحجز بقيمة 200 دولار في الليلة، تبحث تقريباً عن 6.50 دولار في رسوم Stripe. ميزانية لذلك.
المراسلة الفورية والإخطارات
جعل Supabase Realtime مراسلة المضيف والضيف مباشرة:
// الاشتراك في الرسائل الجديدة في محادثة
const channel = supabase
.channel(`conversation:${conversationId}`)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `conversation_id=eq.${conversationId}`,
},
(payload) => {
setMessages(prev => [...prev, payload.new]);
}
)
.subscribe();
لإخطارات البريد الإلكتروني (تأكيدات الحجز وتذكيرات الوصول)، أستخدم Resend أو SendGrid التي يتم تشغيلها من Supabase Edge Functions عبر webhooks قاعدة البيانات. يبدأ تسعير Resend بـ 20 دولار / شهر لـ 50 ألف بريد إلكتروني — أكثر من كافٍ لمنصة متنامية.
التعامل مع الصور والأداء
صور الممتلكات تشكل أو تكسر معدلات التحويل. قد تحتوي كل قائمة على 15-30 صورة، وتحتاج إلى التحميل بسرعة.
نهجي:
- تحميل الأصول إلى Supabase Storage
- استخدم Supabase Image Transformation API لتغيير الحجم في الوقت الفعلي
- Serve عبر مكون
<Image>في Next.js معsizesوsrcSetالمناسبة - تحميل كسول كل شيء أسفل الطي
- استخدم
blurالعنصر النائب مع معاينة base64 صغيرة تم إنشاؤها في وقت التحميل
<Image
src={`${SUPABASE_URL}/storage/v1/render/image/public/properties/${imageId}?width=800&quality=80`}
alt={property.title}
width={800}
height={600}
placeholder="blur"
blurDataURL={image.blur_hash}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
يوفر هذا النهج LCP أقل من ثانية على صفحات القوائم مع 20+ صور.
الاستقرار والاعتبارات الموسعة
للنشر، Vercel هو الخيار الطبيعي لـ Next.js. لكن هناك تفصيل دقيق تتخطاه معظم المقالات: استخدم Vercel Edge Runtime بشكل متقطع لمنصة إيجار. يحتاج تدفق الحجز إلى وقت التشغيل Node.js لـ Stripe SDK والعمليات المعقدة على قاعدة البيانات. Edge رائع للبرنامج الوسيط (إعادة التوجيه الجغرافي وفحوصات المصادقة) لكن ليس للمنطق التجاري.
| خيار النشر | الأفضل لـ | تقدير التكلفة الشهرية |
|---|---|---|
| Vercel Pro + Supabase Pro | MVP إلى 10K MAU | $45 - $100 |
| Vercel Pro + Supabase Team | 10K-100K MAU | $200 - $500 |
| Next.js ذاتية الاستضافة + Supabase Pro | تحسين التكلفة | $100 - $300 |
| AWS/GCP + Supabase ذاتية الاستضافة | التحكم الكامل بالحجم | $500+ |
يبدأ Supabase Pro بـ 25 دولار / شهر لكل مشروع ويتضمن 8 جيجابايت قاعدة بيانات و 250 جيجابايت عرض النطاق الترددي و 100 جيجابايت التخزين. كافٍ لمعظم أنظمة MVP والمنصات في مراحلها الأولى.
تفصيل التكلفة: ما ستنفقه بالفعل
إليق ما تكلفه منصة إيجار عطلات حقيقية بناؤها وتشغيلها في 2026:
| البند | تكلفة MVP | التكلفة الشهرية الجارية |
|---|---|---|
| Supabase Pro | - | $25 |
| Vercel Pro | - | $20 |
| Stripe Connect | - | ~2.9% + 0.30 دولار / معاملة |
| Mapbox / Google Maps | - | $0-200 (مستند الاستخدام) |
| Resend (البريد الإلكتروني) | - | $20 |
| المجال + DNS (Cloudflare) | 15 دولار / سنة | $0 |
| التطوير (الوكالة) | 40 ألف - 120 ألف دولار | - |
| التطوير (مطور واحد) | 15 ألف - 40 ألف دولار | - |
| إجمالي البنية التحتية الشهرية | - | ~$65-265 |
قارن ذلك ببناء منصة SaaS مثل Sharetribe ($299-599/month) أو Guesty، وتبدأ الاقتصاديات للتطوير المخصص في الحصول على المعنى عندما يكون لديك أي جر حقيقي.
إذا كنت جادين بشأن بناء منصة إيجار وتريد مطورين متمرسين الذين شحنوا هذا النوع بالضبط من المنتج، تواصل مع فريقنا أو تحقق من صفحة التسعير لتقدير المشروع. نتخصص في تطوير CMS بدون رأس وتطبيقات Next.js المعقدة.
الأسئلة الشائعة
كم من الوقت يستغرق بناء منصة إيجار عطلات مثل Airbnb؟ يستغرق MVP عملي مع القوائم والبحث والحجوزات والدفع والمراسلة 3-5 أشهر مع فريق ماهر من 2-3 مطورين. قد يحتاج مطور واحد إلى 6-9 أشهر. هذا يوصلك إلى الإطلاق — وليس تكافؤ الميزات مع Airbnb، التي تقف وراءها 15+ سنة من التطوير. ركز أولاً على ميزات مكانتك.
هل Supabase قابلة للتوسع بما يكفي لمنصة إيجار إنتاجية؟ نعم، إلى نقطة معينة. يتعامل Supabase Pro بسهولة مع عشرات الآلاف من المستخدمين المتزامنين. خطتهم Team ($599/month) تدعم بكثير أكثر. قادت Instagram على خادم PostgreSQL واحد لسنوات. اختناقك سيكون في ملاءمة السوق وتوفر المنتج قبل فترة طويلة من أن يكون حجم قاعدة البيانات. عندما تتجاوز Supabase، بيانات الخاصة بك في Postgres القياسي — الهجرة مباشرة.
كيف تمنع الحجوزات المزدوجة في نظام إيجار عطلة؟
استخدم قيود استبعاد PostgreSQL مع امتداد btree_gist. يفرض هذا على مستوى قاعدة البيانات أنه لا يمكن لحجزين نشطين أن يكون لديهما نطاقات تاريخ متداخلة لنفس الخاصية. إنها الطريقة الوحيدة الموثوقة — فحوصات مستوى التطبيق لديها حالات السباق. يوضح مثال المخطط أعلاه بالضبط كيفية تنفيذ هذا.
هل يجب أن أستخدم Stripe Connect أو أنشئ نظام الدفع الخاص بي؟ Stripe Connect. دائما. بناء نظام تقسيم الدفع في السوق الخاص بك ينطوي على تراخيص نقل الأموال وامتثال KYC/AML وإعداد الضرائب الدولية ومنع الاحتيال. يتعامل Stripe مع كل هذا. الرسوم (تقريباً 3.2% لكل معاملة) تستحق ذلك. يمكنك دائماً التفاوض على معدلات بمجرد معالجة حجم كبير.
ما هي أفضل طريقة للتعامل مع خرائط البحث عن العقار؟ PostGIS مع Supabase للاستعلامات الخلفية، و Mapbox GL JS أو Google Maps JavaScript API للواجهة الأمامية. تتعامل استعلامات PostGIS المكانية مع نصف القطر والاستعلامات في صندوق الحدود في ميلي ثانية مع الفهارس الصحيحة. يبدأ تسعير Mapbox بطبقة سخية مجانية (50K حمل خريطة / شهر). يتقاضى Google Maps 7 دولارات لكل 1000 حمل خريطة ديناميكي بعد الرصيد الشهري $200.
كيف أتعامل مع التسعير الموسمي والأسعار الديناميكية؟ استخدم جدول توفر / تسعير override مستند إلى التاريخ بجانب سعر الخاصية الأساسي. لكل ليلة من الحجز، تحقق من وجود تجاوز سعر لهذا التاريخ المحدد. إذا لم يكن كذلك، فارجع إلى السعر الأساسي. يتعامل هذا مع معدلات موسمية وأقساط عطلة نهاية أسبوع وتسعير العطلات والخصومات في اللحظة الأخيرة. تتضمن بعض المنصات أيضاً في PriceLabs ($19.99/listing/month) أو Beyond Pricing لتسعير ديناميكي مؤتمت.
هل Next.js أفضل من Astro لمنصة إيجار؟ بالنسبة لمنصة إيجار كاملة مع تدفقات حجز تفاعلية والمراسلة والمجالس — يفوز Next.js. يحتاج التطبيق إلى تفاعل كبير من جانب العميل. Astro متفوق في المواقع الغنية بالمحتوى مع التفاعل الضئيل (تحقق من قدرات تطوير Astro). بقول ذلك، إذا كنت تبني موقع قوائم فقط بدون حجوزات (مثل دليل)، فإن أداء Astro ستكون رائعة.
هل أحتاج إلى تطبيقات React Native أيضاً — أم يمكنني البدء بـ Web؟ ليس لـ MVP الخاص بك. بناء تطبيق Next.js كـ PWA تفاعلي (Progressive Web App) أولاً. أضف إشعارات الدفع والتخزين المؤقت غير متصل وموجه "أضف إلى شاشة البداية". يغطي هذا 80% من حالات استخدام الجوال. بمجرد التحقق من صحة المنتج والحصول على عائد حقيقي، استثمر في التطبيقات الأصلية. تم إطلاق العديد من منصات الإيجار المتخصصة بنجاح (Hipcamp و Glamping Hub) على الويب أولاً وأضافت التطبيقات الأصلية لاحقاً.