گام به گام: اپلیکیشن Multi-Tenant با Next.js

Next.js اکنون به شما این امکان را می دهد که به راحتی با استفاده از زیر دامنه ها یک برنامه چند مستاجر ایجاد کنید. این به شما امکان میدهد تا برنامههای وب مانند Linktree، Super.so و سایر برنامهها را ایجاد کنید که به عنوان مثال کاربر صفحه وب خود را دریافت میکند.
قبل از شروع، در اینجا چند منبع اضافی وجود دارد:
مرحله 1: یک برنامه Next.js خالی ایجاد کنید
npx create-next-app
از شما پرسیده می شود که آیا Typescript، ESLint و گزینه های دیگر را می خواهید. برای همه چیز بله را بزنید.
پس از ایجاد برنامه، آن را در ویرایشگر کد خود (VSCode) باز کنید.
مرحله 2: استقرار برنامه در Vercel
می توانید با باز کردن ترمینال (Command + J در مک) داخل VSCode را انجام دهید.
Vercel CLI را نصب کنید:npm i -g vercel
پس از انجام این کار، ادامه دهید و اجرا کنید:vercel --prod
تا آن را در تولید مستقر کند
روی پیوند استقرار کلیک کنید تا در داشبورد Vercel قرار بگیرید.
مرحله 3: دامنه خود را راه اندازی کنید
برای استفاده از ویژگی زیر دامنه در Vercel، باید دامنه wildcard خود را تنظیم کنید. اگر شما یک توسعه دهنده هستید، باید دامنه های استفاده نشده زیادی داشته باشید 😉
مال من buildwithnext.com نام دارد
من از Namecheap استفاده می کنم، بنابراین در اینجا نحوه انجام آن به شرح زیر است:
در داشبورد Vercel دامنه نویسه خود را اضافه کنید
سپس وارد حساب Namecheap خود شوید و آدرس های DNS Nameserver زیر را اضافه کنید
هنگامی که یک دامنه حروف اضافه می کنید، Vercel به طور خودکار همه دامنه های دیگر را برای شما پر می کند.
مرحله 4: راه اندازی مسیریابی در Next.js
یک پوشه جدید در پوشه pages به نام اضافه کنید _sites
و سپس یکی دیگر در آنجا زنگ زد [site]
. سپس یک فایل index.tsx را به آن اضافه کنید [site]
پوشه
ساختار پوشه جدید شما اکنون باید به شکل زیر باشد:
pages
└───api
└───sites
│ │
│ [site]
│ │ index.tsx
package.json
etc
داخل فایل index.tsx کد زیر را اضافه کنید:
import { useRouter } from "next/router";
// we will create these in the next step
import { getHostnameDataBySubdomain, getSubdomainPaths } from "@/lib/db";
// Our types for the site data
export interface Props {
name: String
description: String
subdomain: String
customDomain: String
}
export default function Index(props: Props) {
const router = useRouter()
if (router.isFallback) {
return (
<>
<p>
Loading...
</p>
</>
)
}
return (
<>
<h1>
{props.name}
</h1>
</>
)
}
// Getting the paths for all the subdomains in our database
export async function getStaticPaths() {
const paths = await getSubdomainPaths()
return {
paths,
fallback: true
}
}
// Getting data to display on each custom subdomain
export async function getStaticProps({ params: { site } }) {
const sites = await getHostnameDataBySubdomain(site)
return {
props: sites,
revalidate: 3600
}
}
مرحله 5: افزودن میان افزار و داده های ساختگی
یک فایل میانافزار به ما این امکان را میدهد که تماسهای api به باطن خود را رهگیری کنیم و کاری با آن انجام دهیم. در این مورد ما از میان افزار استفاده می کنیم تا مشخص کنیم که فراخوانی api برای چه نام میزبانی انجام می شود و داده های صحیح را نمایش می دهیم.
بیایید یک را ایجاد کنیم middleware.ts
فایل در ریشه دایرکتوری پروژه ما
pages
└───api
└───sites
│ │
│ [site]
│ │ index.tsx
middleware.ts
package.json
etc
در داخل فایل Middleware.ts این کد را اضافه کنید:
import { NextRequest, NextResponse } from 'next/server'
import { getHostnameDataOrDefault } from './lib/db'
export const config = {
matcher: ["https://dev.to/", "https://dev.to/about", '/_sites/:path'],
}
export default async function middleware(req: NextRequest) {
const url = req.nextUrl
// Get hostname (e.g. vercel.com, test.vercel.app, etc.)
const hostname = req.headers.get('host')
// If localhost, assign the host value manually
// If prod, get the custom domain/subdomain value by removing the root URL
// (in the case of "subdomain-3.localhost:3000", "localhost:3000" is the root URL)
// process.env.NODE_ENV === "production" indicates that the app is deployed to a production environment
// process.env.VERCEL === "1" indicates that the app is deployed on Vercel
const currentHost =
process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
? hostname
.replace(`.buildwithnext.com`, "")
: hostname.replace(`.localhost:3000`, "");
const data = await getHostnameDataOrDefault(currentHost)
// Prevent security issues – users should not be able to canonically access
// the pages/sites folder and its respective contents.
if (url.pathname.startsWith(`/_sites`)) {
url.pathname = `/404`
} else {
// console.log('URL 2', req.nextUrl.href)
// rewrite to the current subdomain under the pages/sites folder
url.pathname = `/_sites/${data.subdomain}${url.pathname}`
}
return NextResponse.rewrite(url)
}
اینجا اتفاقات زیادی می افتد، اما من سعی کردم نظرات بیشتری اضافه کنم تا همه چیز روشن شود. ممکن است در ابتدا ترسناک به نظر برسد، اما سعی کنید خط به خط بروید و نظرات را بخوانید. در نهایت معنا پیدا خواهد کرد.
حالا بیایید جلو برویم و یک پوشه lib در ریشه دایرکتوری پروژه خود و یک فایل db.ts در داخل آن اضافه کنیم.
lib
└───db.ts
pages
└───api
└───sites
│ │
│ [site]
│ │ index.tsx
middleware.ts
package.json
etc
داخل فایل db.ts کد زیر را اضافه کنید:
// Dummy data to be replaced with your database
const hostnamesDB = [
{
name: 'This is Site 1',
description: 'Subdomain + custom domain',
subdomain: 'test1',
customDomain: 'custom-domain-1.com',
// Default subdomain for Preview deployments and for local development
defaultForPreview: true,
},
{
name: 'This is Site 2',
description: 'Subdomain only',
subdomain: 'test2',
},
{
name: 'This is Site 3',
description: 'Subdomain only',
subdomain: 'test3',
},
]
const DEFAULT_HOST = hostnamesDB.find((h) => h.defaultForPreview)
/**
* Returns the data of the hostname based on its subdomain or custom domain
* or the default host if there's no match.
*
* This method is used by middleware.ts
*/
export async function getHostnameDataOrDefault(
subdomainOrCustomDomain?: string
) {
if (!subdomainOrCustomDomain) return DEFAULT_HOST
// check if site is a custom domain or a subdomain
const customDomain = subdomainOrCustomDomain.includes('.')
// fetch data from mock database using the site value as the key
return (
hostnamesDB.find((item) =>
customDomain
? item.customDomain === subdomainOrCustomDomain
: item.subdomain === subdomainOrCustomDomain
) ?? DEFAULT_HOST
)
}
/**
* Returns the data of the hostname based on its subdomain.
*
* This method is used by pages under middleware.ts
*/
export async function getHostnameDataBySubdomain(subdomain: string) {
return hostnamesDB.find((item) => item.subdomain === subdomain)
}
/**
* Returns the paths for `getStaticPaths` based on the subdomain of every
* available hostname.
*/
export async function getSubdomainPaths() {
// get all sites that have subdomains set up
const subdomains = hostnamesDB.filter((item) => item.subdomain)
// build paths for each of the sites in the previous two lists
return subdomains.map((item) => {
return { params: { site: item.subdomain } }
})
}
export default hostnamesDB
این یکی کاملاً خود توضیحی است. ما اساساً برخی از داده های ساختگی داریم که از آنها برای دریافت زیر دامنه و داده ها برای نمایش در قسمت جلو استفاده می کنیم.
باز هم خط به خط بروید و نظرات را بخوانید.
مرحله 6: برنامه را تست و اجرا کنید
اکنون می توانیم اجرا کنیم npm run dev
برای تست برنامه
به localhost:3000 بروید (مطمئن شوید که روی localhost:3000 اجرا میکنید زیرا این همان چیزی است که ما در فایل Middleware.ts قرار دادهایم).
حالا می توانید سعی کنید به test2.localhost:3000
. محتوا باید به This is Site 2
اگر اکنون برنامه خود را مستقر کنید، باید به همین صورت عمل کند! vercel --prod
مرحله 7: مراحل بعدی
اکنون می توانید داده های ساختگی را با یک پایگاه داده مانند PlanetScale جایگزین کنید و از Prisma ORM استفاده کنید تا کاربران خود زیر دامنه خود را راه اندازی کنند و داده ها را به سایت های خود اضافه کنند! من آموزش های بیشتری در مورد نحوه انجام این کار اضافه خواهم کرد، پس با ما همراه باشید.
امیدوارم این مقاله مفید بوده باشد!