چندمستأجری در App Router از میدلور شروع میشود، از شعار مقیاس نه
چندمستأجری یعنی چند سازمان از یک استقرار سرویس بگیرند و دادهشان قاطی نشود. شبیه جمله بازاریابی نیست. یک قرارداد است: هر درخواست باید بداند مال کدام مستأجر است، و هر خواندن و نوشتن باید همان مرز را رعایت کند. Next.js با App Router این کار را ممکن میکند. «هزاران سازمان» را تضمین نمیکند. اگر مرز را اشتباه بکشی، با ده مستأجر هم حادثه داری.
Pages Router برای این شکل، مسیر دینامیک را با دور زدن زیاد حل میکرد. App Router سگمنت تو در تو، layout، و Server Component را بومیتر دارد. این مزیت واقعی است. جایگزین طراحی داده نیست.
میدلور کنترل ترافیک است، نه محل کسبوکار
`middleware.ts` قبل از رسیدن درخواست به صفحه اجرا میشود. برای تشخیص مستأجر از زیردامنه یا مسیر، برای رد کردن دامنه امن، و برای فرستادن کاربر لاگیننشده به ورود، جای درست شروع است. روی Edge Runtime اجرا میشود. یعنی شروع سرد کوتاه و سطح API محدود. جای کوئری سنگین، ORM، و قانون پیچیده صورتحساب نیست.
دامنه امن را صریح فهرست کن. `www`، `api`، و `docs` مستأجر نیستند. اگر هر زیردامنه را کور به سازمان ترجمه کنی، روزی دامنه خودت را به داده یک مشتری میچسبانی یا برعکس. میزبان را با یک فهرست کوچک و یک نگاشت روشن بخوان. چیزی که در میدلور حل نمیشود را به لایه بعد بسپار، نه اینکه میدلور را به برنامه دوم تبدیل کنی.
اگر محصول زبان هم دارد، تشخیص زبان را کنار تشخیص مستأجر بنویس تا ترتیب ریدایرکت تصادفی نباشد. یک درخواست نباید اول به خاطر زبان برود یک مسیر و بعد به خاطر مستأجر به مسیر دیگر، مگر اینکه هر دو قانون را روی کاغذ کشیده باشی. ساختار پوشهای مثل `[lang]` و `[tenant]` وقتی معنی دارد که URL واقعاً هر دو را دارد. اگر زبان فقط یک ترجیح کاربر است، سگمنت اجباری نساز.
توسعه محلی اینجا دروغ میگوید چون `localhost` زیردامنه واقعی ندارد. یا میزبان محلی را پیکربندی کن یا یک تونل موقت. وگرنه باگ مسیریابی را اولین بار در پروداکشن میبینی.
مرز را در layout و در کوئری تکرار کن
سگمنت `[tenant]` در App Router مسیر را تمیز میکند. تمیز بودن مسیر، جداسازی داده نیست. layout مستأجر جای خوبی برای پوسته، نام تجاری، و خواندن تنظیم همان سازمان روی سرور است. Server Component این خواندن را بدون باندل کردن کل کاتالوگ در کلاینت انجام میدهد. هنوز باید هر کوئری، شناسه مستأجر را در شرط داشته باشد.
Server Action و Route Handler هم همین زمینه را میخواهند. شناسه را از بدنه فرم که کلاینت فرستاده بهتنهایی نپذیر. میزبان یا نشست را روی سرور دوباره به مستأجر وصل کن و بعد جهش را اجرا کن. احراز هویت میگوید این کاربر کیست. مجوز میگوید این کاربر در این مستأجر چه کاره است. یکی از این دو کافی نیست.
کوکی نشست اگر بین زیردامنهها مشترک است، دامنه کوکی و پرچم امن را عمداً انتخاب کن. اشتراک بیفکر، نشست یک مستأجر را به میزبان مستأجر دیگر میبرد. جداسازی بیش از حد هم ورود را برای دامنه اصلی میشکند. این تصمیم محصول است و باید یک جا نوشته شود.
کش بدون کلید مستأجر، نشت است
کش App Router و CDN وقتی خطرناک میشود که کلید، مستأجر را نداشته باشد. صفحه تنظیمات سازمان الف اگر برای سازمان ب سرو شود، دیگر باگ عملکرد نیست. حادثه داده است. هر تگ باطلسازی و هر کلید `fetch` باید مرز سازمان را داخل خودش داشته باشد. داده مشترک واقعاً عمومی، مثل صفحه بازاریابی دامنه اصلی، سیاست جدا دارد. آن را با صفحه داخل محصول قاطی نکن.
«بهینه برای مستأجر پرترافیک» بعد از درست بودن مرز معنی دارد. استخر اتصال، کوئری باریک، و در صورت نیاز رپلیکا خواندن، ابزار مقیاساند. اگر یک کوئری بدون فیلتر مستأجر در مسیر باشد، رپلیکا فقط نشت را سریعتر تکرار میکند.
نقشها را هم داخل همان مرز بگذار. ادمین سازمان الف نباید با همان نشست، ردیف سازمان ب را ببیند، حتی اگر شناسه را حدس بزند. چک را در دسترسی داده انجام بده، نه فقط با قایم کردن دکمه در UI.
استقرار
زیردامنه عام به اپ باید یک تصمیم DNS و یک تصمیم پلتفرم باشد، نه یادداشت انتهای پروژه. محیطهایی که ترافیک واقعی میگیرند را بشمار: پروداکشن، پیشنمایش طولانی با دسترسی مشتری، و هر نسخه خودمیزبان. پیشنمایش فراموششده اغلب جایی است که قانون مستأجر قدیمی هنوز زنده است.
متغیر محیط را برای راز سراسری بگذار. پیکربندی خاص هر مستأجر را اگر داخل محیط کپی میکنی، با اضافه شدن سازمان صدم از پا درمیآیی. آن پیکربندی مال داده است، با مالک و ممیزی، نه مال فایل env بینهایت.
چندمستأجری بالغ میشود. قانون اول عوض نمیشود: هیچ درخواست، هیچ کوئری، و هیچ کشی بدون مرز مستأجر قابل اعتماد نیست. میدلور این مرز را زود پیشنهاد میکند. دیتابیس و اکشن باید همان را دوباره ثابت کنند.
داده را از روز اول با فرض نشت طراحی کن
یک آزمون ساده برای هر endpoint بنویس: با نشست سازمان الف، شناسه رکورد سازمان ب را بزن و انتظار ممنوع یا خالی داشته باش. این تست از تست خوشمسیر مهمتر است چون مسیر خوش با هر معماری شل هم سبز میشود. نام مستأجر را در لاگ خطای ۵۰۰ هم سانسور نکن تا حدی که دیباگ بمیرد، ولی بدنه رکورد سازمان دیگر هرگز نباید در پاسخ یا در پیام خطا باشد.
اگر بعداً به چند منطقه یا چند دیتابیس رسیدی، همان شناسه مستأجر باید در مرز شبکه هم قابل توضیح باشد. معماری را با وعده «هزار مشتری» شروع نکن. با این شروع کن که دو مشتری نتوانند ردیف هم را ببینند، کش هم را بگیرند، یا با کوکی هم وارد شوند.
پرسشهای کوتاه
مسیر بهتر است یا زیردامنه؟ زیردامنه وقتی هر سازمان باید حس میزبان جدا بدهد. مسیر وقتی محصول یک دامنه است و جداسازی بیشتر قراردادی است تا ظاهری. هر دو به فیلتر داده نیاز دارند.
منطق صورتحساب را در middleware بگذارم؟ نه. میدلور را کوتاه نگه دار. صورتحساب به داده و زمان و ابزار نیاز دارد که Edge جای مناسبی برایشان نیست.
شناسه مستأجر در localStorage کافی است؟ برای رنگ دکمه شاید. برای امنیت هرگز. تصمیم دسترسی را روی سرور از میزبان یا نشست بگیر.